zhouhui.jiang

添加 用户管理、角色管理、菜单管理、字典管理、日志管理

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
...@@ -40,6 +42,9 @@ public class SysRoleController { ...@@ -40,6 +42,9 @@ public class SysRoleController {
40 42
41 @Autowired 43 @Autowired
42 private SysRoleService sysRoleService; 44 private SysRoleService sysRoleService;
45 +
46 + @Autowired
47 + private SysMenuMapper sysMenuMapper;
43 48
44 /** 49 /**
45 * 获取角色列表 50 * 获取角色列表
...@@ -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) {
...@@ -392,4 +398,4 @@ public class SysUserController { ...@@ -392,4 +398,4 @@ public class SysUserController {
392 398
393 return userRes; 399 return userRes;
394 } 400 }
395 -} 401 +}
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -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 /**
...@@ -47,4 +48,4 @@ public interface SysUserMapper extends BaseMapper<SysUser> { ...@@ -47,4 +48,4 @@ public interface SysUserMapper extends BaseMapper<SysUser> {
47 */ 48 */
48 @Update("UPDATE t_sys_user SET last_login_time = NOW(), last_login_ip = #{ip} WHERE user_id = #{userId}") 49 @Update("UPDATE t_sys_user SET last_login_time = NOW(), last_login_ip = #{ip} WHERE user_id = #{userId}")
49 void updateLastLoginInfo(@Param("userId") Long userId, @Param("ip") String ip); 50 void updateLastLoginInfo(@Param("userId") Long userId, @Param("ip") String ip);
50 -} 51 +}
...\ No newline at end of file ...\ No newline at end of file
......
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 }
......
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 +
95 -} 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 + },
188 +}
...\ No newline at end of file ...\ No newline at end of file
......
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[]
7 } 66 }
8 67
9 -// 获取角色分页列表 68 +// 角色接口类型定义
10 -export function getRolePageApi(params: PageRequest & { 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[]
80 +}
81 +
82 +export interface RoleSearchParams {
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 + // 删除角色(单个或批量)
57 -} 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 + }
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
......
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
...@@ -52,4 +187,4 @@ router.onError((error) => { ...@@ -52,4 +187,4 @@ router.onError((error) => {
52 NProgress.done() 187 NProgress.done()
53 }) 188 })
54 189
55 -export default router 190 +export default router
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -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.
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>
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')
......
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. 错误信息支持中文,建议后端返回中文错误信息以提供更好的用户体验
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. 如果后端返回的时间格式发生变化,这个函数仍然可以正常工作
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. 延迟隐藏机制避免鼠标快速移动时菜单闪烁