Showing
35 changed files
with
1649 additions
and
542 deletions
API接口文档.md
0 → 100644
This diff is collapsed. Click to expand it.
| ... | @@ -5,8 +5,11 @@ import com.apple.erp.dto.request.RoleQueryReq; | ... | @@ -5,8 +5,11 @@ import com.apple.erp.dto.request.RoleQueryReq; |
| 5 | import com.apple.erp.dto.request.RoleUpdateReq; | 5 | import com.apple.erp.dto.request.RoleUpdateReq; |
| 6 | import com.apple.erp.dto.response.ApiRes; | 6 | import com.apple.erp.dto.response.ApiRes; |
| 7 | import com.apple.erp.dto.response.RoleRes; | 7 | import com.apple.erp.dto.response.RoleRes; |
| 8 | +import com.apple.erp.dto.response.MenuRes; | ||
| 8 | import com.apple.erp.entity.SysRole; | 9 | import com.apple.erp.entity.SysRole; |
| 10 | +import com.apple.erp.entity.SysMenu; | ||
| 9 | import com.apple.erp.service.SysRoleService; | 11 | import com.apple.erp.service.SysRoleService; |
| 12 | +import com.apple.erp.mapper.SysMenuMapper; | ||
| 10 | import com.baomidou.mybatisplus.core.metadata.IPage; | 13 | import com.baomidou.mybatisplus.core.metadata.IPage; |
| 11 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | 14 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| 12 | import io.swagger.v3.oas.annotations.Operation; | 15 | import io.swagger.v3.oas.annotations.Operation; |
| ... | @@ -21,7 +24,6 @@ import org.springframework.web.bind.annotation.*; | ... | @@ -21,7 +24,6 @@ import org.springframework.web.bind.annotation.*; |
| 21 | import javax.validation.Valid; | 24 | import javax.validation.Valid; |
| 22 | import javax.validation.constraints.NotNull; | 25 | import javax.validation.constraints.NotNull; |
| 23 | import java.time.LocalDateTime; | 26 | import java.time.LocalDateTime; |
| 24 | -import java.util.Arrays; | ||
| 25 | import java.util.List; | 27 | import java.util.List; |
| 26 | import java.util.stream.Collectors; | 28 | import java.util.stream.Collectors; |
| 27 | 29 | ||
| ... | @@ -41,6 +43,9 @@ public class SysRoleController { | ... | @@ -41,6 +43,9 @@ public class SysRoleController { |
| 41 | @Autowired | 43 | @Autowired |
| 42 | private SysRoleService sysRoleService; | 44 | private SysRoleService sysRoleService; |
| 43 | 45 | ||
| 46 | + @Autowired | ||
| 47 | + private SysMenuMapper sysMenuMapper; | ||
| 48 | + | ||
| 44 | /** | 49 | /** |
| 45 | * 获取角色列表 | 50 | * 获取角色列表 |
| 46 | * 支持分页查询和条件筛选,包括角色名称、角色编码、状态等条件 | 51 | * 支持分页查询和条件筛选,包括角色名称、角色编码、状态等条件 |
| ... | @@ -156,7 +161,7 @@ public class SysRoleController { | ... | @@ -156,7 +161,7 @@ public class SysRoleController { |
| 156 | * @throws RuntimeException 当角色编码或名称已存在时 | 161 | * @throws RuntimeException 当角色编码或名称已存在时 |
| 157 | */ | 162 | */ |
| 158 | @Operation(summary = "新增角色", description = "创建新角色,包括角色基本信息和菜单权限分配") | 163 | @Operation(summary = "新增角色", description = "创建新角色,包括角色基本信息和菜单权限分配") |
| 159 | - @PostMapping | 164 | + @PostMapping("/add") |
| 160 | @PreAuthorize("hasAuthority('sys:role:add')") | 165 | @PreAuthorize("hasAuthority('sys:role:add')") |
| 161 | public ApiRes<Void> add( | 166 | public ApiRes<Void> add( |
| 162 | @Parameter(description = "角色新增请求,包含角色基本信息和菜单ID列表", required = true) | 167 | @Parameter(description = "角色新增请求,包含角色基本信息和菜单ID列表", required = true) |
| ... | @@ -222,7 +227,7 @@ public class SysRoleController { | ... | @@ -222,7 +227,7 @@ public class SysRoleController { |
| 222 | * @throws RuntimeException 当角色不存在或角色编码/名称已存在时 | 227 | * @throws RuntimeException 当角色不存在或角色编码/名称已存在时 |
| 223 | */ | 228 | */ |
| 224 | @Operation(summary = "修改角色", description = "更新角色基本信息,包括角色资料和菜单权限分配") | 229 | @Operation(summary = "修改角色", description = "更新角色基本信息,包括角色资料和菜单权限分配") |
| 225 | - @PutMapping | 230 | + @PostMapping("/edit") |
| 226 | @PreAuthorize("hasAuthority('sys:role:edit')") | 231 | @PreAuthorize("hasAuthority('sys:role:edit')") |
| 227 | public ApiRes<Void> edit( | 232 | public ApiRes<Void> edit( |
| 228 | @Parameter(description = "角色更新请求,包含角色ID、基本信息和菜单ID列表", required = true) | 233 | @Parameter(description = "角色更新请求,包含角色ID、基本信息和菜单ID列表", required = true) |
| ... | @@ -329,7 +334,7 @@ public class SysRoleController { | ... | @@ -329,7 +334,7 @@ public class SysRoleController { |
| 329 | * @throws RuntimeException 当尝试修改超级管理员角色状态时 | 334 | * @throws RuntimeException 当尝试修改超级管理员角色状态时 |
| 330 | */ | 335 | */ |
| 331 | @Operation(summary = "修改角色状态", description = "启用或停用角色") | 336 | @Operation(summary = "修改角色状态", description = "启用或停用角色") |
| 332 | - @PutMapping("/changeStatus") | 337 | + @PostMapping("/changeStatus") |
| 333 | @PreAuthorize("hasAuthority('sys:role:edit')") | 338 | @PreAuthorize("hasAuthority('sys:role:edit')") |
| 334 | public ApiRes<Void> changeStatus( | 339 | public ApiRes<Void> changeStatus( |
| 335 | @Parameter(description = "角色信息,主要使用角色ID和状态字段", required = true) @RequestBody SysRole role) { | 340 | @Parameter(description = "角色信息,主要使用角色ID和状态字段", required = true) @RequestBody SysRole role) { |
| ... | @@ -358,6 +363,53 @@ public class SysRoleController { | ... | @@ -358,6 +363,53 @@ public class SysRoleController { |
| 358 | roleRes.setStatusText(role.getStatus() == 1 ? "正常" : "停用"); | 363 | roleRes.setStatusText(role.getStatus() == 1 ? "正常" : "停用"); |
| 359 | } | 364 | } |
| 360 | 365 | ||
| 366 | + // 获取角色菜单权限 | ||
| 367 | + if (role.getRoleId() != null) { | ||
| 368 | + List<SysMenu> menuList = sysMenuMapper.selectMenuTreeByRoleId(role.getRoleId()); | ||
| 369 | + if (menuList != null && !menuList.isEmpty()) { | ||
| 370 | + List<MenuRes> menuResList = menuList.stream() | ||
| 371 | + .map(this::convertToMenuRes) | ||
| 372 | + .collect(Collectors.toList()); | ||
| 373 | + roleRes.setMenus(menuResList); | ||
| 374 | + } | ||
| 375 | + } | ||
| 376 | + | ||
| 361 | return roleRes; | 377 | return roleRes; |
| 362 | } | 378 | } |
| 379 | + | ||
| 380 | + /** | ||
| 381 | + * 转换菜单实体为菜单响应DTO | ||
| 382 | + * | ||
| 383 | + * @param menu 菜单实体 | ||
| 384 | + * @return 菜单响应DTO | ||
| 385 | + */ | ||
| 386 | + private MenuRes convertToMenuRes(SysMenu menu) { | ||
| 387 | + MenuRes menuRes = new MenuRes(); | ||
| 388 | + BeanUtils.copyProperties(menu, menuRes); | ||
| 389 | + | ||
| 390 | + // 设置菜单类型描述 | ||
| 391 | + if (menu.getMenuType() != null) { | ||
| 392 | + switch (menu.getMenuType()) { | ||
| 393 | + case "0": | ||
| 394 | + menuRes.setMenuTypeText("目录"); | ||
| 395 | + break; | ||
| 396 | + case "1": | ||
| 397 | + menuRes.setMenuTypeText("菜单"); | ||
| 398 | + break; | ||
| 399 | + case "2": | ||
| 400 | + menuRes.setMenuTypeText("按钮"); | ||
| 401 | + break; | ||
| 402 | + default: | ||
| 403 | + menuRes.setMenuTypeText("未知"); | ||
| 404 | + break; | ||
| 405 | + } | ||
| 406 | + } | ||
| 407 | + | ||
| 408 | + // 设置状态描述 | ||
| 409 | + if (menu.getStatus() != null) { | ||
| 410 | + menuRes.setStatusText(menu.getStatus().equals(1) ? "显示" : "隐藏"); | ||
| 411 | + } | ||
| 412 | + | ||
| 413 | + return menuRes; | ||
| 414 | + } | ||
| 363 | } | 415 | } | ... | ... |
| ... | @@ -8,6 +8,7 @@ import com.apple.erp.dto.response.ApiRes; | ... | @@ -8,6 +8,7 @@ import com.apple.erp.dto.response.ApiRes; |
| 8 | import com.apple.erp.dto.response.UserRes; | 8 | import com.apple.erp.dto.response.UserRes; |
| 9 | import com.apple.erp.entity.SysUser; | 9 | import com.apple.erp.entity.SysUser; |
| 10 | import com.apple.erp.service.SysUserService; | 10 | import com.apple.erp.service.SysUserService; |
| 11 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||
| 11 | import com.baomidou.mybatisplus.core.metadata.IPage; | 12 | import com.baomidou.mybatisplus.core.metadata.IPage; |
| 12 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | 13 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| 13 | import io.swagger.v3.oas.annotations.Operation; | 14 | import io.swagger.v3.oas.annotations.Operation; |
| ... | @@ -17,6 +18,7 @@ import org.springframework.beans.BeanUtils; | ... | @@ -17,6 +18,7 @@ import org.springframework.beans.BeanUtils; |
| 17 | import org.springframework.beans.factory.annotation.Autowired; | 18 | import org.springframework.beans.factory.annotation.Autowired; |
| 18 | import org.springframework.security.access.prepost.PreAuthorize; | 19 | import org.springframework.security.access.prepost.PreAuthorize; |
| 19 | import org.springframework.security.crypto.password.PasswordEncoder; | 20 | import org.springframework.security.crypto.password.PasswordEncoder; |
| 21 | +import org.springframework.transaction.annotation.Transactional; | ||
| 20 | import org.springframework.validation.annotation.Validated; | 22 | import org.springframework.validation.annotation.Validated; |
| 21 | import org.springframework.web.bind.annotation.*; | 23 | import org.springframework.web.bind.annotation.*; |
| 22 | 24 | ||
| ... | @@ -53,7 +55,7 @@ public class SysUserController { | ... | @@ -53,7 +55,7 @@ public class SysUserController { |
| 53 | * @apiNote 接口路径: GET /api/system/user/list | 55 | * @apiNote 接口路径: GET /api/system/user/list |
| 54 | * @apiNote 权限要求: sys:user:list | 56 | * @apiNote 权限要求: sys:user:list |
| 55 | * @apiNote 支持分页参数: pageNum(页码), pageSize(每页大小) | 57 | * @apiNote 支持分页参数: pageNum(页码), pageSize(每页大小) |
| 56 | - * @apiNote 支持筛选条件: username(用户名), realName(真实姓名), phone(手机号), email(邮箱), status(状态) | 58 | + * @apiNote 支持筛选条件: username(用户名), realName(真实姓名), phone(手机号), email(邮箱), status(状态), startTime(开始时间), endTime(结束时间) |
| 57 | * | 59 | * |
| 58 | * @param queryReq 用户查询条件,包含分页参数和筛选条件 | 60 | * @param queryReq 用户查询条件,包含分页参数和筛选条件 |
| 59 | * - pageNum: 页码,默认1 | 61 | * - pageNum: 页码,默认1 |
| ... | @@ -63,6 +65,8 @@ public class SysUserController { | ... | @@ -63,6 +65,8 @@ public class SysUserController { |
| 63 | * - phone: 手机号,支持模糊查询 | 65 | * - phone: 手机号,支持模糊查询 |
| 64 | * - email: 邮箱,支持模糊查询 | 66 | * - email: 邮箱,支持模糊查询 |
| 65 | * - status: 用户状态,0-停用,1-启用 | 67 | * - status: 用户状态,0-停用,1-启用 |
| 68 | + * - startTime: 开始时间,创建时间范围查询的起始时间 | ||
| 69 | + * - endTime: 结束时间,创建时间范围查询的结束时间 | ||
| 66 | * @return 分页的用户列表数据 | 70 | * @return 分页的用户列表数据 |
| 67 | * - code: 响应码,200表示成功 | 71 | * - code: 响应码,200表示成功 |
| 68 | * - message: 响应消息 | 72 | * - message: 响应消息 |
| ... | @@ -73,18 +77,17 @@ public class SysUserController { | ... | @@ -73,18 +77,17 @@ public class SysUserController { |
| 73 | * - size: 每页大小 | 77 | * - size: 每页大小 |
| 74 | * @throws SecurityException 当用户没有sys:user:list权限时 | 78 | * @throws SecurityException 当用户没有sys:user:list权限时 |
| 75 | */ | 79 | */ |
| 76 | - @Operation(summary = "获取用户列表", description = "支持分页查询和条件筛选,包括用户名、真实姓名、手机号、邮箱、状态等条件") | 80 | + @Operation(summary = "获取用户列表", description = "支持分页查询和条件筛选,包括用户名、真实姓名、手机号、邮箱、状态、创建时间范围等条件") |
| 77 | @GetMapping("/list") | 81 | @GetMapping("/list") |
| 78 | @PreAuthorize("hasAuthority('sys:user:list')") | 82 | @PreAuthorize("hasAuthority('sys:user:list')") |
| 79 | public ApiRes<IPage<UserRes>> list( | 83 | public ApiRes<IPage<UserRes>> list( |
| 80 | @Parameter(description = "用户查询条件,包含分页参数和筛选条件") UserQueryReq queryReq) { | 84 | @Parameter(description = "用户查询条件,包含分页参数和筛选条件") UserQueryReq queryReq) { |
| 81 | Page<SysUser> page = new Page<>(queryReq.getPageNum(), queryReq.getPageSize()); | 85 | Page<SysUser> page = new Page<>(queryReq.getPageNum(), queryReq.getPageSize()); |
| 82 | 86 | ||
| 83 | - // 构建查询条件 | 87 | + // 获取查询条件包装器(包含所有查询条件) |
| 84 | - SysUser queryUser = new SysUser(); | 88 | + LambdaQueryWrapper<SysUser> wrapper = sysUserService.getQueryWrapper(queryReq); |
| 85 | - BeanUtils.copyProperties(queryReq, queryUser); | ||
| 86 | 89 | ||
| 87 | - IPage<SysUser> userPage = sysUserService.page(page, sysUserService.getQueryWrapper(queryUser)); | 90 | + IPage<SysUser> userPage = sysUserService.page(page, wrapper); |
| 88 | 91 | ||
| 89 | // 转换为响应DTO | 92 | // 转换为响应DTO |
| 90 | List<UserRes> userResList = userPage.getRecords().stream() | 93 | List<UserRes> userResList = userPage.getRecords().stream() |
| ... | @@ -143,7 +146,7 @@ public class SysUserController { | ... | @@ -143,7 +146,7 @@ public class SysUserController { |
| 143 | * 新增用户 | 146 | * 新增用户 |
| 144 | * 创建新用户,包括用户基本信息和角色分配 | 147 | * 创建新用户,包括用户基本信息和角色分配 |
| 145 | * | 148 | * |
| 146 | - * @apiNote 接口路径: POST /api/system/user | 149 | + * @apiNote 接口路径: POST /api/system/user/add |
| 147 | * @apiNote 权限要求: sys:user:add | 150 | * @apiNote 权限要求: sys:user:add |
| 148 | * @apiNote 功能说明: 创建新用户账户,支持同时分配角色权限 | 151 | * @apiNote 功能说明: 创建新用户账户,支持同时分配角色权限 |
| 149 | * @apiNote 数据验证: 用户名唯一性检查,密码强度验证,邮箱格式验证 | 152 | * @apiNote 数据验证: 用户名唯一性检查,密码强度验证,邮箱格式验证 |
| ... | @@ -165,8 +168,9 @@ public class SysUserController { | ... | @@ -165,8 +168,9 @@ public class SysUserController { |
| 165 | * @throws RuntimeException 当用户名已存在时 | 168 | * @throws RuntimeException 当用户名已存在时 |
| 166 | */ | 169 | */ |
| 167 | @Operation(summary = "新增用户", description = "创建新用户,包括用户基本信息和角色分配") | 170 | @Operation(summary = "新增用户", description = "创建新用户,包括用户基本信息和角色分配") |
| 168 | - @PostMapping | 171 | + @PostMapping("/add") |
| 169 | @PreAuthorize("hasAuthority('sys:user:add')") | 172 | @PreAuthorize("hasAuthority('sys:user:add')") |
| 173 | + @Transactional(rollbackFor = Exception.class) | ||
| 170 | public ApiRes<Void> add( | 174 | public ApiRes<Void> add( |
| 171 | @Parameter(description = "用户新增请求,包含用户基本信息和角色ID列表", required = true) | 175 | @Parameter(description = "用户新增请求,包含用户基本信息和角色ID列表", required = true) |
| 172 | @Valid @RequestBody UserAddReq addReq) { | 176 | @Valid @RequestBody UserAddReq addReq) { |
| ... | @@ -198,7 +202,7 @@ public class SysUserController { | ... | @@ -198,7 +202,7 @@ public class SysUserController { |
| 198 | * 修改用户 | 202 | * 修改用户 |
| 199 | * 更新用户基本信息,包括用户资料和角色分配 | 203 | * 更新用户基本信息,包括用户资料和角色分配 |
| 200 | * | 204 | * |
| 201 | - * @apiNote 接口路径: PUT /api/system/user | 205 | + * @apiNote 接口路径: POST /api/system/user/edit |
| 202 | * @apiNote 权限要求: sys:user:edit | 206 | * @apiNote 权限要求: sys:user:edit |
| 203 | * @apiNote 功能说明: 更新现有用户的基本信息和角色分配 | 207 | * @apiNote 功能说明: 更新现有用户的基本信息和角色分配 |
| 204 | * @apiNote 数据验证: 用户存在性检查,邮箱格式验证,角色有效性验证 | 208 | * @apiNote 数据验证: 用户存在性检查,邮箱格式验证,角色有效性验证 |
| ... | @@ -219,8 +223,9 @@ public class SysUserController { | ... | @@ -219,8 +223,9 @@ public class SysUserController { |
| 219 | * @throws RuntimeException 当用户不存在时 | 223 | * @throws RuntimeException 当用户不存在时 |
| 220 | */ | 224 | */ |
| 221 | @Operation(summary = "修改用户", description = "更新用户基本信息,包括用户资料和角色分配") | 225 | @Operation(summary = "修改用户", description = "更新用户基本信息,包括用户资料和角色分配") |
| 222 | - @PutMapping | 226 | + @PostMapping("/edit") |
| 223 | @PreAuthorize("hasAuthority('sys:user:edit')") | 227 | @PreAuthorize("hasAuthority('sys:user:edit')") |
| 228 | + @Transactional(rollbackFor = Exception.class) | ||
| 224 | public ApiRes<Void> edit( | 229 | public ApiRes<Void> edit( |
| 225 | @Parameter(description = "用户更新请求,包含用户ID、基本信息和角色ID列表", required = true) | 230 | @Parameter(description = "用户更新请求,包含用户ID、基本信息和角色ID列表", required = true) |
| 226 | @Valid @RequestBody UserUpdateReq updateReq) { | 231 | @Valid @RequestBody UserUpdateReq updateReq) { |
| ... | @@ -274,6 +279,7 @@ public class SysUserController { | ... | @@ -274,6 +279,7 @@ public class SysUserController { |
| 274 | @Operation(summary = "删除用户", description = "批量删除用户,会同时清理用户角色关联关系") | 279 | @Operation(summary = "删除用户", description = "批量删除用户,会同时清理用户角色关联关系") |
| 275 | @DeleteMapping("/{userIds}") | 280 | @DeleteMapping("/{userIds}") |
| 276 | @PreAuthorize("hasAuthority('sys:user:remove')") | 281 | @PreAuthorize("hasAuthority('sys:user:remove')") |
| 282 | + @Transactional(rollbackFor = Exception.class) | ||
| 277 | public ApiRes<Void> remove( | 283 | public ApiRes<Void> remove( |
| 278 | @Parameter(description = "需要删除的用户ID数组", required = true) @PathVariable Long[] userIds) { | 284 | @Parameter(description = "需要删除的用户ID数组", required = true) @PathVariable Long[] userIds) { |
| 279 | // 检查是否包含超级管理员 | 285 | // 检查是否包含超级管理员 |
| ... | @@ -341,7 +347,7 @@ public class SysUserController { | ... | @@ -341,7 +347,7 @@ public class SysUserController { |
| 341 | * 状态修改 | 347 | * 状态修改 |
| 342 | * 启用或停用用户账户 | 348 | * 启用或停用用户账户 |
| 343 | * | 349 | * |
| 344 | - * @apiNote 接口路径: PUT /api/system/user/changeStatus | 350 | + * @apiNote 接口路径: POST /api/system/user/changeStatus |
| 345 | * @apiNote 权限要求: sys:user:edit | 351 | * @apiNote 权限要求: sys:user:edit |
| 346 | * @apiNote 功能说明: 修改用户账户状态,支持启用或停用用户 | 352 | * @apiNote 功能说明: 修改用户账户状态,支持启用或停用用户 |
| 347 | * @apiNote 安全限制: 不允许修改超级管理员用户状态 | 353 | * @apiNote 安全限制: 不允许修改超级管理员用户状态 |
| ... | @@ -358,7 +364,7 @@ public class SysUserController { | ... | @@ -358,7 +364,7 @@ public class SysUserController { |
| 358 | * @throws RuntimeException 当尝试修改超级管理员状态时 | 364 | * @throws RuntimeException 当尝试修改超级管理员状态时 |
| 359 | */ | 365 | */ |
| 360 | @Operation(summary = "修改用户状态", description = "启用或停用用户账户") | 366 | @Operation(summary = "修改用户状态", description = "启用或停用用户账户") |
| 361 | - @PutMapping("/changeStatus") | 367 | + @PostMapping("/changeStatus") |
| 362 | @PreAuthorize("hasAuthority('sys:user:edit')") | 368 | @PreAuthorize("hasAuthority('sys:user:edit')") |
| 363 | public ApiRes<Void> changeStatus( | 369 | public ApiRes<Void> changeStatus( |
| 364 | @Parameter(description = "用户信息,主要使用用户ID和状态字段", required = true) @RequestBody SysUser user) { | 370 | @Parameter(description = "用户信息,主要使用用户ID和状态字段", required = true) @RequestBody SysUser user) { | ... | ... |
| ... | @@ -2,6 +2,7 @@ package com.apple.erp.dto.request; | ... | @@ -2,6 +2,7 @@ package com.apple.erp.dto.request; |
| 2 | 2 | ||
| 3 | import lombok.Data; | 3 | import lombok.Data; |
| 4 | 4 | ||
| 5 | + | ||
| 5 | /** | 6 | /** |
| 6 | * 用户查询请求DTO | 7 | * 用户查询请求DTO |
| 7 | * | 8 | * |
| ... | @@ -38,6 +39,16 @@ public class UserQueryReq { | ... | @@ -38,6 +39,16 @@ public class UserQueryReq { |
| 38 | private Integer status; | 39 | private Integer status; |
| 39 | 40 | ||
| 40 | /** | 41 | /** |
| 42 | + * 开始时间(创建时间范围查询) | ||
| 43 | + */ | ||
| 44 | + private String startTime; | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * 结束时间(创建时间范围查询) | ||
| 48 | + */ | ||
| 49 | + private String endTime; | ||
| 50 | + | ||
| 51 | + /** | ||
| 41 | * 页码 | 52 | * 页码 |
| 42 | */ | 53 | */ |
| 43 | private Integer pageNum = 1; | 54 | private Integer pageNum = 1; | ... | ... |
| ... | @@ -33,8 +33,9 @@ public interface SysUserMapper extends BaseMapper<SysUser> { | ... | @@ -33,8 +33,9 @@ public interface SysUserMapper extends BaseMapper<SysUser> { |
| 33 | 33 | ||
| 34 | /** | 34 | /** |
| 35 | * 根据用户名查询用户基本信息 | 35 | * 根据用户名查询用户基本信息 |
| 36 | + * 用于用户名唯一性检查,需要查询所有状态的用户(包括停用用户) | ||
| 36 | */ | 37 | */ |
| 37 | - @Select("SELECT * FROM t_sys_user WHERE username = #{username} AND del_flag = '0' AND status = 1") | 38 | + @Select("SELECT * FROM t_sys_user WHERE username = #{username} AND del_flag = '0'") |
| 38 | SysUser findByUsername(@Param("username") String username); | 39 | SysUser findByUsername(@Param("username") String username); |
| 39 | 40 | ||
| 40 | /** | 41 | /** | ... | ... |
| 1 | package com.apple.erp.service; | 1 | package com.apple.erp.service; |
| 2 | 2 | ||
| 3 | +import com.apple.erp.dto.request.UserQueryReq; | ||
| 3 | import com.apple.erp.dto.response.RoleRes; | 4 | import com.apple.erp.dto.response.RoleRes; |
| 4 | import com.apple.erp.entity.SysUser; | 5 | import com.apple.erp.entity.SysUser; |
| 5 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | 6 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| ... | @@ -38,12 +39,12 @@ public interface SysUserService extends IService<SysUser> { | ... | @@ -38,12 +39,12 @@ public interface SysUserService extends IService<SysUser> { |
| 38 | 39 | ||
| 39 | /** | 40 | /** |
| 40 | * 获取用户查询条件包装器 | 41 | * 获取用户查询条件包装器 |
| 41 | - * 根据用户信息构建MyBatis Plus查询条件,支持用户名、真实姓名、手机号、邮箱、状态的模糊查询 | 42 | + * 根据查询请求构建MyBatis Plus查询条件,支持用户名、真实姓名、手机号、邮箱、状态、创建时间范围等条件 |
| 42 | * | 43 | * |
| 43 | - * @param user 用户查询条件对象 | 44 | + * @param queryReq 用户查询请求对象 |
| 44 | * @return MyBatis Plus查询条件包装器 | 45 | * @return MyBatis Plus查询条件包装器 |
| 45 | */ | 46 | */ |
| 46 | - LambdaQueryWrapper<SysUser> getQueryWrapper(SysUser user); | 47 | + LambdaQueryWrapper<SysUser> getQueryWrapper(UserQueryReq queryReq); |
| 47 | 48 | ||
| 48 | /** | 49 | /** |
| 49 | * 获取用户角色列表 | 50 | * 获取用户角色列表 | ... | ... |
| 1 | package com.apple.erp.service.impl; | 1 | package com.apple.erp.service.impl; |
| 2 | 2 | ||
| 3 | +import com.apple.erp.dto.request.UserQueryReq; | ||
| 3 | import com.apple.erp.dto.response.RoleRes; | 4 | import com.apple.erp.dto.response.RoleRes; |
| 4 | import com.apple.erp.entity.SysRole; | 5 | import com.apple.erp.entity.SysRole; |
| 5 | import com.apple.erp.entity.SysUser; | 6 | import com.apple.erp.entity.SysUser; |
| ... | @@ -17,6 +18,8 @@ import org.springframework.stereotype.Service; | ... | @@ -17,6 +18,8 @@ import org.springframework.stereotype.Service; |
| 17 | import org.springframework.transaction.annotation.Transactional; | 18 | import org.springframework.transaction.annotation.Transactional; |
| 18 | 19 | ||
| 19 | import java.time.LocalDateTime; | 20 | import java.time.LocalDateTime; |
| 21 | +import java.time.format.DateTimeFormatter; | ||
| 22 | +import java.time.format.DateTimeParseException; | ||
| 20 | import java.util.ArrayList; | 23 | import java.util.ArrayList; |
| 21 | import java.util.List; | 24 | import java.util.List; |
| 22 | import java.util.concurrent.TimeUnit; | 25 | import java.util.concurrent.TimeUnit; |
| ... | @@ -93,39 +96,55 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl | ... | @@ -93,39 +96,55 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl |
| 93 | 96 | ||
| 94 | /** | 97 | /** |
| 95 | * 获取用户查询条件包装器 | 98 | * 获取用户查询条件包装器 |
| 96 | - * 根据用户信息构建MyBatis Plus查询条件,支持用户名、真实姓名、手机号、邮箱、状态的模糊查询 | 99 | + * 根据查询请求构建MyBatis Plus查询条件,支持用户名、真实姓名、手机号、邮箱、状态、创建时间范围等条件 |
| 97 | * 优化判空规则,兼容空字符串和null的情况 | 100 | * 优化判空规则,兼容空字符串和null的情况 |
| 98 | * | 101 | * |
| 99 | - * @param user 用户查询条件对象 | 102 | + * @param queryReq 用户查询请求对象 |
| 100 | * @return MyBatis Plus查询条件包装器 | 103 | * @return MyBatis Plus查询条件包装器 |
| 101 | */ | 104 | */ |
| 102 | @Override | 105 | @Override |
| 103 | - public LambdaQueryWrapper<SysUser> getQueryWrapper(SysUser user) { | 106 | + public LambdaQueryWrapper<SysUser> getQueryWrapper(UserQueryReq queryReq) { |
| 104 | LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>(); | 107 | LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>(); |
| 105 | 108 | ||
| 106 | // 用户名:支持模糊查询,过滤空字符串和null | 109 | // 用户名:支持模糊查询,过滤空字符串和null |
| 107 | - if (StringUtils.isNotEmpty(user.getUsername())) { | 110 | + if (StringUtils.isNotEmpty(queryReq.getUsername())) { |
| 108 | - wrapper.like(SysUser::getUsername, user.getUsername()); | 111 | + wrapper.like(SysUser::getUsername, queryReq.getUsername()); |
| 109 | } | 112 | } |
| 110 | 113 | ||
| 111 | // 真实姓名:支持模糊查询,过滤空字符串和null | 114 | // 真实姓名:支持模糊查询,过滤空字符串和null |
| 112 | - if (StringUtils.isNotEmpty(user.getRealName())) { | 115 | + if (StringUtils.isNotEmpty(queryReq.getRealName())) { |
| 113 | - wrapper.like(SysUser::getRealName, user.getRealName()); | 116 | + wrapper.like(SysUser::getRealName, queryReq.getRealName()); |
| 114 | } | 117 | } |
| 115 | 118 | ||
| 116 | // 手机号:支持模糊查询,过滤空字符串和null | 119 | // 手机号:支持模糊查询,过滤空字符串和null |
| 117 | - if (StringUtils.isNotEmpty(user.getPhone())) { | 120 | + if (StringUtils.isNotEmpty(queryReq.getPhone())) { |
| 118 | - wrapper.like(SysUser::getPhone, user.getPhone()); | 121 | + wrapper.like(SysUser::getPhone, queryReq.getPhone()); |
| 119 | } | 122 | } |
| 120 | 123 | ||
| 121 | // 邮箱:支持模糊查询,过滤空字符串和null | 124 | // 邮箱:支持模糊查询,过滤空字符串和null |
| 122 | - if (StringUtils.isNotEmpty(user.getEmail())) { | 125 | + if (StringUtils.isNotEmpty(queryReq.getEmail())) { |
| 123 | - wrapper.like(SysUser::getEmail, user.getEmail()); | 126 | + wrapper.like(SysUser::getEmail, queryReq.getEmail()); |
| 124 | } | 127 | } |
| 125 | 128 | ||
| 126 | // 状态:精确查询 | 129 | // 状态:精确查询 |
| 127 | - if (user.getStatus() != null) { | 130 | + if (queryReq.getStatus() != null) { |
| 128 | - wrapper.eq(SysUser::getStatus, user.getStatus()); | 131 | + wrapper.eq(SysUser::getStatus, queryReq.getStatus()); |
| 132 | + } | ||
| 133 | + | ||
| 134 | + // 开始时间:创建时间 >= 开始时间 | ||
| 135 | + if (StringUtils.isNotEmpty(queryReq.getStartTime())) { | ||
| 136 | + LocalDateTime startDateTime = parseDateTime(queryReq.getStartTime()); | ||
| 137 | + if (startDateTime != null) { | ||
| 138 | + wrapper.ge(SysUser::getCreateTime, startDateTime); | ||
| 139 | + } | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + // 结束时间:创建时间 <= 结束时间 | ||
| 143 | + if (StringUtils.isNotEmpty(queryReq.getEndTime())) { | ||
| 144 | + LocalDateTime endDateTime = parseDateTime(queryReq.getEndTime()); | ||
| 145 | + if (endDateTime != null) { | ||
| 146 | + wrapper.le(SysUser::getCreateTime, endDateTime); | ||
| 147 | + } | ||
| 129 | } | 148 | } |
| 130 | 149 | ||
| 131 | // 固定条件:未删除 | 150 | // 固定条件:未删除 |
| ... | @@ -138,6 +157,44 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl | ... | @@ -138,6 +157,44 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl |
| 138 | } | 157 | } |
| 139 | 158 | ||
| 140 | /** | 159 | /** |
| 160 | + * 解析时间字符串为LocalDateTime | ||
| 161 | + * 支持格式:yyyy-MM-dd, yyyy-MM-dd HH:mm:ss | ||
| 162 | + * | ||
| 163 | + * @param dateTimeStr 时间字符串 | ||
| 164 | + * @return LocalDateTime对象,解析失败返回null | ||
| 165 | + */ | ||
| 166 | + private LocalDateTime parseDateTime(String dateTimeStr) { | ||
| 167 | + if (StringUtils.isEmpty(dateTimeStr)) { | ||
| 168 | + return null; | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + // 支持的时间格式 | ||
| 172 | + DateTimeFormatter[] formatters = { | ||
| 173 | + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"), | ||
| 174 | + DateTimeFormatter.ofPattern("yyyy-MM-dd") | ||
| 175 | + }; | ||
| 176 | + | ||
| 177 | + // 尝试各种格式解析 | ||
| 178 | + for (DateTimeFormatter formatter : formatters) { | ||
| 179 | + try { | ||
| 180 | + if (formatter.toString().contains("HH:mm:ss")) { | ||
| 181 | + // 完整时间格式 | ||
| 182 | + return LocalDateTime.parse(dateTimeStr, formatter); | ||
| 183 | + } else { | ||
| 184 | + // 仅日期格式,转换为当天的00:00:00 | ||
| 185 | + return LocalDateTime.parse(dateTimeStr + " 00:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); | ||
| 186 | + } | ||
| 187 | + } catch (DateTimeParseException e) { | ||
| 188 | + continue; | ||
| 189 | + } | ||
| 190 | + } | ||
| 191 | + | ||
| 192 | + // 解析失败,记录日志 | ||
| 193 | + System.err.println("时间格式解析失败: " + dateTimeStr); | ||
| 194 | + return null; | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + /** | ||
| 141 | * 获取用户角色列表 | 198 | * 获取用户角色列表 |
| 142 | * 查询指定用户拥有的所有角色信息,包含角色基本信息和状态描述 | 199 | * 查询指定用户拥有的所有角色信息,包含角色基本信息和状态描述 |
| 143 | * | 200 | * | ... | ... |
| ... | @@ -54,7 +54,7 @@ | ... | @@ -54,7 +54,7 @@ |
| 54 | select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.menu_type, m.status, m.perms, m.icon, m.create_by, m.create_time, m.update_by, m.update_time, m.del_flag | 54 | select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.menu_type, m.status, m.perms, m.icon, m.create_by, m.create_time, m.update_by, m.update_time, m.del_flag |
| 55 | from t_sys_menu m | 55 | from t_sys_menu m |
| 56 | left join t_sys_role_menu rm on m.menu_id = rm.menu_id | 56 | left join t_sys_role_menu rm on m.menu_id = rm.menu_id |
| 57 | - where rm.role_id = #{roleId} and m.menu_type in ('0','1') and m.status = 1 and m.del_flag = '0' | 57 | + where rm.role_id = #{roleId} and m.status = 1 and m.del_flag = '0' |
| 58 | order by m.parent_id, m.sort | 58 | order by m.parent_id, m.sort |
| 59 | </select> | 59 | </select> |
| 60 | 60 | ... | ... |
| ... | @@ -34,7 +34,7 @@ | ... | @@ -34,7 +34,7 @@ |
| 34 | <!-- 根据用户名查询用户基本信息 --> | 34 | <!-- 根据用户名查询用户基本信息 --> |
| 35 | <select id="findByUsername" resultType="com.apple.erp.entity.SysUser"> | 35 | <select id="findByUsername" resultType="com.apple.erp.entity.SysUser"> |
| 36 | SELECT * FROM t_sys_user | 36 | SELECT * FROM t_sys_user |
| 37 | - WHERE username = #{username} AND del_flag = '0' AND status = 1 | 37 | + WHERE username = #{username} |
| 38 | </select> | 38 | </select> |
| 39 | 39 | ||
| 40 | <!-- 根据用户ID查询菜单权限标识列表 --> | 40 | <!-- 根据用户ID查询菜单权限标识列表 --> | ... | ... |
| ... | @@ -79,9 +79,3 @@ declare global { | ... | @@ -79,9 +79,3 @@ declare global { |
| 79 | const watchPostEffect: typeof import('vue')['watchPostEffect'] | 79 | const watchPostEffect: typeof import('vue')['watchPostEffect'] |
| 80 | const watchSyncEffect: typeof import('vue')['watchSyncEffect'] | 80 | const watchSyncEffect: typeof import('vue')['watchSyncEffect'] |
| 81 | } | 81 | } |
| 82 | -// for type re-export | ||
| 83 | -declare global { | ||
| 84 | - // @ts-ignore | ||
| 85 | - export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' | ||
| 86 | - import('vue') | ||
| 87 | -} | ... | ... |
| ... | @@ -7,10 +7,21 @@ export {} | ... | @@ -7,10 +7,21 @@ export {} |
| 7 | 7 | ||
| 8 | declare module 'vue' { | 8 | declare module 'vue' { |
| 9 | export interface GlobalComponents { | 9 | export interface GlobalComponents { |
| 10 | + ActionBar: typeof import('./src/components/common/ActionBar.vue')['default'] | ||
| 11 | + DataTable: typeof import('./src/components/common/DataTable.vue')['default'] | ||
| 12 | + ElButton: typeof import('element-plus/es')['ElButton'] | ||
| 13 | + ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] | ||
| 14 | + ElForm: typeof import('element-plus/es')['ElForm'] | ||
| 15 | + ElFormItem: typeof import('element-plus/es')['ElFormItem'] | ||
| 16 | + ElInput: typeof import('element-plus/es')['ElInput'] | ||
| 17 | + ElLink: typeof import('element-plus/es')['ElLink'] | ||
| 10 | Header: typeof import('./src/components/layout/Header.vue')['default'] | 18 | Header: typeof import('./src/components/layout/Header.vue')['default'] |
| 19 | + Pagination: typeof import('./src/components/common/Pagination.vue')['default'] | ||
| 11 | RouterLink: typeof import('vue-router')['RouterLink'] | 20 | RouterLink: typeof import('vue-router')['RouterLink'] |
| 12 | RouterView: typeof import('vue-router')['RouterView'] | 21 | RouterView: typeof import('vue-router')['RouterView'] |
| 22 | + SearchBar: typeof import('./src/components/common/SearchBar.vue')['default'] | ||
| 13 | Sidebar: typeof import('./src/components/layout/Sidebar.vue')['default'] | 23 | Sidebar: typeof import('./src/components/layout/Sidebar.vue')['default'] |
| 14 | SidebarItem: typeof import('./src/components/layout/SidebarItem.vue')['default'] | 24 | SidebarItem: typeof import('./src/components/layout/SidebarItem.vue')['default'] |
| 25 | + TableToolbar: typeof import('./src/components/common/TableToolbar.vue')['default'] | ||
| 15 | } | 26 | } |
| 16 | } | 27 | } | ... | ... |
frontend/index.html
0 → 100644
| 1 | +<!DOCTYPE html> | ||
| 2 | +<html lang="zh-CN"> | ||
| 3 | + <head> | ||
| 4 | + <meta charset="UTF-8" /> | ||
| 5 | + <link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
| 6 | + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| 7 | + <title>Apple经销商ERP系统</title> | ||
| 8 | + <meta name="description" content="Apple经销商ERP系统前端应用" /> | ||
| 9 | + <meta name="keywords" content="Apple,ERP,经销商,管理系统" /> | ||
| 10 | + </head> | ||
| 11 | + <body> | ||
| 12 | + <div id="app"></div> | ||
| 13 | + <script type="module" src="/src/main.ts"></script> | ||
| 14 | + </body> | ||
| 15 | +</html> |
| 1 | <template> | 1 | <template> |
| 2 | - <div id="app"> | 2 | + <!-- <div id="app"> |
| 3 | <h1>Vue应用已启动</h1> | 3 | <h1>Vue应用已启动</h1> |
| 4 | <p>如果您能看到这个页面,说明Vue应用正常运行</p> | 4 | <p>如果您能看到这个页面,说明Vue应用正常运行</p> |
| 5 | <p>当前时间: {{ currentTime }}</p> | 5 | <p>当前时间: {{ currentTime }}</p> |
| 6 | - </div> | 6 | + </div> --> |
| 7 | + <router-view/> | ||
| 7 | </template> | 8 | </template> |
| 8 | 9 | ||
| 9 | <script setup lang="ts"> | 10 | <script setup lang="ts"> |
| ... | @@ -18,9 +19,23 @@ onMounted(() => { | ... | @@ -18,9 +19,23 @@ onMounted(() => { |
| 18 | </script> | 19 | </script> |
| 19 | 20 | ||
| 20 | <style> | 21 | <style> |
| 22 | +* { | ||
| 23 | + margin: 0; | ||
| 24 | + padding: 0; | ||
| 25 | + box-sizing: border-box; | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +html, body { | ||
| 29 | + margin: 0; | ||
| 30 | + padding: 0; | ||
| 31 | + height: 100%; | ||
| 32 | + overflow-x: hidden; | ||
| 33 | +} | ||
| 34 | + | ||
| 21 | #app { | 35 | #app { |
| 22 | height: 100vh; | 36 | height: 100vh; |
| 23 | font-family: Arial, sans-serif; | 37 | font-family: Arial, sans-serif; |
| 24 | - padding: 20px; | 38 | + margin: 0; |
| 39 | + padding: 0; | ||
| 25 | } | 40 | } |
| 26 | </style> | 41 | </style> | ... | ... |
| 1 | -import { request } from '@/utils/request' | 1 | +import axios from 'axios' |
| 2 | -import type { DictType, DictItem, PageRequest, PageResponse } from '@/types' | 2 | + |
| 3 | - | 3 | +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8083' |
| 4 | -// 字典类型相关API | 4 | + |
| 5 | - | 5 | +const api = axios.create({ |
| 6 | -// 获取字典类型列表 | 6 | + baseURL: API_BASE_URL, |
| 7 | -export function getDictTypeListApi(): Promise<DictType[]> { | 7 | + timeout: 10000, |
| 8 | - return request.get('/api/sys/dict/type/list') | 8 | + headers: { |
| 9 | -} | 9 | + 'Content-Type': 'application/json' |
| 10 | - | 10 | + } |
| 11 | -// 获取字典类型分页列表 | 11 | +}) |
| 12 | -export function getDictTypePageApi(params: PageRequest & { | 12 | + |
| 13 | - dictName?: string | 13 | +api.interceptors.request.use( |
| 14 | + (config) => { | ||
| 15 | + const token = localStorage.getItem('token') | ||
| 16 | + if (token) { | ||
| 17 | + config.headers.Authorization = `Bearer ${token}` | ||
| 18 | + } | ||
| 19 | + return config | ||
| 20 | + }, | ||
| 21 | + (error) => { | ||
| 22 | + return Promise.reject(error) | ||
| 23 | + } | ||
| 24 | +) | ||
| 25 | + | ||
| 26 | +api.interceptors.response.use( | ||
| 27 | + (response) => { | ||
| 28 | + return response.data | ||
| 29 | + }, | ||
| 30 | + (error) => { | ||
| 31 | + console.error('API请求错误:', error) | ||
| 32 | + return Promise.reject(error) | ||
| 33 | + } | ||
| 34 | +) | ||
| 35 | + | ||
| 36 | +export interface DictType { | ||
| 37 | + dictTypeId: number | ||
| 38 | + dictType: string | ||
| 39 | + dictName: string | ||
| 40 | + status: number | ||
| 41 | + statusText: string | ||
| 42 | + remark?: string | ||
| 43 | + createBy?: string | ||
| 44 | + createTime: string | ||
| 45 | + updateBy?: string | ||
| 46 | + updateTime: string | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +export interface DictItem { | ||
| 50 | + dictItemId: number | ||
| 51 | + dictTypeId: number | ||
| 52 | + dictType: string | ||
| 53 | + dictLabel: string | ||
| 54 | + dictValue: string | ||
| 55 | + sort: number | ||
| 56 | + remark?: string | ||
| 57 | + createBy?: string | ||
| 58 | + createTime: string | ||
| 59 | + updateBy?: string | ||
| 60 | + updateTime: string | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +export interface DictTypeSearchParams { | ||
| 14 | dictType?: string | 64 | dictType?: string |
| 65 | + dictName?: string | ||
| 15 | status?: number | 66 | status?: number |
| 16 | -}): Promise<PageResponse<DictType>> { | 67 | + pageNum?: number |
| 17 | - return request.get('/api/sys/dict/type/page', params) | 68 | + pageSize?: number |
| 18 | -} | ||
| 19 | - | ||
| 20 | -// 获取字典类型详情 | ||
| 21 | -export function getDictTypeDetailApi(dictId: number): Promise<DictType> { | ||
| 22 | - return request.get(`/api/sys/dict/type/${dictId}`) | ||
| 23 | -} | ||
| 24 | - | ||
| 25 | -// 新增字典类型 | ||
| 26 | -export function addDictTypeApi(data: Partial<DictType>): Promise<void> { | ||
| 27 | - return request.post('/api/sys/dict/type', data) | ||
| 28 | -} | ||
| 29 | - | ||
| 30 | -// 更新字典类型 | ||
| 31 | -export function updateDictTypeApi(data: Partial<DictType>): Promise<void> { | ||
| 32 | - return request.put('/api/sys/dict/type', data) | ||
| 33 | -} | ||
| 34 | - | ||
| 35 | -// 删除字典类型 | ||
| 36 | -export function deleteDictTypeApi(dictId: number): Promise<void> { | ||
| 37 | - return request.delete(`/api/sys/dict/type/${dictId}`) | ||
| 38 | -} | ||
| 39 | - | ||
| 40 | -// 批量删除字典类型 | ||
| 41 | -export function deleteBatchDictTypeApi(dictIds: number[]): Promise<void> { | ||
| 42 | - return request.delete('/api/sys/dict/type/batch', { dictIds }) | ||
| 43 | -} | ||
| 44 | - | ||
| 45 | -// 刷新字典缓存 | ||
| 46 | -export function refreshDictCacheApi(): Promise<void> { | ||
| 47 | - return request.post('/api/sys/dict/type/refreshCache') | ||
| 48 | } | 69 | } |
| 49 | 70 | ||
| 50 | -// 字典项相关API | 71 | +export interface DictItemSearchParams { |
| 51 | - | ||
| 52 | -// 获取字典项列表 | ||
| 53 | -export function getDictItemListApi(dictTypeId: number): Promise<DictItem[]> { | ||
| 54 | - return request.get('/api/sys/dict/item/list', { dictTypeId }) | ||
| 55 | -} | ||
| 56 | - | ||
| 57 | -// 获取字典项分页列表 | ||
| 58 | -export function getDictItemPageApi(params: PageRequest & { | ||
| 59 | dictTypeId?: number | 72 | dictTypeId?: number |
| 60 | dictLabel?: string | 73 | dictLabel?: string |
| 61 | dictValue?: string | 74 | dictValue?: string |
| 62 | - status?: number | 75 | + pageNum?: number |
| 63 | -}): Promise<PageResponse<DictItem>> { | 76 | + pageSize?: number |
| 64 | - return request.get('/api/sys/dict/item/page', params) | 77 | +} |
| 65 | -} | 78 | + |
| 66 | - | 79 | +export interface DictTypeAddReq { |
| 67 | -// 获取字典项详情 | 80 | + dictType: string |
| 68 | -export function getDictItemDetailApi(dictItemId: number): Promise<DictItem> { | 81 | + dictName: string |
| 69 | - return request.get(`/api/sys/dict/item/${dictItemId}`) | 82 | + status: number |
| 70 | -} | 83 | + remark?: string |
| 71 | - | 84 | +} |
| 72 | -// 新增字典项 | 85 | + |
| 73 | -export function addDictItemApi(data: Partial<DictItem>): Promise<void> { | 86 | +export interface DictTypeUpdateReq extends DictTypeAddReq { |
| 74 | - return request.post('/api/sys/dict/item', data) | 87 | + dictTypeId: number |
| 75 | -} | 88 | +} |
| 76 | - | 89 | + |
| 77 | -// 更新字典项 | 90 | +export interface DictItemAddReq { |
| 78 | -export function updateDictItemApi(data: Partial<DictItem>): Promise<void> { | 91 | + dictTypeId: number |
| 79 | - return request.put('/api/sys/dict/item', data) | 92 | + dictLabel: string |
| 80 | -} | 93 | + dictValue: string |
| 81 | - | 94 | + sort?: number |
| 82 | -// 删除字典项 | 95 | + remark?: string |
| 83 | -export function deleteDictItemApi(dictItemId: number): Promise<void> { | 96 | +} |
| 84 | - return request.delete(`/api/sys/dict/item/${dictItemId}`) | 97 | + |
| 85 | -} | 98 | +export interface DictItemUpdateReq extends DictItemAddReq { |
| 86 | - | 99 | + dictItemId: number |
| 87 | -// 批量删除字典项 | 100 | +} |
| 88 | -export function deleteBatchDictItemApi(dictItemIds: number[]): Promise<void> { | 101 | + |
| 89 | - return request.delete('/api/sys/dict/item/batch', { dictItemIds }) | 102 | +export interface ApiResponse<T = any> { |
| 90 | -} | 103 | + code: number |
| 91 | - | 104 | + message: string |
| 92 | -// 根据字典类型获取字典项 | 105 | + data: T |
| 93 | -export function getDictItemsByTypeApi(dictType: string): Promise<DictItem[]> { | 106 | +} |
| 94 | - return request.get(`/api/sys/dict/item/type/${dictType}`) | 107 | + |
| 108 | +export interface PageResponse<T> { | ||
| 109 | + records: T[] | ||
| 110 | + total: number | ||
| 111 | + size: number | ||
| 112 | + current: number | ||
| 113 | + orders: any[] | ||
| 114 | + optimizeCountSql: boolean | ||
| 115 | + searchCount: boolean | ||
| 116 | + maxLimit: any | ||
| 117 | + countId: any | ||
| 118 | + pages: number | ||
| 119 | +} | ||
| 120 | + | ||
| 121 | +export const dictApi = { | ||
| 122 | + // 字典类型管理 | ||
| 123 | + // 获取字典类型列表 | ||
| 124 | + getDictTypes: (params: DictTypeSearchParams): Promise<ApiResponse<PageResponse<DictType>>> => { | ||
| 125 | + return api.get('/api/system/dict/type/list', { params }) | ||
| 126 | + }, | ||
| 127 | + | ||
| 128 | + // 获取字典类型详情 | ||
| 129 | + getDictTypeById: (dictTypeId: number): Promise<ApiResponse<DictType>> => { | ||
| 130 | + return api.get(`/api/system/dict/type/${dictTypeId}`) | ||
| 131 | + }, | ||
| 132 | + | ||
| 133 | + // 新增字典类型 | ||
| 134 | + createDictType: (dictTypeData: DictTypeAddReq): Promise<ApiResponse<any>> => { | ||
| 135 | + return api.post('/api/system/dict/type', dictTypeData) | ||
| 136 | + }, | ||
| 137 | + | ||
| 138 | + // 修改字典类型 | ||
| 139 | + updateDictType: (dictTypeData: DictTypeUpdateReq): Promise<ApiResponse<any>> => { | ||
| 140 | + return api.put('/api/system/dict/type', dictTypeData) | ||
| 141 | + }, | ||
| 142 | + | ||
| 143 | + // 删除字典类型 | ||
| 144 | + deleteDictTypes: (dictTypeIds: number[]): Promise<ApiResponse<any>> => { | ||
| 145 | + return api.delete(`/api/system/dict/type/${dictTypeIds.join(',')}`) | ||
| 146 | + }, | ||
| 147 | + | ||
| 148 | + // 获取字典类型选择框列表 | ||
| 149 | + getDictTypeOptions: (): Promise<ApiResponse<DictType[]>> => { | ||
| 150 | + return api.get('/api/system/dict/type/optionselect') | ||
| 151 | + }, | ||
| 152 | + | ||
| 153 | + // 刷新字典缓存 | ||
| 154 | + refreshDictCache: (): Promise<ApiResponse<any>> => { | ||
| 155 | + return api.delete('/api/system/dict/type/refreshCache') | ||
| 156 | + }, | ||
| 157 | + | ||
| 158 | + // 字典项管理 | ||
| 159 | + // 获取字典项列表 | ||
| 160 | + getDictItems: (params: DictItemSearchParams): Promise<ApiResponse<PageResponse<DictItem>>> => { | ||
| 161 | + return api.get('/api/system/dict/item/list', { params }) | ||
| 162 | + }, | ||
| 163 | + | ||
| 164 | + // 根据字典类型获取字典项列表 | ||
| 165 | + getDictItemsByType: (dictType: string): Promise<ApiResponse<DictItem[]>> => { | ||
| 166 | + return api.get(`/api/system/dict/item/type/${dictType}`) | ||
| 167 | + }, | ||
| 168 | + | ||
| 169 | + // 获取字典项详情 | ||
| 170 | + getDictItemById: (dictItemId: number): Promise<ApiResponse<DictItem>> => { | ||
| 171 | + return api.get(`/api/system/dict/item/${dictItemId}`) | ||
| 172 | + }, | ||
| 173 | + | ||
| 174 | + // 新增字典项 | ||
| 175 | + createDictItem: (dictItemData: DictItemAddReq): Promise<ApiResponse<any>> => { | ||
| 176 | + return api.post('/api/system/dict/item', dictItemData) | ||
| 177 | + }, | ||
| 178 | + | ||
| 179 | + // 修改字典项 | ||
| 180 | + updateDictItem: (dictItemData: DictItemUpdateReq): Promise<ApiResponse<any>> => { | ||
| 181 | + return api.put('/api/system/dict/item', dictItemData) | ||
| 182 | + }, | ||
| 183 | + | ||
| 184 | + // 删除字典项 | ||
| 185 | + deleteDictItems: (dictItemIds: number[]): Promise<ApiResponse<any>> => { | ||
| 186 | + return api.delete(`/api/system/dict/item/${dictItemIds.join(',')}`) | ||
| 187 | + }, | ||
| 95 | } | 188 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
frontend/src/api/dictItem.ts
0 → 100644
| 1 | +import axios from 'axios' | ||
| 2 | + | ||
| 3 | +// 创建axios实例 | ||
| 4 | +const api = axios.create({ | ||
| 5 | + baseURL: 'http://localhost:8083/api/system/dict/item', | ||
| 6 | + timeout: 10000, | ||
| 7 | + headers: { | ||
| 8 | + 'Content-Type': 'application/json' | ||
| 9 | + } | ||
| 10 | +}) | ||
| 11 | + | ||
| 12 | +// 请求拦截器 | ||
| 13 | +api.interceptors.request.use( | ||
| 14 | + (config) => { | ||
| 15 | + const token = localStorage.getItem('token') | ||
| 16 | + if (token) { | ||
| 17 | + config.headers.Authorization = `Bearer ${token}` | ||
| 18 | + } | ||
| 19 | + return config | ||
| 20 | + }, | ||
| 21 | + (error) => { | ||
| 22 | + return Promise.reject(error) | ||
| 23 | + } | ||
| 24 | +) | ||
| 25 | + | ||
| 26 | +// 响应拦截器 | ||
| 27 | +api.interceptors.response.use( | ||
| 28 | + (response) => { | ||
| 29 | + return response.data | ||
| 30 | + }, | ||
| 31 | + (error) => { | ||
| 32 | + console.error('API请求错误:', error) | ||
| 33 | + if (error.response?.status === 401) { | ||
| 34 | + localStorage.removeItem('token') | ||
| 35 | + localStorage.removeItem('userInfo') | ||
| 36 | + window.location.href = '/login' | ||
| 37 | + } | ||
| 38 | + return Promise.reject(error) | ||
| 39 | + } | ||
| 40 | +) | ||
| 41 | + | ||
| 42 | +// 字典项接口类型定义 | ||
| 43 | +export interface DictItem { | ||
| 44 | + dictItemId: number | ||
| 45 | + dictTypeId: number | ||
| 46 | + dictType: string | ||
| 47 | + dictLabel: string | ||
| 48 | + dictValue: string | ||
| 49 | + sort: number | ||
| 50 | + remark: string | ||
| 51 | + createBy: string | ||
| 52 | + createTime: string | ||
| 53 | + updateBy: string | ||
| 54 | + updateTime: string | ||
| 55 | +} | ||
| 56 | + | ||
| 57 | +export interface DictItemSearchParams { | ||
| 58 | + pageNum: number | ||
| 59 | + pageSize: number | ||
| 60 | + dictTypeId?: number | ||
| 61 | + dictLabel?: string | ||
| 62 | + dictValue?: string | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +export interface DictItemAddReq { | ||
| 66 | + dictTypeId: number | ||
| 67 | + dictLabel: string | ||
| 68 | + dictValue: string | ||
| 69 | + sort?: number | ||
| 70 | + remark?: string | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +export interface DictItemUpdateReq { | ||
| 74 | + dictItemId: number | ||
| 75 | + dictTypeId: number | ||
| 76 | + dictLabel: string | ||
| 77 | + dictValue: string | ||
| 78 | + sort?: number | ||
| 79 | + remark?: string | ||
| 80 | +} | ||
| 81 | + | ||
| 82 | +export interface ApiResponse<T> { | ||
| 83 | + code: number | ||
| 84 | + message: string | ||
| 85 | + data: T | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +export interface PageData<T> { | ||
| 89 | + records: T[] | ||
| 90 | + total: number | ||
| 91 | + current: number | ||
| 92 | + size: number | ||
| 93 | + pages: number | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +// API调用方法 | ||
| 97 | +export const dictItemApi = { | ||
| 98 | + // 获取字典项列表 | ||
| 99 | + getDictItemList: (params: DictItemSearchParams): Promise<ApiResponse<PageData<DictItem>>> => { | ||
| 100 | + return api.get('/list', { params }) | ||
| 101 | + }, | ||
| 102 | + | ||
| 103 | + // 根据字典类型获取字典项列表 | ||
| 104 | + getDictItemsByType: (dictType: string): Promise<ApiResponse<DictItem[]>> => { | ||
| 105 | + return api.get(`/type/${dictType}`) | ||
| 106 | + }, | ||
| 107 | + | ||
| 108 | + // 获取字典项详情 | ||
| 109 | + getDictItemById: (dictItemId: number): Promise<ApiResponse<DictItem>> => { | ||
| 110 | + return api.get(`/${dictItemId}`) | ||
| 111 | + }, | ||
| 112 | + | ||
| 113 | + // 新增字典项 | ||
| 114 | + createDictItem: (dictItemData: DictItemAddReq): Promise<ApiResponse<any>> => { | ||
| 115 | + return api.post('/', dictItemData) | ||
| 116 | + }, | ||
| 117 | + | ||
| 118 | + // 修改字典项 | ||
| 119 | + updateDictItem: (dictItemData: DictItemUpdateReq): Promise<ApiResponse<any>> => { | ||
| 120 | + return api.put('/', dictItemData) | ||
| 121 | + }, | ||
| 122 | + | ||
| 123 | + // 删除字典项 | ||
| 124 | + deleteDictItem: (dictItemIds: number[]): Promise<ApiResponse<any>> => { | ||
| 125 | + const ids = dictItemIds.join(',') | ||
| 126 | + return api.delete(`/${ids}`) | ||
| 127 | + } | ||
| 128 | +} | ||
| 129 | + | ||
| 130 | +export default dictItemApi |
| 1 | -import { request } from '@/utils/request' | 1 | +import axios from 'axios' |
| 2 | -import type { Log, PageRequest, PageResponse } from '@/types' | ||
| 3 | 2 | ||
| 4 | -// 获取日志分页列表 | 3 | +// 创建axios实例 |
| 5 | -export function getLogPageApi(params: PageRequest & { | 4 | +const api = axios.create({ |
| 6 | - title?: string | 5 | + baseURL: 'http://localhost:8083/api/system/log', |
| 6 | + timeout: 10000, | ||
| 7 | + headers: { | ||
| 8 | + 'Content-Type': 'application/json' | ||
| 9 | + } | ||
| 10 | +}) | ||
| 11 | + | ||
| 12 | +// 请求拦截器 | ||
| 13 | +api.interceptors.request.use( | ||
| 14 | + (config) => { | ||
| 15 | + const token = localStorage.getItem('token') | ||
| 16 | + if (token) { | ||
| 17 | + config.headers.Authorization = `Bearer ${token}` | ||
| 18 | + } | ||
| 19 | + return config | ||
| 20 | + }, | ||
| 21 | + (error) => { | ||
| 22 | + return Promise.reject(error) | ||
| 23 | + } | ||
| 24 | +) | ||
| 25 | + | ||
| 26 | +// 响应拦截器 | ||
| 27 | +api.interceptors.response.use( | ||
| 28 | + (response) => { | ||
| 29 | + return response.data | ||
| 30 | + }, | ||
| 31 | + (error) => { | ||
| 32 | + console.error('API请求错误:', error) | ||
| 33 | + if (error.response?.status === 401) { | ||
| 34 | + localStorage.removeItem('token') | ||
| 35 | + localStorage.removeItem('userInfo') | ||
| 36 | + window.location.href = '/login' | ||
| 37 | + } | ||
| 38 | + return Promise.reject(error) | ||
| 39 | + } | ||
| 40 | +) | ||
| 41 | + | ||
| 42 | +// 日志接口类型定义 | ||
| 43 | +export interface Log { | ||
| 44 | + logId: number | ||
| 45 | + logType: string | ||
| 46 | + logTypeText: string | ||
| 47 | + userId: number | ||
| 48 | + username: string | ||
| 49 | + module: string | ||
| 50 | + operation: string | ||
| 51 | + ip: string | ||
| 52 | + logTime: string | ||
| 53 | + status: number | ||
| 54 | + statusText: string | ||
| 55 | + errorMsg: string | ||
| 56 | + requestParam: string | ||
| 57 | + createBy: string | ||
| 58 | + createTime: string | ||
| 59 | + updateBy: string | ||
| 60 | + updateTime: string | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +export interface LogDetail { | ||
| 64 | + logId: number | ||
| 65 | + logType: string | ||
| 66 | + logTypeText: string | ||
| 67 | + userId: number | ||
| 68 | + username: string | ||
| 69 | + module: string | ||
| 70 | + operation: string | ||
| 71 | + ip: string | ||
| 72 | + logTime: string | ||
| 73 | + status: number | ||
| 74 | + statusText: string | ||
| 75 | + errorMsg: string | ||
| 76 | + requestParam: string | ||
| 77 | + createBy: string | ||
| 78 | + createTime: string | ||
| 79 | + updateBy: string | ||
| 80 | + updateTime: string | ||
| 81 | +} | ||
| 82 | + | ||
| 83 | +export interface LogSearchParams { | ||
| 84 | + pageNum: number | ||
| 85 | + pageSize: number | ||
| 7 | logType?: string | 86 | logType?: string |
| 87 | + userId?: number | ||
| 88 | + username?: string | ||
| 89 | + module?: string | ||
| 90 | + operation?: string | ||
| 91 | + ip?: string | ||
| 8 | status?: number | 92 | status?: number |
| 9 | startTime?: string | 93 | startTime?: string |
| 10 | endTime?: string | 94 | endTime?: string |
| 11 | -}): Promise<PageResponse<Log>> { | ||
| 12 | - return request.get('/api/sys/log/page', params) | ||
| 13 | } | 95 | } |
| 14 | 96 | ||
| 15 | -// 获取日志详情 | 97 | +export interface ApiResponse<T> { |
| 16 | -export function getLogDetailApi(logId: number): Promise<Log> { | 98 | + code: number |
| 17 | - return request.get(`/api/sys/log/${logId}`) | 99 | + message: string |
| 100 | + data: T | ||
| 18 | } | 101 | } |
| 19 | 102 | ||
| 20 | -// 删除日志 | 103 | +export interface PageData<T> { |
| 21 | -export function deleteLogApi(logId: number): Promise<void> { | 104 | + records: T[] |
| 22 | - return request.delete(`/api/sys/log/${logId}`) | 105 | + total: number |
| 106 | + current: number | ||
| 107 | + size: number | ||
| 108 | + pages: number | ||
| 23 | } | 109 | } |
| 24 | 110 | ||
| 25 | -// 批量删除日志 | 111 | +// API调用方法 |
| 26 | -export function deleteBatchLogApi(logIds: number[]): Promise<void> { | 112 | +export const logApi = { |
| 27 | - return request.delete('/api/sys/log/batch', { logIds }) | 113 | + // 获取日志列表 |
| 28 | -} | 114 | + getLogList: (params: LogSearchParams): Promise<ApiResponse<PageData<Log>>> => { |
| 115 | + return api.get('/page', { params }) | ||
| 116 | + }, | ||
| 29 | 117 | ||
| 30 | -// 清理过期日志 | 118 | + // 获取日志详情 |
| 31 | -export function cleanExpiredLogsApi(days: number): Promise<void> { | 119 | + getLogDetail: (logId: number): Promise<ApiResponse<LogDetail>> => { |
| 32 | - return request.post('/api/sys/log/clean', { days }) | 120 | + return api.get(`/${logId}`) |
| 33 | -} | 121 | + }, |
| 34 | 122 | ||
| 35 | -// 获取日志统计 | 123 | + // 获取用户登录日志 |
| 36 | -export function getLogCountApi(params: { | 124 | + getLoginLogsByUserId: (userId: number, startTime?: string, endTime?: string): Promise<ApiResponse<Log[]>> => { |
| 37 | - logType?: string | 125 | + return api.get(`/login/${userId}`, { |
| 38 | - startTime?: string | 126 | + params: { |
| 39 | - endTime?: string | 127 | + startTime, |
| 40 | -}): Promise<{ total: number; success: number; error: number }> { | 128 | + endTime |
| 41 | - return request.get('/api/sys/log/count', params) | 129 | + } |
| 42 | -} | 130 | + }) |
| 131 | + }, | ||
| 43 | 132 | ||
| 44 | -// 获取用户登录日志 | 133 | + // 清理过期日志 |
| 45 | -export function getUserLoginLogsApi(params: PageRequest & { | 134 | + cleanExpiredLogs: (expireDays: number): Promise<ApiResponse<string>> => { |
| 46 | - userId: number | 135 | + return api.delete('/clean', { |
| 47 | - startTime?: string | 136 | + params: { |
| 48 | - endTime?: string | 137 | + expireDays |
| 49 | -}): Promise<PageResponse<Log>> { | 138 | + } |
| 50 | - return request.get('/api/sys/log/login', params) | 139 | + }) |
| 140 | + }, | ||
| 141 | + | ||
| 142 | + // 统计日志数量 | ||
| 143 | + countLogs: (logType?: string, startTime?: string, endTime?: string): Promise<ApiResponse<number>> => { | ||
| 144 | + return api.get('/count', { | ||
| 145 | + params: { | ||
| 146 | + logType, | ||
| 147 | + startTime, | ||
| 148 | + endTime | ||
| 149 | + } | ||
| 150 | + }) | ||
| 151 | + }, | ||
| 152 | + | ||
| 153 | + // 删除日志 | ||
| 154 | + deleteLog: (logId: number): Promise<ApiResponse<string>> => { | ||
| 155 | + return api.delete(`/${logId}`) | ||
| 156 | + }, | ||
| 157 | + | ||
| 158 | + // 批量删除日志 | ||
| 159 | + deleteLogs: (logIds: number[]): Promise<ApiResponse<string>> => { | ||
| 160 | + return api.delete('/batch', { data: logIds }) | ||
| 161 | + } | ||
| 51 | } | 162 | } |
| 163 | + | ||
| 164 | +export default logApi | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | -import { request } from '@/utils/request' | 1 | +import axios from 'axios' |
| 2 | -import type { Menu, PageRequest, PageResponse } from '@/types' | ||
| 3 | 2 | ||
| 4 | -// 获取菜单列表 | 3 | +// API基础配置 |
| 5 | -export function getMenuListApi(): Promise<Menu[]> { | 4 | +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8083' |
| 6 | - return request.get('/api/sys/menu/list') | ||
| 7 | -} | ||
| 8 | 5 | ||
| 9 | -// 获取菜单树 | 6 | +// 创建axios实例 |
| 10 | -export function getMenuTreeApi(userId?: number): Promise<Menu[]> { | 7 | +const api = axios.create({ |
| 11 | - return request.get('/api/sys/menu/tree', { userId }) | 8 | + baseURL: API_BASE_URL, |
| 12 | -} | 9 | + timeout: 10000, |
| 10 | + headers: { | ||
| 11 | + 'Content-Type': 'application/json' | ||
| 12 | + } | ||
| 13 | +}) | ||
| 13 | 14 | ||
| 14 | -// 获取菜单分页列表 | 15 | +console.log('菜单API实例创建,baseURL:', API_BASE_URL) |
| 15 | -export function getMenuPageApi(params: PageRequest & { | 16 | + |
| 16 | - menuName?: string | 17 | +// 请求拦截器 - 添加token |
| 18 | +api.interceptors.request.use( | ||
| 19 | + (config) => { | ||
| 20 | + console.log('菜单API请求:', { | ||
| 21 | + url: config.url, | ||
| 22 | + baseURL: config.baseURL, | ||
| 23 | + method: config.method, | ||
| 24 | + params: config.params, | ||
| 25 | + fullURL: `${config.baseURL}${config.url}` | ||
| 26 | + }) | ||
| 27 | + | ||
| 28 | + const token = localStorage.getItem('token') | ||
| 29 | + if (token) { | ||
| 30 | + config.headers.Authorization = `Bearer ${token}` | ||
| 31 | + } | ||
| 32 | + return config | ||
| 33 | + }, | ||
| 34 | + (error) => { | ||
| 35 | + return Promise.reject(error) | ||
| 36 | + } | ||
| 37 | +) | ||
| 38 | + | ||
| 39 | +// 响应拦截器 | ||
| 40 | +api.interceptors.response.use( | ||
| 41 | + (response) => { | ||
| 42 | + return response.data | ||
| 43 | + }, | ||
| 44 | + (error) => { | ||
| 45 | + console.error('API请求错误:', error) | ||
| 46 | + return Promise.reject(error) | ||
| 47 | + } | ||
| 48 | +) | ||
| 49 | + | ||
| 50 | +// 菜单接口类型定义 | ||
| 51 | +export interface Menu { | ||
| 52 | + menuId: number | ||
| 53 | + parentId?: number | ||
| 54 | + menuName: string | ||
| 55 | + menuType: string | ||
| 56 | + menuTypeText: string | ||
| 17 | path?: string | 57 | path?: string |
| 18 | - menuType?: string | 58 | + icon?: string |
| 59 | + sort: number | ||
| 60 | + status: number | ||
| 61 | + statusText: string | ||
| 19 | perms?: string | 62 | perms?: string |
| 20 | -}): Promise<PageResponse<Menu>> { | 63 | + component?: string |
| 21 | - return request.get('/api/sys/menu/page', params) | 64 | + isFrame?: number |
| 65 | + remark?: string | ||
| 66 | + level?: number | ||
| 67 | + createTime: string | ||
| 68 | + updateTime: string | ||
| 69 | + children?: Menu[] | ||
| 22 | } | 70 | } |
| 23 | 71 | ||
| 24 | -// 获取菜单详情 | 72 | +export interface MenuSearchParams { |
| 25 | -export function getMenuDetailApi(menuId: number): Promise<Menu> { | 73 | + menuName?: string |
| 26 | - return request.get(`/api/sys/menu/${menuId}`) | 74 | + menuType?: string |
| 75 | + status?: number | ||
| 76 | + pageNum?: number | ||
| 77 | + pageSize?: number | ||
| 27 | } | 78 | } |
| 28 | 79 | ||
| 29 | -// 新增菜单 | 80 | +export interface ApiResponse<T = any> { |
| 30 | -export function addMenuApi(data: Partial<Menu>): Promise<void> { | 81 | + code: number |
| 31 | - return request.post('/api/sys/menu', data) | 82 | + message: string |
| 83 | + data: T | ||
| 32 | } | 84 | } |
| 33 | 85 | ||
| 34 | -// 更新菜单 | 86 | +export interface PageResponse<T> { |
| 35 | -export function updateMenuApi(data: Partial<Menu>): Promise<void> { | 87 | + records: T[] |
| 36 | - return request.put('/api/sys/menu', data) | 88 | + total: number |
| 89 | + size: number | ||
| 90 | + current: number | ||
| 91 | + orders: any[] | ||
| 92 | + optimizeCountSql: boolean | ||
| 93 | + searchCount: boolean | ||
| 94 | + maxLimit: any | ||
| 95 | + countId: any | ||
| 96 | + pages: number | ||
| 37 | } | 97 | } |
| 38 | 98 | ||
| 39 | -// 删除菜单 | 99 | +// 菜单API接口 |
| 40 | -export function deleteMenuApi(menuId: number): Promise<void> { | 100 | +export const menuApi = { |
| 41 | - return request.delete(`/api/sys/menu/${menuId}`) | 101 | + // 获取菜单列表 |
| 42 | -} | 102 | + getMenuList: (params: MenuSearchParams): Promise<ApiResponse<PageResponse<Menu>>> => { |
| 103 | + return api.get('/api/system/menu/list', { params }) | ||
| 104 | + }, | ||
| 43 | 105 | ||
| 44 | -// 批量删除菜单 | 106 | + // 获取菜单树列表 |
| 45 | -export function deleteBatchMenuApi(menuIds: number[]): Promise<void> { | 107 | + getMenuTree: (userId?: number): Promise<ApiResponse<Menu[]>> => { |
| 46 | - return request.delete('/api/sys/menu/batch', { menuIds }) | 108 | + return api.get('/api/system/menu/tree', { params: { userId } }) |
| 47 | -} | 109 | + }, |
| 48 | 110 | ||
| 49 | -// 获取角色菜单树 | 111 | + // 获取菜单详情 |
| 50 | -export function getRoleMenuTreeApi(roleId: number): Promise<Menu[]> { | 112 | + getMenuById: (menuId: number): Promise<ApiResponse<Menu>> => { |
| 51 | - return request.get(`/api/sys/menu/roleMenuTree/${roleId}`) | 113 | + return api.get(`/api/system/menu/${menuId}`) |
| 52 | -} | 114 | + }, |
| 115 | + | ||
| 116 | + // 新增菜单 | ||
| 117 | + createMenu: (menuData: any): Promise<ApiResponse<any>> => { | ||
| 118 | + return api.post('/api/system/menu', menuData) | ||
| 119 | + }, | ||
| 53 | 120 | ||
| 54 | -// 获取菜单选择树 | 121 | + // 修改菜单 |
| 55 | -export function getMenuSelectTreeApi(): Promise<Menu[]> { | 122 | + updateMenu: (menuData: any): Promise<ApiResponse<any>> => { |
| 56 | - return request.get('/api/sys/menu/treeselect') | 123 | + return api.put('/api/system/menu', menuData) |
| 124 | + }, | ||
| 125 | + | ||
| 126 | + // 删除菜单 | ||
| 127 | + deleteMenu: (menuIds: number[]): Promise<ApiResponse<any>> => { | ||
| 128 | + return api.delete(`/api/system/menu/${menuIds.join(',')}`) | ||
| 129 | + }, | ||
| 130 | + | ||
| 131 | + // 获取菜单下拉树选择 | ||
| 132 | + getMenuTreeSelect: (): Promise<ApiResponse<Menu[]>> => { | ||
| 133 | + return api.get('/api/system/menu/treeselect') | ||
| 134 | + }, | ||
| 135 | + | ||
| 136 | + // 根据角色ID获取菜单下拉树选择 | ||
| 137 | + getMenuTreeSelectByRole: (roleId: number): Promise<ApiResponse<Menu[]>> => { | ||
| 138 | + return api.get(`/api/system/menu/roleMenuTreeselect/${roleId}`) | ||
| 139 | + } | ||
| 57 | } | 140 | } |
| 141 | + | ||
| 142 | +export default menuApi | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | -import { request } from '@/utils/request' | 1 | +import axios from 'axios' |
| 2 | -import type { Role, PageRequest, PageResponse } from '@/types' | ||
| 3 | 2 | ||
| 4 | -// 获取角色列表 | 3 | +// API基础配置 |
| 5 | -export function getRoleListApi(): Promise<Role[]> { | 4 | +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8083' |
| 6 | - return request.get('/api/sys/role/list') | 5 | + |
| 6 | +// 创建axios实例 | ||
| 7 | +const api = axios.create({ | ||
| 8 | + baseURL: API_BASE_URL, | ||
| 9 | + timeout: 10000, | ||
| 10 | + headers: { | ||
| 11 | + 'Content-Type': 'application/json' | ||
| 12 | + } | ||
| 13 | +}) | ||
| 14 | + | ||
| 15 | +console.log('角色API实例创建,baseURL:', API_BASE_URL) | ||
| 16 | + | ||
| 17 | +// 请求拦截器 - 添加token | ||
| 18 | +api.interceptors.request.use( | ||
| 19 | + (config) => { | ||
| 20 | + console.log('角色API请求:', { | ||
| 21 | + url: config.url, | ||
| 22 | + baseURL: config.baseURL, | ||
| 23 | + method: config.method, | ||
| 24 | + params: config.params, | ||
| 25 | + fullURL: `${config.baseURL}${config.url}` | ||
| 26 | + }) | ||
| 27 | + | ||
| 28 | + const token = localStorage.getItem('token') | ||
| 29 | + if (token) { | ||
| 30 | + config.headers.Authorization = `Bearer ${token}` | ||
| 31 | + } | ||
| 32 | + return config | ||
| 33 | + }, | ||
| 34 | + (error) => { | ||
| 35 | + return Promise.reject(error) | ||
| 36 | + } | ||
| 37 | +) | ||
| 38 | + | ||
| 39 | +// 响应拦截器 | ||
| 40 | +api.interceptors.response.use( | ||
| 41 | + (response) => { | ||
| 42 | + return response.data | ||
| 43 | + }, | ||
| 44 | + (error) => { | ||
| 45 | + console.error('API请求错误:', error) | ||
| 46 | + return Promise.reject(error) | ||
| 47 | + } | ||
| 48 | +) | ||
| 49 | + | ||
| 50 | +// 菜单接口类型定义 | ||
| 51 | +export interface Menu { | ||
| 52 | + menuId: number | ||
| 53 | + parentId?: number | ||
| 54 | + menuName: string | ||
| 55 | + menuType: string | ||
| 56 | + menuTypeText: string | ||
| 57 | + path?: string | ||
| 58 | + icon?: string | ||
| 59 | + sort: number | ||
| 60 | + status: number | ||
| 61 | + statusText: string | ||
| 62 | + perms?: string | ||
| 63 | + createTime: string | ||
| 64 | + updateTime: string | ||
| 65 | + children?: Menu[] | ||
| 66 | +} | ||
| 67 | + | ||
| 68 | +// 角色接口类型定义 | ||
| 69 | +export interface Role { | ||
| 70 | + roleId: number | ||
| 71 | + roleCode: string | ||
| 72 | + roleName: string | ||
| 73 | + status: number | ||
| 74 | + statusText: string | ||
| 75 | + remark: string | ||
| 76 | + createTime: string | ||
| 77 | + updateTime: string | ||
| 78 | + menuIds?: number[] | ||
| 79 | + menus?: Menu[] | ||
| 7 | } | 80 | } |
| 8 | 81 | ||
| 9 | -// 获取角色分页列表 | 82 | +export interface RoleSearchParams { |
| 10 | -export function getRolePageApi(params: PageRequest & { | 83 | + roleCode?: string |
| 11 | roleName?: string | 84 | roleName?: string |
| 12 | - roleKey?: string | ||
| 13 | status?: number | 85 | status?: number |
| 14 | -}): Promise<PageResponse<Role>> { | 86 | + pageNum?: number |
| 15 | - return request.get('/api/sys/role/page', params) | 87 | + pageSize?: number |
| 16 | } | 88 | } |
| 17 | 89 | ||
| 18 | -// 获取角色详情 | 90 | +export interface ApiResponse<T = any> { |
| 19 | -export function getRoleDetailApi(roleId: number): Promise<Role> { | 91 | + code: number |
| 20 | - return request.get(`/api/sys/role/${roleId}`) | 92 | + message: string |
| 93 | + data: T | ||
| 21 | } | 94 | } |
| 22 | 95 | ||
| 23 | -// 新增角色 | 96 | +export interface PageResponse<T> { |
| 24 | -export function addRoleApi(data: Partial<Role>): Promise<void> { | 97 | + records: T[] |
| 25 | - return request.post('/api/sys/role', data) | 98 | + total: number |
| 99 | + size: number | ||
| 100 | + current: number | ||
| 101 | + orders: any[] | ||
| 102 | + optimizeCountSql: boolean | ||
| 103 | + searchCount: boolean | ||
| 104 | + maxLimit: any | ||
| 105 | + countId: any | ||
| 106 | + pages: number | ||
| 26 | } | 107 | } |
| 27 | 108 | ||
| 28 | -// 更新角色 | 109 | +// 角色API接口 |
| 29 | -export function updateRoleApi(data: Partial<Role>): Promise<void> { | 110 | +export const roleApi = { |
| 30 | - return request.put('/api/sys/role', data) | 111 | + // 获取角色列表 |
| 31 | -} | 112 | + getRoleList: (params?: RoleSearchParams): Promise<ApiResponse<PageResponse<Role>>> => { |
| 113 | + return api.get('/api/system/role/list', { params }) | ||
| 114 | + }, | ||
| 32 | 115 | ||
| 33 | -// 删除角色 | 116 | + // 获取所有角色(不分页,用于下拉选择) |
| 34 | -export function deleteRoleApi(roleId: number): Promise<void> { | 117 | + getAllRoles: (): Promise<ApiResponse<Role[]>> => { |
| 35 | - return request.delete(`/api/sys/role/${roleId}`) | 118 | + return api.get('/api/system/role/list', { |
| 36 | -} | 119 | + params: { status: 1, pageSize: 9999 } |
| 120 | + }) | ||
| 121 | + }, | ||
| 37 | 122 | ||
| 38 | -// 批量删除角色 | 123 | + // 根据ID获取角色详情 |
| 39 | -export function deleteBatchRoleApi(roleIds: number[]): Promise<void> { | 124 | + getRoleById: (roleId: number): Promise<ApiResponse<Role>> => { |
| 40 | - return request.delete('/api/sys/role/batch', { roleIds }) | 125 | + return api.get(`/api/system/role/${roleId}`) |
| 41 | -} | 126 | + }, |
| 42 | 127 | ||
| 43 | -// 修改角色状态 | 128 | + // 创建角色 |
| 44 | -export function changeRoleStatusApi(data: { | 129 | + createRole: (roleData: any): Promise<ApiResponse<any>> => { |
| 45 | - roleId: number | 130 | + return api.post('/api/system/role/add', roleData) |
| 46 | - status: number | 131 | + }, |
| 47 | -}): Promise<void> { | ||
| 48 | - return request.post('/api/sys/role/changeStatus', data) | ||
| 49 | -} | ||
| 50 | 132 | ||
| 51 | -// 分配权限 | 133 | + // 更新角色 |
| 52 | -export function assignPermissionsApi(data: { | 134 | + updateRole: (roleData: any): Promise<ApiResponse<any>> => { |
| 53 | - roleId: number | 135 | + return api.post('/api/system/role/edit', roleData) |
| 54 | - menuIds: number[] | 136 | + }, |
| 55 | -}): Promise<void> { | 137 | + |
| 56 | - return request.post('/api/sys/role/assignPermissions', data) | 138 | + // 删除角色(单个或批量) |
| 139 | + deleteRole: (roleIds: number[]): Promise<ApiResponse<any>> => { | ||
| 140 | + return api.delete(`/api/system/role/${roleIds.join(',')}`) | ||
| 141 | + }, | ||
| 142 | + | ||
| 143 | + // 更新角色状态 | ||
| 144 | + updateRoleStatus: (roleId: number, status: number): Promise<ApiResponse<any>> => { | ||
| 145 | + return api.post('/api/system/role/changeStatus', { | ||
| 146 | + roleId: roleId, | ||
| 147 | + status: status | ||
| 148 | + }) | ||
| 149 | + }, | ||
| 150 | + | ||
| 151 | + // 获取菜单树列表 | ||
| 152 | + getMenuTree: (): Promise<ApiResponse<Menu[]>> => { | ||
| 153 | + return api.get('/api/system/menu/tree') | ||
| 154 | + }, | ||
| 155 | + | ||
| 156 | + // 测试角色列表接口参数格式 | ||
| 157 | + testRoleListParams: (): RoleSearchParams => { | ||
| 158 | + return { | ||
| 159 | + roleCode: "", | ||
| 160 | + roleName: "", | ||
| 161 | + status: 1, // 默认获取有效角色 | ||
| 162 | + pageNum: 1, | ||
| 163 | + pageSize: 9999 // 获取全部有效角色 | ||
| 164 | + } | ||
| 165 | + } | ||
| 57 | } | 166 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | -import { request } from '@/utils/request' | 1 | +import axios from 'axios' |
| 2 | -import type { User, PageRequest, PageResponse } from '@/types' | ||
| 3 | 2 | ||
| 4 | -// 获取用户列表 | 3 | +// API基础配置 |
| 5 | -export function getUserListApi(): Promise<User[]> { | 4 | +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8083' |
| 6 | - return request.get('/api/sys/user/list') | 5 | + |
| 6 | +// 创建axios实例 | ||
| 7 | +const api = axios.create({ | ||
| 8 | + baseURL: API_BASE_URL, | ||
| 9 | + timeout: 10000, | ||
| 10 | + headers: { | ||
| 11 | + 'Content-Type': 'application/json' | ||
| 12 | + } | ||
| 13 | +}) | ||
| 14 | + | ||
| 15 | +// 请求拦截器 - 添加token | ||
| 16 | +api.interceptors.request.use( | ||
| 17 | + (config) => { | ||
| 18 | + const token = localStorage.getItem('token') | ||
| 19 | + if (token) { | ||
| 20 | + config.headers.Authorization = `Bearer ${token}` | ||
| 21 | + } | ||
| 22 | + return config | ||
| 23 | + }, | ||
| 24 | + (error) => { | ||
| 25 | + return Promise.reject(error) | ||
| 26 | + } | ||
| 27 | +) | ||
| 28 | + | ||
| 29 | +// 响应拦截器 - 处理错误 | ||
| 30 | +api.interceptors.response.use( | ||
| 31 | + (response) => { | ||
| 32 | + return response | ||
| 33 | + }, | ||
| 34 | + (error) => { | ||
| 35 | + if (error.response?.status === 401) { | ||
| 36 | + // token过期,跳转到登录页 | ||
| 37 | + localStorage.removeItem('token') | ||
| 38 | + localStorage.removeItem('userInfo') | ||
| 39 | + window.location.href = '/' | ||
| 40 | + } | ||
| 41 | + return Promise.reject(error) | ||
| 42 | + } | ||
| 43 | +) | ||
| 44 | + | ||
| 45 | +// 用户数据类型 | ||
| 46 | +export interface User { | ||
| 47 | + userId: number | ||
| 48 | + username: string | ||
| 49 | + realName: string | ||
| 50 | + phone: string | ||
| 51 | + email: string | ||
| 52 | + status: number | ||
| 53 | + statusText: string | ||
| 54 | + remark: string | ||
| 55 | + createBy: string | ||
| 56 | + createTime: string | ||
| 57 | + updateBy: string | ||
| 58 | + updateTime: string | ||
| 59 | + lastLoginTime: string | null | ||
| 60 | + lastLoginIp: string | null | ||
| 61 | + roles: Role[] | ||
| 7 | } | 62 | } |
| 8 | 63 | ||
| 9 | -// 获取用户分页列表 | 64 | +// 角色类型 |
| 10 | -export function getUserPageApi(params: PageRequest & { | 65 | +export interface Role { |
| 66 | + roleId: number | ||
| 67 | + roleCode: string | ||
| 68 | + roleName: string | ||
| 69 | + status: number | ||
| 70 | + statusText: string | ||
| 71 | + remark: string | ||
| 72 | + createBy: string | ||
| 73 | + createTime: string | ||
| 74 | + updateBy: string | ||
| 75 | + updateTime: string | ||
| 76 | + menus: any | ||
| 77 | +} | ||
| 78 | + | ||
| 79 | +// 搜索参数类型 | ||
| 80 | +export interface UserSearchParams { | ||
| 11 | username?: string | 81 | username?: string |
| 12 | - realName?: string | ||
| 13 | phone?: string | 82 | phone?: string |
| 14 | - email?: string | ||
| 15 | status?: number | 83 | status?: number |
| 16 | -}): Promise<PageResponse<User>> { | 84 | + startTime?: string |
| 17 | - return request.get('/api/sys/user/page', params) | 85 | + endTime?: string |
| 86 | + page?: number | ||
| 87 | + pageSize?: number | ||
| 18 | } | 88 | } |
| 19 | 89 | ||
| 20 | -// 获取用户详情 | 90 | +// 用户表单类型 |
| 21 | -export function getUserDetailApi(userId: number): Promise<User> { | 91 | +export interface UserForm { |
| 22 | - return request.get(`/api/sys/user/${userId}`) | 92 | + userId?: number |
| 93 | + username: string | ||
| 94 | + realName: string | ||
| 95 | + phone: string | ||
| 96 | + email: string | ||
| 97 | + status: number | ||
| 98 | + remark: string | ||
| 99 | + roleIds: number[] | ||
| 100 | + password?: string | ||
| 23 | } | 101 | } |
| 24 | 102 | ||
| 25 | -// 新增用户 | 103 | +// API响应类型 |
| 26 | -export function addUserApi(data: Partial<User>): Promise<void> { | 104 | +export interface ApiResponse<T> { |
| 27 | - return request.post('/api/sys/user', data) | 105 | + code: number |
| 106 | + message: string | ||
| 107 | + data: T | ||
| 28 | } | 108 | } |
| 29 | 109 | ||
| 30 | -// 更新用户 | 110 | +// 分页响应类型 |
| 31 | -export function updateUserApi(data: Partial<User>): Promise<void> { | 111 | +export interface PageResponse<T> { |
| 32 | - return request.put('/api/sys/user', data) | 112 | + records: T[] |
| 113 | + total: number | ||
| 114 | + size: number | ||
| 115 | + current: number | ||
| 116 | + orders: any[] | ||
| 117 | + optimizeCountSql: boolean | ||
| 118 | + searchCount: boolean | ||
| 119 | + maxLimit: any | ||
| 120 | + countId: any | ||
| 121 | + pages: number | ||
| 33 | } | 122 | } |
| 34 | 123 | ||
| 35 | -// 删除用户 | 124 | +// 用户API接口 |
| 36 | -export function deleteUserApi(userId: number): Promise<void> { | 125 | +export const userApi = { |
| 37 | - return request.delete(`/api/sys/user/${userId}`) | 126 | + // 获取用户列表 |
| 38 | -} | 127 | + getUserList: async (params: UserSearchParams = {}) => { |
| 128 | + try { | ||
| 129 | + const response = await api.get<ApiResponse<PageResponse<User>>>('/api/system/user/list', { params }) | ||
| 130 | + return response.data | ||
| 131 | + } catch (error) { | ||
| 132 | + console.error('获取用户列表失败:', error) | ||
| 133 | + throw error | ||
| 134 | + } | ||
| 135 | + }, | ||
| 39 | 136 | ||
| 40 | -// 批量删除用户 | 137 | + // 获取用户详情 |
| 41 | -export function deleteBatchUserApi(userIds: number[]): Promise<void> { | 138 | + getUserById: async (id: number) => { |
| 42 | - return request.delete('/api/sys/user/batch', { userIds }) | 139 | + try { |
| 43 | -} | 140 | + const response = await api.get<ApiResponse<User>>(`/api/system/user/${id}`) |
| 141 | + return response.data | ||
| 142 | + } catch (error) { | ||
| 143 | + console.error('获取用户详情失败:', error) | ||
| 144 | + throw error | ||
| 145 | + } | ||
| 146 | + }, | ||
| 44 | 147 | ||
| 45 | -// 重置密码 | 148 | + // 创建用户 |
| 46 | -export function resetPasswordApi(data: { | 149 | + createUser: async (userData: UserForm) => { |
| 47 | - userId: number | 150 | + try { |
| 48 | - newPassword: string | 151 | + const response = await api.post<ApiResponse<User>>('/api/system/user/add', userData) |
| 49 | -}): Promise<void> { | 152 | + return response.data |
| 50 | - return request.post('/api/sys/user/resetPassword', data) | 153 | + } catch (error) { |
| 51 | -} | 154 | + console.error('创建用户失败:', error) |
| 155 | + throw error | ||
| 156 | + } | ||
| 157 | + }, | ||
| 52 | 158 | ||
| 53 | -// 修改用户状态 | 159 | + // 更新用户 |
| 54 | -export function changeUserStatusApi(data: { | 160 | + updateUser: async (id: number, userData: UserForm) => { |
| 55 | - userId: number | 161 | + try { |
| 56 | - status: number | 162 | + const response = await api.post<ApiResponse<User>>('/api/system/user/edit', userData) |
| 57 | -}): Promise<void> { | 163 | + return response.data |
| 58 | - return request.post('/api/sys/user/changeStatus', data) | 164 | + } catch (error) { |
| 59 | -} | 165 | + console.error('更新用户失败:', error) |
| 166 | + throw error | ||
| 167 | + } | ||
| 168 | + }, | ||
| 169 | + | ||
| 170 | + // 删除用户(单个或批量) | ||
| 171 | + deleteUser: async (id: number) => { | ||
| 172 | + try { | ||
| 173 | + const response = await api.delete<ApiResponse<void>>(`/api/system/user/${id}`) | ||
| 174 | + return response.data | ||
| 175 | + } catch (error) { | ||
| 176 | + console.error('删除用户失败:', error) | ||
| 177 | + throw error | ||
| 178 | + } | ||
| 179 | + }, | ||
| 180 | + | ||
| 181 | + // 批量删除用户 | ||
| 182 | + batchDeleteUsers: async (ids: number[]) => { | ||
| 183 | + try { | ||
| 184 | + const response = await api.delete<ApiResponse<void>>(`/api/system/user/${ids.join(',')}`) | ||
| 185 | + return response.data | ||
| 186 | + } catch (error) { | ||
| 187 | + console.error('批量删除用户失败:', error) | ||
| 188 | + throw error | ||
| 189 | + } | ||
| 190 | + }, | ||
| 191 | + | ||
| 192 | + // 更新用户状态 | ||
| 193 | + updateUserStatus: async (id: number, status: number) => { | ||
| 194 | + try { | ||
| 195 | + const response = await api.post<ApiResponse<void>>('/api/system/user/changeStatus', { | ||
| 196 | + userId: id, | ||
| 197 | + status: status | ||
| 198 | + }) | ||
| 199 | + return response.data | ||
| 200 | + } catch (error) { | ||
| 201 | + console.error('更新用户状态失败:', error) | ||
| 202 | + throw error | ||
| 203 | + } | ||
| 204 | + }, | ||
| 60 | 205 | ||
| 61 | -// 分配角色 | ||
| 62 | -export function assignRolesApi(data: { | ||
| 63 | - userId: number | ||
| 64 | - roleIds: number[] | ||
| 65 | -}): Promise<void> { | ||
| 66 | - return request.post('/api/sys/user/assignRoles', data) | ||
| 67 | } | 206 | } |
| 207 | + | ||
| 208 | +export default userApi | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
frontend/src/layouts/MainLayout.vue
0 → 100644
This diff is collapsed. Click to expand it.
| 1 | import { createApp } from 'vue' | 1 | import { createApp } from 'vue' |
| 2 | +import { createPinia } from 'pinia' | ||
| 2 | import App from './App.vue' | 3 | import App from './App.vue' |
| 3 | 4 | ||
| 5 | +import router from './router' | ||
| 6 | + | ||
| 4 | // 创建应用实例 | 7 | // 创建应用实例 |
| 5 | const app = createApp(App) | 8 | const app = createApp(App) |
| 9 | +const pinia = createPinia() | ||
| 6 | 10 | ||
| 11 | +app.use(pinia) | ||
| 12 | +app.use(router) | ||
| 7 | // 挂载应用 | 13 | // 挂载应用 |
| 8 | app.mount('#app') | 14 | app.mount('#app') | ... | ... |
| 1 | import { createRouter, createWebHistory } from 'vue-router' | 1 | import { createRouter, createWebHistory } from 'vue-router' |
| 2 | import type { RouteRecordRaw } from 'vue-router' | 2 | import type { RouteRecordRaw } from 'vue-router' |
| 3 | +import Login from '@/views/login/index.vue' | ||
| 4 | +import MainLayout from '@/layouts/MainLayout.vue' | ||
| 5 | +import Dashboard from '@/views/dashboard/index.vue' | ||
| 6 | +import Users from '@/views/users/index.vue' | ||
| 3 | import Test from '@/views/Test.vue' | 7 | import Test from '@/views/Test.vue' |
| 4 | import NProgress from 'nprogress' | 8 | import NProgress from 'nprogress' |
| 5 | import 'nprogress/nprogress.css' | 9 | import 'nprogress/nprogress.css' |
| ... | @@ -11,6 +15,115 @@ NProgress.configure({ showSpinner: false }) | ... | @@ -11,6 +15,115 @@ NProgress.configure({ showSpinner: false }) |
| 11 | const staticRoutes: RouteRecordRaw[] = [ | 15 | const staticRoutes: RouteRecordRaw[] = [ |
| 12 | { | 16 | { |
| 13 | path: '/', | 17 | path: '/', |
| 18 | + name: 'Login', | ||
| 19 | + component: Login, | ||
| 20 | + meta: { | ||
| 21 | + title: '登录', | ||
| 22 | + requiresAuth: false | ||
| 23 | + } | ||
| 24 | + }, | ||
| 25 | + { | ||
| 26 | + path: '/main', | ||
| 27 | + component: MainLayout, | ||
| 28 | + children: [ | ||
| 29 | + { | ||
| 30 | + path: 'dashboard', | ||
| 31 | + name: 'Dashboard', | ||
| 32 | + component: Dashboard, | ||
| 33 | + meta: { | ||
| 34 | + title: '首页', | ||
| 35 | + requiresAuth: true | ||
| 36 | + } | ||
| 37 | + }, | ||
| 38 | + { | ||
| 39 | + path: 'users', | ||
| 40 | + name: 'Users', | ||
| 41 | + component: Users, | ||
| 42 | + meta: { | ||
| 43 | + title: '用户管理', | ||
| 44 | + requiresAuth: true | ||
| 45 | + } | ||
| 46 | + }, | ||
| 47 | + { | ||
| 48 | + path: 'sys/role', | ||
| 49 | + name: 'Role', | ||
| 50 | + component: () => import('@/views/sys/role/index.vue'), | ||
| 51 | + meta: { | ||
| 52 | + title: '角色管理', | ||
| 53 | + requiresAuth: true | ||
| 54 | + } | ||
| 55 | + }, | ||
| 56 | + { | ||
| 57 | + path: 'sys/menu', | ||
| 58 | + name: 'Menu', | ||
| 59 | + component: () => import('@/views/sys/menu/index.vue'), | ||
| 60 | + meta: { | ||
| 61 | + title: '菜单管理', | ||
| 62 | + requiresAuth: true | ||
| 63 | + } | ||
| 64 | + }, | ||
| 65 | + { | ||
| 66 | + path: 'sys/dict', | ||
| 67 | + name: 'Dict', | ||
| 68 | + component: () => import('@/views/sys/dict/index.vue'), | ||
| 69 | + meta: { | ||
| 70 | + title: '字典管理', | ||
| 71 | + requiresAuth: true | ||
| 72 | + } | ||
| 73 | + }, | ||
| 74 | + { | ||
| 75 | + path: 'sys/dict-item', | ||
| 76 | + name: 'DictItem', | ||
| 77 | + component: () => import('@/views/sys/dict-item/index.vue'), | ||
| 78 | + meta: { | ||
| 79 | + title: '字典项管理', | ||
| 80 | + requiresAuth: true | ||
| 81 | + } | ||
| 82 | + }, | ||
| 83 | + { | ||
| 84 | + path: 'sys/log', | ||
| 85 | + name: 'Log', | ||
| 86 | + component: () => import('@/views/sys/log/index.vue'), | ||
| 87 | + meta: { | ||
| 88 | + title: '日志管理', | ||
| 89 | + requiresAuth: true | ||
| 90 | + } | ||
| 91 | + }, | ||
| 92 | + { | ||
| 93 | + path: 'dicts', | ||
| 94 | + name: 'Dicts', | ||
| 95 | + component: () => import('@/views/dicts/index.vue'), | ||
| 96 | + meta: { | ||
| 97 | + title: '字典管理', | ||
| 98 | + requiresAuth: true | ||
| 99 | + } | ||
| 100 | + }, | ||
| 101 | + { | ||
| 102 | + path: 'logs', | ||
| 103 | + name: 'Logs', | ||
| 104 | + component: () => import('@/views/logs/index.vue'), | ||
| 105 | + meta: { | ||
| 106 | + title: '日志管理', | ||
| 107 | + requiresAuth: true | ||
| 108 | + } | ||
| 109 | + }, | ||
| 110 | + { | ||
| 111 | + path: 'settings', | ||
| 112 | + name: 'Settings', | ||
| 113 | + component: () => import('@/views/settings/index.vue'), | ||
| 114 | + meta: { | ||
| 115 | + title: '系统设置', | ||
| 116 | + requiresAuth: true | ||
| 117 | + } | ||
| 118 | + } | ||
| 119 | + ] | ||
| 120 | + }, | ||
| 121 | + { | ||
| 122 | + path: '/dashboard', | ||
| 123 | + redirect: '/main/dashboard' | ||
| 124 | + }, | ||
| 125 | + { | ||
| 126 | + path: '/test', | ||
| 14 | name: 'Test', | 127 | name: 'Test', |
| 15 | component: Test, | 128 | component: Test, |
| 16 | meta: { | 129 | meta: { |
| ... | @@ -27,8 +140,13 @@ const router = createRouter({ | ... | @@ -27,8 +140,13 @@ const router = createRouter({ |
| 27 | scrollBehavior: () => ({ top: 0 }) | 140 | scrollBehavior: () => ({ top: 0 }) |
| 28 | }) | 141 | }) |
| 29 | 142 | ||
| 30 | -// 路由守卫 | 143 | +// 优化的路由守卫 |
| 31 | -router.beforeEach(async (to, from, next) => { | 144 | +router.beforeEach((to, from, next) => { |
| 145 | + // 只在路由真正变化时记录日志 | ||
| 146 | + if (to.path !== from.path) { | ||
| 147 | + console.log('路由变化:', { from: from.path, to: to.path }) | ||
| 148 | + } | ||
| 149 | + | ||
| 32 | // 开始进度条 | 150 | // 开始进度条 |
| 33 | NProgress.start() | 151 | NProgress.start() |
| 34 | 152 | ||
| ... | @@ -37,7 +155,24 @@ router.beforeEach(async (to, from, next) => { | ... | @@ -37,7 +155,24 @@ router.beforeEach(async (to, from, next) => { |
| 37 | document.title = `${to.meta.title} - Apple经销商ERP系统` | 155 | document.title = `${to.meta.title} - Apple经销商ERP系统` |
| 38 | } | 156 | } |
| 39 | 157 | ||
| 40 | - // 暂时跳过所有权限检查,直接放行 | 158 | + // 检查认证状态 |
| 159 | + if (to.meta.requiresAuth) { | ||
| 160 | + const token = localStorage.getItem('token') | ||
| 161 | + if (!token) { | ||
| 162 | + console.log('未认证,跳转到登录页') | ||
| 163 | + next('/') | ||
| 164 | + return | ||
| 165 | + } | ||
| 166 | + } | ||
| 167 | + | ||
| 168 | + // 如果已登录且访问登录页,跳转到首页 | ||
| 169 | + if (to.path === '/' && localStorage.getItem('token')) { | ||
| 170 | + console.log('已登录,跳转到首页') | ||
| 171 | + next('/main/dashboard') | ||
| 172 | + return | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + // 继续导航 | ||
| 41 | next() | 176 | next() |
| 42 | }) | 177 | }) |
| 43 | 178 | ... | ... |
| ... | @@ -12,9 +12,19 @@ const service: AxiosInstance = axios.create({ | ... | @@ -12,9 +12,19 @@ const service: AxiosInstance = axios.create({ |
| 12 | } | 12 | } |
| 13 | }) | 13 | }) |
| 14 | 14 | ||
| 15 | +console.log('Axios实例创建,baseURL:', import.meta.env.VITE_API_BASE_URL) | ||
| 16 | + | ||
| 15 | // 请求拦截器 | 17 | // 请求拦截器 |
| 16 | service.interceptors.request.use( | 18 | service.interceptors.request.use( |
| 17 | (config: any) => { | 19 | (config: any) => { |
| 20 | + console.log('请求拦截器 - 请求配置:', { | ||
| 21 | + url: config.url, | ||
| 22 | + baseURL: config.baseURL, | ||
| 23 | + method: config.method, | ||
| 24 | + params: config.params, | ||
| 25 | + headers: config.headers | ||
| 26 | + }) | ||
| 27 | + | ||
| 18 | const userStore = useUserStore() | 28 | const userStore = useUserStore() |
| 19 | 29 | ||
| 20 | // 添加token到请求头 | 30 | // 添加token到请求头 |
| ... | @@ -105,6 +115,7 @@ service.interceptors.response.use( | ... | @@ -105,6 +115,7 @@ service.interceptors.response.use( |
| 105 | // 请求方法封装 | 115 | // 请求方法封装 |
| 106 | export const request = { | 116 | export const request = { |
| 107 | get<T = any>(url: string, params?: any): Promise<T> { | 117 | get<T = any>(url: string, params?: any): Promise<T> { |
| 118 | + console.log('Request GET:', url, params) | ||
| 108 | return service.get(url, { params }) | 119 | return service.get(url, { params }) |
| 109 | }, | 120 | }, |
| 110 | 121 | ... | ... |
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
frontend/src/views/sys/dict-item/index.vue
0 → 100644
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
| 1 | -<template> | ||
| 2 | - <div class="user-management"> | ||
| 3 | - <!-- 搜索表单 --> | ||
| 4 | - <el-card class="search-form"> | ||
| 5 | - <el-form :model="searchForm" inline> | ||
| 6 | - <el-form-item label="用户名"> | ||
| 7 | - <el-input v-model="searchForm.username" placeholder="请输入用户名" clearable /> | ||
| 8 | - </el-form-item> | ||
| 9 | - <el-form-item label="真实姓名"> | ||
| 10 | - <el-input v-model="searchForm.realName" placeholder="请输入真实姓名" clearable /> | ||
| 11 | - </el-form-item> | ||
| 12 | - <el-form-item label="状态"> | ||
| 13 | - <el-select v-model="searchForm.status" placeholder="请选择状态" clearable> | ||
| 14 | - <el-option label="正常" :value="1" /> | ||
| 15 | - <el-option label="停用" :value="0" /> | ||
| 16 | - </el-select> | ||
| 17 | - </el-form-item> | ||
| 18 | - <el-form-item> | ||
| 19 | - <el-button type="primary" @click="handleSearch"> | ||
| 20 | - <el-icon><Search /></el-icon> | ||
| 21 | - 搜索 | ||
| 22 | - </el-button> | ||
| 23 | - <el-button @click="handleReset"> | ||
| 24 | - <el-icon><Refresh /></el-icon> | ||
| 25 | - 重置 | ||
| 26 | - </el-button> | ||
| 27 | - </el-form-item> | ||
| 28 | - </el-form> | ||
| 29 | - </el-card> | ||
| 30 | - | ||
| 31 | - <!-- 操作按钮 --> | ||
| 32 | - <el-card class="operation-buttons"> | ||
| 33 | - <el-button type="primary" @click="handleAdd"> | ||
| 34 | - <el-icon><Plus /></el-icon> | ||
| 35 | - 新增用户 | ||
| 36 | - </el-button> | ||
| 37 | - <el-button type="danger" :disabled="!multipleSelection.length" @click="handleBatchDelete"> | ||
| 38 | - <el-icon><Delete /></el-icon> | ||
| 39 | - 批量删除 | ||
| 40 | - </el-button> | ||
| 41 | - </el-card> | ||
| 42 | - | ||
| 43 | - <!-- 数据表格 --> | ||
| 44 | - <el-card class="table-container"> | ||
| 45 | - <el-table | ||
| 46 | - v-loading="loading" | ||
| 47 | - :data="tableData" | ||
| 48 | - @selection-change="handleSelectionChange" | ||
| 49 | - > | ||
| 50 | - <el-table-column type="selection" width="55" /> | ||
| 51 | - <el-table-column prop="userId" label="用户ID" width="80" /> | ||
| 52 | - <el-table-column prop="username" label="用户名" width="120" /> | ||
| 53 | - <el-table-column prop="realName" label="真实姓名" width="120" /> | ||
| 54 | - <el-table-column prop="email" label="邮箱" width="200" /> | ||
| 55 | - <el-table-column prop="phone" label="手机号" width="120" /> | ||
| 56 | - <el-table-column prop="status" label="状态" width="80"> | ||
| 57 | - <template #default="{ row }"> | ||
| 58 | - <el-tag :type="row.status === 1 ? 'success' : 'danger'"> | ||
| 59 | - {{ row.status === 1 ? '正常' : '停用' }} | ||
| 60 | - </el-tag> | ||
| 61 | - </template> | ||
| 62 | - </el-table-column> | ||
| 63 | - <el-table-column prop="createTime" label="创建时间" width="180" /> | ||
| 64 | - <el-table-column label="操作" width="200" fixed="right"> | ||
| 65 | - <template #default="{ row }"> | ||
| 66 | - <el-button type="primary" size="small" @click="handleEdit(row)"> | ||
| 67 | - 编辑 | ||
| 68 | - </el-button> | ||
| 69 | - <el-button type="danger" size="small" @click="handleDelete(row)"> | ||
| 70 | - 删除 | ||
| 71 | - </el-button> | ||
| 72 | - </template> | ||
| 73 | - </el-table-column> | ||
| 74 | - </el-table> | ||
| 75 | - | ||
| 76 | - <!-- 分页 --> | ||
| 77 | - <el-pagination | ||
| 78 | - v-model:current-page="pagination.current" | ||
| 79 | - v-model:page-size="pagination.size" | ||
| 80 | - :total="pagination.total" | ||
| 81 | - :page-sizes="[10, 20, 50, 100]" | ||
| 82 | - layout="total, sizes, prev, pager, next, jumper" | ||
| 83 | - @size-change="handleSizeChange" | ||
| 84 | - @current-change="handleCurrentChange" | ||
| 85 | - /> | ||
| 86 | - </el-card> | ||
| 87 | - </div> | ||
| 88 | -</template> | ||
| 89 | - | ||
| 90 | -<script setup lang="ts"> | ||
| 91 | -import { ref, reactive, onMounted } from 'vue' | ||
| 92 | -import { ElMessage, ElMessageBox } from 'element-plus' | ||
| 93 | -import type { User } from '@/types' | ||
| 94 | - | ||
| 95 | -// 搜索表单 | ||
| 96 | -const searchForm = reactive({ | ||
| 97 | - username: '', | ||
| 98 | - realName: '', | ||
| 99 | - status: undefined | ||
| 100 | -}) | ||
| 101 | - | ||
| 102 | -// 表格数据 | ||
| 103 | -const tableData = ref<User[]>([]) | ||
| 104 | -const loading = ref(false) | ||
| 105 | -const multipleSelection = ref<User[]>([]) | ||
| 106 | - | ||
| 107 | -// 分页 | ||
| 108 | -const pagination = reactive({ | ||
| 109 | - current: 1, | ||
| 110 | - size: 10, | ||
| 111 | - total: 0 | ||
| 112 | -}) | ||
| 113 | - | ||
| 114 | -// 获取用户列表 | ||
| 115 | -const getUserList = async () => { | ||
| 116 | - loading.value = true | ||
| 117 | - try { | ||
| 118 | - // 这里应该调用API | ||
| 119 | - // const response = await getUserPageApi({ ...searchForm, ...pagination }) | ||
| 120 | - // tableData.value = response.records | ||
| 121 | - // pagination.total = response.total | ||
| 122 | - | ||
| 123 | - // 模拟数据 | ||
| 124 | - tableData.value = [ | ||
| 125 | - { | ||
| 126 | - userId: 1, | ||
| 127 | - username: 'admin', | ||
| 128 | - realName: '管理员', | ||
| 129 | - email: 'admin@example.com', | ||
| 130 | - phone: '13800138000', | ||
| 131 | - status: 1, | ||
| 132 | - createTime: '2024-01-01 10:00:00', | ||
| 133 | - updateTime: '2024-01-01 10:00:00', | ||
| 134 | - roles: [], | ||
| 135 | - permissions: [] | ||
| 136 | - } | ||
| 137 | - ] | ||
| 138 | - pagination.total = 1 | ||
| 139 | - } catch (error) { | ||
| 140 | - ElMessage.error('获取用户列表失败') | ||
| 141 | - } finally { | ||
| 142 | - loading.value = false | ||
| 143 | - } | ||
| 144 | -} | ||
| 145 | - | ||
| 146 | -// 搜索 | ||
| 147 | -const handleSearch = () => { | ||
| 148 | - pagination.current = 1 | ||
| 149 | - getUserList() | ||
| 150 | -} | ||
| 151 | - | ||
| 152 | -// 重置 | ||
| 153 | -const handleReset = () => { | ||
| 154 | - Object.assign(searchForm, { | ||
| 155 | - username: '', | ||
| 156 | - realName: '', | ||
| 157 | - status: undefined | ||
| 158 | - }) | ||
| 159 | - handleSearch() | ||
| 160 | -} | ||
| 161 | - | ||
| 162 | -// 新增用户 | ||
| 163 | -const handleAdd = () => { | ||
| 164 | - ElMessage.info('新增用户功能待实现') | ||
| 165 | -} | ||
| 166 | - | ||
| 167 | -// 编辑用户 | ||
| 168 | -const handleEdit = (row: User) => { | ||
| 169 | - ElMessage.info(`编辑用户: ${row.username}`) | ||
| 170 | -} | ||
| 171 | - | ||
| 172 | -// 删除用户 | ||
| 173 | -const handleDelete = async (row: User) => { | ||
| 174 | - try { | ||
| 175 | - await ElMessageBox.confirm(`确定要删除用户 "${row.username}" 吗?`, '提示', { | ||
| 176 | - confirmButtonText: '确定', | ||
| 177 | - cancelButtonText: '取消', | ||
| 178 | - type: 'warning' | ||
| 179 | - }) | ||
| 180 | - ElMessage.success('删除成功') | ||
| 181 | - getUserList() | ||
| 182 | - } catch (error) { | ||
| 183 | - // 用户取消删除 | ||
| 184 | - } | ||
| 185 | -} | ||
| 186 | - | ||
| 187 | -// 批量删除 | ||
| 188 | -const handleBatchDelete = async () => { | ||
| 189 | - try { | ||
| 190 | - await ElMessageBox.confirm(`确定要删除选中的 ${multipleSelection.value.length} 个用户吗?`, '提示', { | ||
| 191 | - confirmButtonText: '确定', | ||
| 192 | - cancelButtonText: '取消', | ||
| 193 | - type: 'warning' | ||
| 194 | - }) | ||
| 195 | - ElMessage.success('批量删除成功') | ||
| 196 | - getUserList() | ||
| 197 | - } catch (error) { | ||
| 198 | - // 用户取消删除 | ||
| 199 | - } | ||
| 200 | -} | ||
| 201 | - | ||
| 202 | -// 选择变化 | ||
| 203 | -const handleSelectionChange = (selection: User[]) => { | ||
| 204 | - multipleSelection.value = selection | ||
| 205 | -} | ||
| 206 | - | ||
| 207 | -// 分页大小变化 | ||
| 208 | -const handleSizeChange = (size: number) => { | ||
| 209 | - pagination.size = size | ||
| 210 | - getUserList() | ||
| 211 | -} | ||
| 212 | - | ||
| 213 | -// 当前页变化 | ||
| 214 | -const handleCurrentChange = (current: number) => { | ||
| 215 | - pagination.current = current | ||
| 216 | - getUserList() | ||
| 217 | -} | ||
| 218 | - | ||
| 219 | -// 组件挂载时获取数据 | ||
| 220 | -onMounted(() => { | ||
| 221 | - getUserList() | ||
| 222 | -}) | ||
| 223 | -</script> | ||
| 224 | - | ||
| 225 | -<style lang="scss" scoped> | ||
| 226 | -.user-management { | ||
| 227 | - .search-form { | ||
| 228 | - margin-bottom: 16px; | ||
| 229 | - } | ||
| 230 | - | ||
| 231 | - .operation-buttons { | ||
| 232 | - margin-bottom: 16px; | ||
| 233 | - } | ||
| 234 | - | ||
| 235 | - .table-container { | ||
| 236 | - .el-pagination { | ||
| 237 | - margin-top: 16px; | ||
| 238 | - text-align: right; | ||
| 239 | - } | ||
| 240 | - } | ||
| 241 | -} | ||
| 242 | -</style> |
frontend/src/views/users/index.vue
0 → 100644
This diff is collapsed. Click to expand it.
| 1 | import { defineConfig } from 'vite' | 1 | import { defineConfig } from 'vite' |
| 2 | import vue from '@vitejs/plugin-vue' | 2 | import vue from '@vitejs/plugin-vue' |
| 3 | import { resolve } from 'path' | 3 | import { resolve } from 'path' |
| 4 | +import AutoImport from 'unplugin-auto-import/vite' | ||
| 5 | +import Components from 'unplugin-vue-components/vite' | ||
| 6 | +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' | ||
| 4 | 7 | ||
| 5 | // https://vitejs.dev/config/ | 8 | // https://vitejs.dev/config/ |
| 6 | export default defineConfig({ | 9 | export default defineConfig({ |
| 7 | - plugins: [vue()], | 10 | + plugins: [ |
| 11 | + vue(), | ||
| 12 | + AutoImport({ | ||
| 13 | + resolvers: [ElementPlusResolver()], | ||
| 14 | + }), | ||
| 15 | + Components({ | ||
| 16 | + resolvers: [ElementPlusResolver()], | ||
| 17 | + }), | ||
| 18 | + ], | ||
| 8 | resolve: { | 19 | resolve: { |
| 9 | alias: { | 20 | alias: { |
| 10 | '@': resolve(__dirname, 'src') | 21 | '@': resolve(__dirname, 'src') | ... | ... |
frontend/字段验证错误显示功能说明.md
0 → 100644
| 1 | +# 字段验证错误显示功能说明 | ||
| 2 | + | ||
| 3 | +## 功能概述 | ||
| 4 | + | ||
| 5 | +前端用户管理页面现在支持显示后端返回的详细字段验证错误信息,提供更好的用户体验。 | ||
| 6 | + | ||
| 7 | +## 功能特性 | ||
| 8 | + | ||
| 9 | +### 1. 字段级错误显示 | ||
| 10 | +- 当后端返回 `400` 状态码和详细的字段错误信息时,前端会自动解析并在对应字段下方显示错误信息 | ||
| 11 | +- 支持的错误格式:`{"code":400,"message":"参数验证失败","data":{"email":"邮箱格式不正确"}}` | ||
| 12 | + | ||
| 13 | +### 2. 视觉反馈 | ||
| 14 | +- 有错误的字段会显示红色边框 | ||
| 15 | +- 错误信息以红色文字显示在字段下方 | ||
| 16 | +- 错误信息字体较小,不影响整体布局 | ||
| 17 | + | ||
| 18 | +### 3. 错误清除机制 | ||
| 19 | +- 打开新增/编辑对话框时自动清除之前的错误信息 | ||
| 20 | +- 重新提交表单时清除之前的错误信息 | ||
| 21 | + | ||
| 22 | +## 支持的字段 | ||
| 23 | + | ||
| 24 | +以下字段支持错误显示: | ||
| 25 | +- `username` - 用户名 | ||
| 26 | +- `realName` - 真实姓名 | ||
| 27 | +- `phone` - 手机号 | ||
| 28 | +- `email` - 邮箱 | ||
| 29 | +- `remark` - 备注 | ||
| 30 | + | ||
| 31 | +## 后端错误格式要求 | ||
| 32 | + | ||
| 33 | +后端需要返回以下格式的错误信息: | ||
| 34 | + | ||
| 35 | +```json | ||
| 36 | +{ | ||
| 37 | + "code": 400, | ||
| 38 | + "message": "参数验证失败", | ||
| 39 | + "data": { | ||
| 40 | + "username": "用户名已存在", | ||
| 41 | + "email": "邮箱格式不正确", | ||
| 42 | + "phone": "手机号格式不正确" | ||
| 43 | + } | ||
| 44 | +} | ||
| 45 | +``` | ||
| 46 | + | ||
| 47 | +## 使用示例 | ||
| 48 | + | ||
| 49 | +### 1. 邮箱格式错误 | ||
| 50 | +当用户输入无效邮箱时: | ||
| 51 | +- 邮箱输入框显示红色边框 | ||
| 52 | +- 输入框下方显示"邮箱格式不正确" | ||
| 53 | + | ||
| 54 | +### 2. 用户名重复 | ||
| 55 | +当用户名已存在时: | ||
| 56 | +- 用户名输入框显示红色边框 | ||
| 57 | +- 输入框下方显示"用户名已存在" | ||
| 58 | + | ||
| 59 | +### 3. 多个字段错误 | ||
| 60 | +当多个字段都有错误时: | ||
| 61 | +- 所有有错误的字段都会显示红色边框 | ||
| 62 | +- 每个字段下方显示对应的错误信息 | ||
| 63 | + | ||
| 64 | +## 技术实现 | ||
| 65 | + | ||
| 66 | +### 1. 响应式错误状态 | ||
| 67 | +```javascript | ||
| 68 | +const fieldErrors = reactive<Record<string, string>>({}) | ||
| 69 | +``` | ||
| 70 | + | ||
| 71 | +### 2. 错误解析函数 | ||
| 72 | +```javascript | ||
| 73 | +const parseFieldErrors = (errorData: any) => { | ||
| 74 | + clearFieldErrors() | ||
| 75 | + | ||
| 76 | + if (errorData && typeof errorData === 'object') { | ||
| 77 | + Object.keys(errorData).forEach(field => { | ||
| 78 | + fieldErrors[field] = errorData[field] | ||
| 79 | + }) | ||
| 80 | + } | ||
| 81 | +} | ||
| 82 | +``` | ||
| 83 | + | ||
| 84 | +### 3. 模板绑定 | ||
| 85 | +```vue | ||
| 86 | +<input | ||
| 87 | + v-model="userForm.email" | ||
| 88 | + type="email" | ||
| 89 | + class="form-input" | ||
| 90 | + :class="{ 'error': fieldErrors.email }" | ||
| 91 | +/> | ||
| 92 | +<div v-if="fieldErrors.email" class="field-error">{{ fieldErrors.email }}</div> | ||
| 93 | +``` | ||
| 94 | + | ||
| 95 | +## 样式说明 | ||
| 96 | + | ||
| 97 | +### 错误状态样式 | ||
| 98 | +```css | ||
| 99 | +.form-input.error, .form-select.error { | ||
| 100 | + border-color: #ff4d4f; | ||
| 101 | + box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2); | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | +.field-error { | ||
| 105 | + color: #ff4d4f; | ||
| 106 | + font-size: 11px; | ||
| 107 | + margin-top: 4px; | ||
| 108 | + display: block; | ||
| 109 | + line-height: 1.4; | ||
| 110 | +} | ||
| 111 | +``` | ||
| 112 | + | ||
| 113 | +## 测试方法 | ||
| 114 | + | ||
| 115 | +1. 打开用户管理页面 | ||
| 116 | +2. 点击"新增用户"按钮 | ||
| 117 | +3. 输入无效的邮箱格式(如:`invalid-email`) | ||
| 118 | +4. 点击保存 | ||
| 119 | +5. 观察邮箱字段是否显示红色边框和错误信息 | ||
| 120 | + | ||
| 121 | +## 注意事项 | ||
| 122 | + | ||
| 123 | +1. 错误信息会在用户重新打开对话框时自动清除 | ||
| 124 | +2. 只有后端返回 `400` 状态码且包含 `data` 字段时才会显示字段级错误 | ||
| 125 | +3. 如果后端返回其他格式的错误,仍会显示通用错误信息 | ||
| 126 | +4. 错误信息支持中文,建议后端返回中文错误信息以提供更好的用户体验 |
frontend/时间格式修改说明.md
0 → 100644
| 1 | +# 用户管理时间格式修改说明 | ||
| 2 | + | ||
| 3 | +## 修改内容 | ||
| 4 | + | ||
| 5 | +将用户管理列表中的创建时间格式从 ISO 格式改为更易读的格式: | ||
| 6 | + | ||
| 7 | +- **修改前**: `2025-09-23T17:49:06` | ||
| 8 | +- **修改后**: `2025-09-23 17:49:06` | ||
| 9 | + | ||
| 10 | +## 技术实现 | ||
| 11 | + | ||
| 12 | +### 1. 添加时间格式化函数 | ||
| 13 | + | ||
| 14 | +```javascript | ||
| 15 | +// 格式化时间显示 | ||
| 16 | +const formatDateTime = (dateTime: string | undefined): string => { | ||
| 17 | + if (!dateTime) return '' | ||
| 18 | + | ||
| 19 | + // 将 ISO 格式的时间字符串转换为可读格式 | ||
| 20 | + // 例如: 2025-09-23T17:49:06 -> 2025-09-23 17:49:06 | ||
| 21 | + return dateTime.replace('T', ' ') | ||
| 22 | +} | ||
| 23 | +``` | ||
| 24 | + | ||
| 25 | +### 2. 修改模板显示 | ||
| 26 | + | ||
| 27 | +```vue | ||
| 28 | +<!-- 修改前 --> | ||
| 29 | +<td>{{ user.createTime }}</td> | ||
| 30 | + | ||
| 31 | +<!-- 修改后 --> | ||
| 32 | +<td>{{ formatDateTime(user.createTime) }}</td> | ||
| 33 | +``` | ||
| 34 | + | ||
| 35 | +## 功能特点 | ||
| 36 | + | ||
| 37 | +1. **空值处理**: 如果时间字段为空或未定义,返回空字符串 | ||
| 38 | +2. **简单替换**: 使用 `replace('T', ' ')` 将 ISO 格式中的 `T` 替换为空格 | ||
| 39 | +3. **类型安全**: 使用 TypeScript 确保类型安全 | ||
| 40 | +4. **性能优化**: 简单的字符串替换,性能开销很小 | ||
| 41 | + | ||
| 42 | +## 影响范围 | ||
| 43 | + | ||
| 44 | +- **用户管理列表**: 创建时间列显示格式已修改 | ||
| 45 | +- **其他时间字段**: 目前只修改了 `createTime` 字段 | ||
| 46 | +- **搜索功能**: 时间搜索功能不受影响 | ||
| 47 | + | ||
| 48 | +## 测试方法 | ||
| 49 | + | ||
| 50 | +1. 打开用户管理页面 | ||
| 51 | +2. 查看用户列表中的"创建时间"列 | ||
| 52 | +3. 确认时间格式显示为 `2025-09-23 17:49:06` 而不是 `2025-09-23T17:49:06` | ||
| 53 | + | ||
| 54 | +## 扩展说明 | ||
| 55 | + | ||
| 56 | +如果需要修改其他时间字段(如 `updateTime`),可以按照相同的方式: | ||
| 57 | + | ||
| 58 | +```vue | ||
| 59 | +<td>{{ formatDateTime(user.updateTime) }}</td> | ||
| 60 | +``` | ||
| 61 | + | ||
| 62 | +## 注意事项 | ||
| 63 | + | ||
| 64 | +1. 这个修改只影响前端显示,不影响后端数据存储格式 | ||
| 65 | +2. 时间搜索功能仍然使用原始的 ISO 格式 | ||
| 66 | +3. 如果后端返回的时间格式发生变化,这个函数仍然可以正常工作 |
frontend/时间选择器功能说明.md
0 → 100644
| 1 | +# 时间选择器功能说明 | ||
| 2 | + | ||
| 3 | +## 功能概述 | ||
| 4 | + | ||
| 5 | +用户管理页面的时间搜索组件已升级为具有悬停弹出选项功能的高级时间选择器,提供更便捷的时间范围选择体验。 | ||
| 6 | + | ||
| 7 | +## 功能特性 | ||
| 8 | + | ||
| 9 | +### 1. 悬停弹出选项 | ||
| 10 | +- 鼠标悬停在时间输入框上时,自动弹出时间选项菜单 | ||
| 11 | +- 支持快速选择常用的时间范围 | ||
| 12 | +- 提供直观的视觉反馈 | ||
| 13 | + | ||
| 14 | +### 2. 预设时间选项 | ||
| 15 | +- **今天**: 选择当前日期 | ||
| 16 | +- **昨天**: 选择昨天的日期 | ||
| 17 | +- **本周**: 选择本周开始日期(周一) | ||
| 18 | +- **上周**: 选择上周的时间范围 | ||
| 19 | +- **本月**: 选择本月开始日期 | ||
| 20 | +- **上月**: 选择上月的时间范围 | ||
| 21 | +- **自定义**: 打开原生日期选择器 | ||
| 22 | + | ||
| 23 | +### 3. 智能时间范围 | ||
| 24 | +- 开始时间和结束时间都有独立的选项菜单 | ||
| 25 | +- 结束时间会自动选择合理的结束日期 | ||
| 26 | +- 支持时间范围的逻辑验证 | ||
| 27 | + | ||
| 28 | +## 技术实现 | ||
| 29 | + | ||
| 30 | +### 1. 组件结构 | ||
| 31 | +```vue | ||
| 32 | +<div class="date-picker-container"> | ||
| 33 | + <input | ||
| 34 | + v-model="searchForm.startTime" | ||
| 35 | + type="text" | ||
| 36 | + class="date-input" | ||
| 37 | + readonly | ||
| 38 | + @mouseenter="showStartDateOptions = true" | ||
| 39 | + @mouseleave="hideStartDateOptions" | ||
| 40 | + /> | ||
| 41 | + <div v-if="showStartDateOptions" class="date-options"> | ||
| 42 | + <!-- 时间选项列表 --> | ||
| 43 | + </div> | ||
| 44 | +</div> | ||
| 45 | +``` | ||
| 46 | + | ||
| 47 | +### 2. 状态管理 | ||
| 48 | +```javascript | ||
| 49 | +// 时间选择器状态 | ||
| 50 | +const showStartDateOptions = ref(false) | ||
| 51 | +const showEndDateOptions = ref(false) | ||
| 52 | +const showStartDatePicker = ref(false) | ||
| 53 | +const showEndDatePicker = ref(false) | ||
| 54 | +``` | ||
| 55 | + | ||
| 56 | +### 3. 时间计算函数 | ||
| 57 | +- `getCurrentDate()`: 获取当前日期 | ||
| 58 | +- `getDateBefore(days)`: 获取指定天数前的日期 | ||
| 59 | +- `getWeekStart()`: 获取本周开始日期 | ||
| 60 | +- `getMonthStart()`: 获取本月开始日期 | ||
| 61 | +- `getLastMonthStart()`: 获取上月开始日期 | ||
| 62 | +- `getLastMonthEnd()`: 获取上月结束日期 | ||
| 63 | + | ||
| 64 | +## 使用方法 | ||
| 65 | + | ||
| 66 | +### 1. 快速选择时间 | ||
| 67 | +1. 将鼠标悬停在"开始时间"或"结束时间"输入框上 | ||
| 68 | +2. 在弹出的选项菜单中选择所需的时间范围 | ||
| 69 | +3. 系统自动填充对应的日期 | ||
| 70 | + | ||
| 71 | +### 2. 自定义时间选择 | ||
| 72 | +1. 悬停在时间输入框上 | ||
| 73 | +2. 选择"自定义"选项 | ||
| 74 | +3. 使用原生日期选择器选择具体日期 | ||
| 75 | + | ||
| 76 | +### 3. 时间范围搜索 | ||
| 77 | +1. 选择开始时间和结束时间 | ||
| 78 | +2. 点击"搜索"按钮执行查询 | ||
| 79 | +3. 系统会搜索指定时间范围内的用户 | ||
| 80 | + | ||
| 81 | +## 样式设计 | ||
| 82 | + | ||
| 83 | +### 1. 选项菜单样式 | ||
| 84 | +```css | ||
| 85 | +.date-options { | ||
| 86 | + position: absolute; | ||
| 87 | + top: 100%; | ||
| 88 | + background: white; | ||
| 89 | + border: 1px solid #d9d9d9; | ||
| 90 | + border-radius: 4px; | ||
| 91 | + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); | ||
| 92 | + z-index: 1000; | ||
| 93 | +} | ||
| 94 | +``` | ||
| 95 | + | ||
| 96 | +### 2. 选项项样式 | ||
| 97 | +```css | ||
| 98 | +.date-option { | ||
| 99 | + padding: 8px 12px; | ||
| 100 | + cursor: pointer; | ||
| 101 | + transition: background-color 0.2s; | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | +.date-option:hover { | ||
| 105 | + background-color: #f5f5f5; | ||
| 106 | + color: #1890ff; | ||
| 107 | +} | ||
| 108 | +``` | ||
| 109 | + | ||
| 110 | +## 交互逻辑 | ||
| 111 | + | ||
| 112 | +### 1. 悬停显示 | ||
| 113 | +- 鼠标进入输入框时显示选项菜单 | ||
| 114 | +- 鼠标离开输入框时延迟隐藏菜单(200ms) | ||
| 115 | +- 鼠标进入选项菜单时保持显示状态 | ||
| 116 | + | ||
| 117 | +### 2. 选择处理 | ||
| 118 | +- 点击选项后自动填充对应日期 | ||
| 119 | +- 选择"自定义"时显示原生日期选择器 | ||
| 120 | +- 选择完成后自动隐藏选项菜单 | ||
| 121 | + | ||
| 122 | +### 3. 时间范围逻辑 | ||
| 123 | +- 开始时间不能晚于结束时间 | ||
| 124 | +- 结束时间不能早于开始时间 | ||
| 125 | +- 支持空值选择(只选择开始时间或结束时间) | ||
| 126 | + | ||
| 127 | +## 兼容性 | ||
| 128 | + | ||
| 129 | +- 支持现代浏览器的原生日期选择器 | ||
| 130 | +- 降级到文本输入框(readonly) | ||
| 131 | +- 响应式设计,适配不同屏幕尺寸 | ||
| 132 | + | ||
| 133 | +## 扩展功能 | ||
| 134 | + | ||
| 135 | +### 1. 可添加的选项 | ||
| 136 | +- 最近7天 | ||
| 137 | +- 最近30天 | ||
| 138 | +- 最近3个月 | ||
| 139 | +- 最近一年 | ||
| 140 | + | ||
| 141 | +### 2. 可优化的功能 | ||
| 142 | +- 时间范围验证 | ||
| 143 | +- 快捷清除功能 | ||
| 144 | +- 时间格式显示优化 | ||
| 145 | + | ||
| 146 | +## 测试方法 | ||
| 147 | + | ||
| 148 | +1. 打开用户管理页面 | ||
| 149 | +2. 将鼠标悬停在"创建时间"的输入框上 | ||
| 150 | +3. 观察是否弹出选项菜单 | ||
| 151 | +4. 点击不同选项测试功能 | ||
| 152 | +5. 选择"自定义"测试原生日期选择器 | ||
| 153 | +6. 执行搜索验证时间范围功能 | ||
| 154 | + | ||
| 155 | +## 注意事项 | ||
| 156 | + | ||
| 157 | +1. 时间格式统一使用 YYYY-MM-DD 格式 | ||
| 158 | +2. 时区处理基于浏览器本地时间 | ||
| 159 | +3. 选项菜单的 z-index 设置为 1000,确保显示在最上层 | ||
| 160 | +4. 延迟隐藏机制避免鼠标快速移动时菜单闪烁 |
-
Please register or login to post a comment