【新增】用户管理
This commit is contained in:
@@ -25,6 +25,11 @@ public class User implements Serializable {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.corewing.app.modules.admin.biz;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.dto.biz.ResetPassword;
|
||||
import com.corewing.app.entity.SysUser;
|
||||
import com.corewing.app.entity.User;
|
||||
import com.corewing.app.service.UserService;
|
||||
import io.lettuce.core.ConnectionEvents;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/biz/user")
|
||||
public class BizUserController {
|
||||
|
||||
@Resource
|
||||
private UserService userService;
|
||||
|
||||
/**
|
||||
* 跳转到用户管理页面
|
||||
* @return 页面名称
|
||||
*/
|
||||
@GetMapping("/index")
|
||||
public String index() {
|
||||
return "admin/biz/user/index";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分页信息
|
||||
* @param user 用户搜索对象
|
||||
* @return 用户信息集合
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
@ResponseBody
|
||||
public Result<Page<User>> page(User user) {
|
||||
Page<User> sysUserPage = userService.page(user);
|
||||
return Result.success(sysUserPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param resetPassword 修改密码DTO
|
||||
* @return 成功 or 失败
|
||||
*/
|
||||
@PutMapping("/resetPassword")
|
||||
@ResponseBody
|
||||
public Result<String> resetPassword(@RequestBody ResetPassword resetPassword) {
|
||||
boolean flag = userService.resetPassword(resetPassword);
|
||||
return Result.isBoolAsMsg(flag, "修改密码成功");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package com.corewing.app.modules.admin;
|
||||
package com.corewing.app.modules.admin.sys;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.dto.SysLoginRequest;
|
||||
import com.corewing.app.dto.api.SysLoginRequest;
|
||||
import com.corewing.app.entity.SysUser;
|
||||
import com.corewing.app.service.SysUserService;
|
||||
import com.corewing.app.util.I18nUtil;
|
||||
import com.corewing.app.util.IpUtil;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -16,7 +18,7 @@ import java.util.Map;
|
||||
/**
|
||||
* 后台管理用户 Controller
|
||||
*/
|
||||
@RestController
|
||||
@Controller
|
||||
@RequestMapping("/sys/user")
|
||||
public class SysUserController {
|
||||
|
||||
@@ -26,10 +28,23 @@ public class SysUserController {
|
||||
this.sysUserService = sysUserService;
|
||||
}
|
||||
|
||||
@GetMapping("/index")
|
||||
public String index() {
|
||||
return "admin/sys/user/index";
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@ResponseBody
|
||||
public Result<Page<SysUser>> page(SysUser sysUser) {
|
||||
Page<SysUser> sysUserPage = sysUserService.page(sysUser);
|
||||
return Result.success(sysUserPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台管理登录
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
@ResponseBody
|
||||
public Result<Map<String, Object>> login(@RequestBody SysLoginRequest request, HttpServletRequest httpRequest) {
|
||||
try {
|
||||
String loginIp = IpUtil.getClientIp(httpRequest);
|
||||
@@ -56,6 +71,7 @@ public class SysUserController {
|
||||
* 后台管理登出
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
@ResponseBody
|
||||
public Result<String> logout() {
|
||||
StpUtil.logout();
|
||||
return Result.success(I18nUtil.getMessage("user.logout.success"));
|
||||
@@ -65,6 +81,7 @@ public class SysUserController {
|
||||
* 获取当前登录用户信息
|
||||
*/
|
||||
@GetMapping("/info")
|
||||
@ResponseBody
|
||||
public Result<SysUser> getUserInfo() {
|
||||
Long userId = StpUtil.getLoginIdAsLong();
|
||||
SysUser user = sysUserService.getById(userId);
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.corewing.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.corewing.app.entity.SysUser;
|
||||
|
||||
@@ -8,6 +9,8 @@ import com.corewing.app.entity.SysUser;
|
||||
*/
|
||||
public interface SysUserService extends IService<SysUser> {
|
||||
|
||||
Page<SysUser> page(SysUser sysUser);
|
||||
|
||||
/**
|
||||
* 根据用户名查询用户
|
||||
*
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.corewing.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.corewing.app.dto.biz.ResetPassword;
|
||||
import com.corewing.app.entity.SysUser;
|
||||
import com.corewing.app.entity.User;
|
||||
|
||||
/**
|
||||
@@ -8,6 +11,8 @@ import com.corewing.app.entity.User;
|
||||
*/
|
||||
public interface UserService extends IService<User> {
|
||||
|
||||
Page<User> page(User user);
|
||||
|
||||
/**
|
||||
* 根据用户名查询用户
|
||||
*
|
||||
@@ -74,4 +79,11 @@ public interface UserService extends IService<User> {
|
||||
* @param loginIp 登录IP
|
||||
*/
|
||||
void updateLoginInfo(Long userId, String loginIp);
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param resetPassword
|
||||
* @return
|
||||
*/
|
||||
boolean resetPassword(ResetPassword resetPassword);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.corewing.app.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.corewing.app.common.page.PageContext;
|
||||
import com.corewing.app.entity.SysUser;
|
||||
import com.corewing.app.mapper.SysUserMapper;
|
||||
import com.corewing.app.service.SysUserService;
|
||||
@@ -20,6 +22,13 @@ import java.time.LocalDateTime;
|
||||
@Service
|
||||
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
|
||||
|
||||
public Page<SysUser> page(SysUser sysUser) {
|
||||
Page<SysUser> page = PageContext.getPage(SysUser.class);
|
||||
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
|
||||
return page(page, queryWrapper);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SysUser getByUsername(String username) {
|
||||
if (!StringUtils.hasText(username)) {
|
||||
|
||||
@@ -2,7 +2,10 @@ package com.corewing.app.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.corewing.app.common.page.PageContext;
|
||||
import com.corewing.app.dto.biz.ResetPassword;
|
||||
import com.corewing.app.entity.User;
|
||||
import com.corewing.app.mapper.UserMapper;
|
||||
import com.corewing.app.service.UserService;
|
||||
@@ -23,10 +26,22 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
|
||||
private final VerifyCodeService verifyCodeService;
|
||||
private final Ip2RegionUtil ip2RegionUtil;
|
||||
private final UserMapper userMapper;
|
||||
|
||||
public UserServiceImpl(VerifyCodeService verifyCodeService, Ip2RegionUtil ip2RegionUtil) {
|
||||
public UserServiceImpl(VerifyCodeService verifyCodeService, Ip2RegionUtil ip2RegionUtil, UserMapper userMapper) {
|
||||
this.verifyCodeService = verifyCodeService;
|
||||
this.ip2RegionUtil = ip2RegionUtil;
|
||||
this.userMapper = userMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<User> page(User user) {
|
||||
Page<User> page = PageContext.getPage(User.class);
|
||||
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.like(StringUtils.hasText(user.getNickName()), User::getNickName, user.getNickName());
|
||||
queryWrapper.like(StringUtils.hasText(user.getUsername()), User::getUsername, user.getUsername());
|
||||
queryWrapper.like(user.getStatus() != null, User::getStatus, user.getStatus());
|
||||
return page(page, queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,8 +95,9 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
}
|
||||
|
||||
// 验证密码(MD5加密)
|
||||
String encryptPassword = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
|
||||
if (!encryptPassword.equals(user.getPassword())) {
|
||||
// String encryptPassword = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
|
||||
// 客户端已经使用加密
|
||||
if (!password.equals(user.getPassword())) {
|
||||
throw new RuntimeException(I18nUtil.getMessage("error.password.incorrect"));
|
||||
}
|
||||
|
||||
@@ -166,4 +182,16 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
user.setLoginRegion(ip2RegionUtil.getRegion(loginIp));
|
||||
this.updateById(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetPassword(ResetPassword resetPassword) {
|
||||
User user = getById(resetPassword.getUserId());
|
||||
if(user == null) {
|
||||
throw new RuntimeException(I18nUtil.getMessage("error.user.not.found"));
|
||||
}
|
||||
// 更新新密码
|
||||
String newPasswordMd5 = DigestUtils.md5DigestAsHex(resetPassword.getPassword().getBytes(StandardCharsets.UTF_8));
|
||||
user.setPassword(newPasswordMd5);
|
||||
return updateById(user);
|
||||
}
|
||||
}
|
||||
|
||||
117
src/main/resources/static/assets/css/table.css
Normal file
117
src/main/resources/static/assets/css/table.css
Normal file
@@ -0,0 +1,117 @@
|
||||
/* 基础样式优化 */
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 批量操作样式(表格下方左侧) */
|
||||
.batch-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #6c757d;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
.table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.table-hover tbody tr:hover {
|
||||
background-color: rgba(14, 165, 233, 0.05);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.table tbody td, table thead th{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/*.table tbody tr.selected {*/
|
||||
/* background-color: rgba(14, 165, 233, 0.1);*/
|
||||
/* border-color: #0ea5e9;*/
|
||||
/*}*/
|
||||
|
||||
/*.table th {*/
|
||||
/* font-weight: 600;*/
|
||||
/* color: #212529;*/
|
||||
/* border-bottom-width: 2px;*/
|
||||
/*}*/
|
||||
|
||||
/*.table td, .table th {*/
|
||||
/* vertical-align: middle;*/
|
||||
/* padding: 12px 16px;*/
|
||||
/*}*/
|
||||
|
||||
/* 搜索框样式 */
|
||||
.search-item {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
min-width: 200px;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.input-group-text {
|
||||
background-color: #f1f3f5;
|
||||
border-color: #dee2e6;
|
||||
}
|
||||
|
||||
/* 按钮样式优化 */
|
||||
.btn {
|
||||
padding: 6px 16px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 4px 12px;
|
||||
}
|
||||
|
||||
/* 徽章样式 */
|
||||
.badge {
|
||||
padding: 4px 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
/* 无数据提示 */
|
||||
.no-data {
|
||||
color: #6c757d;
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.no-data i {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
color: #adb5bd;
|
||||
}
|
||||
627
src/main/resources/templates/admin/biz/user/index.html
Normal file
627
src/main/resources/templates/admin/biz/user/index.html
Normal file
@@ -0,0 +1,627 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>用户管理系统</title>
|
||||
<!-- 外部引入库文件 -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<!-- 外部引入自定义样式 -->
|
||||
<link rel="stylesheet" href="/assets/css/table.css">
|
||||
</head>
|
||||
<body id="app">
|
||||
<div class="main-container">
|
||||
<div class="table-container">
|
||||
<!-- 页面标题 -->
|
||||
<h3 class="mb-4">用户管理</h3>
|
||||
|
||||
<!-- 多参数搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<!-- 搜索参数1:关键词 -->
|
||||
<div class="search-item">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
昵称
|
||||
</span>
|
||||
<input
|
||||
v-model="searchParams.nickName"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="请输入昵称"
|
||||
@keyup.enter="fetchData()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-item">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
用户名
|
||||
</span>
|
||||
<input
|
||||
v-model="searchParams.username"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="请输入用户名"
|
||||
@keyup.enter="fetchData()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索参数2:状态筛选 -->
|
||||
<div class="search-item">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
状态
|
||||
</span>
|
||||
<select
|
||||
v-model="searchParams.status"
|
||||
class="form-select"
|
||||
@change="fetchData()"
|
||||
>
|
||||
<option value="">全部状态</option>
|
||||
<option value="1">启用</option>
|
||||
<option value="0">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和重置按钮 -->
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary" @click="fetchData()">
|
||||
<i class="bi bi-search me-1"></i> 搜索
|
||||
</button>
|
||||
<button class="btn btn-success" @click="resetSearch()">
|
||||
<i class="bi bi-arrow-counterclockwise me-1"></i> 重置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增按钮 -->
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-info" @click="openAddModal()">
|
||||
<i class="bi bi-plus-circle me-1"></i> 新增
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped table-hover table-bordered ">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<!-- 全选复选框 -->
|
||||
<th style="width: 50px;">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
v-model="selectAll"
|
||||
@change="toggleSelectAll()"
|
||||
>
|
||||
</th>
|
||||
<th>ID</th>
|
||||
<th>昵称</th>
|
||||
<th>用户名</th>
|
||||
<th>状态</th>
|
||||
<th>创建时间</th>
|
||||
<th style="width: 120px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 加载中状态 -->
|
||||
<tr v-if="loading">
|
||||
<td colspan="8" class="p-0">
|
||||
<div class="loading-container">
|
||||
<div class="spinner-border text-primary" role="status" style="width: 2rem; height: 2rem;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-2 text-muted">加载中,请稍候...</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- 无数据状态 -->
|
||||
<tr v-else-if="tableData.length === 0">
|
||||
<td colspan="8" class="p-0">
|
||||
<div class="no-data">
|
||||
<i class="bi bi-folder-x"></i>
|
||||
<h5>暂无匹配数据</h5>
|
||||
<p class="text-muted">请尝试调整搜索条件或重置查询</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- 数据列表 -->
|
||||
<tr v-else v-for="(item, index) in tableData" :key="item.id"
|
||||
:class="{ selected: selectedIds.includes(item.id) }">
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
v-model="selectedIds"
|
||||
:value="item.id"
|
||||
@change="toggleSelectItem(item.id)"
|
||||
>
|
||||
</td>
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item.nickName }}</td>
|
||||
<td>{{ item.username }}</td>
|
||||
<td>
|
||||
<span class="badge" :class="item.status === 1 ? 'bg-success' : 'bg-danger'">
|
||||
{{ item.status === 1 ? '启用' : '禁用' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ formatTime(item.createTime) }}</td>
|
||||
<td>
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-sm btn-warning" @click="openPwdModal(item)">
|
||||
<i class="bi bi-lock"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-primary" @click="openEditModal(item)">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" @click="handleDelete(item.id)">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 批量操作(表格下方左侧) -->
|
||||
<div class="batch-actions mt-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="text-muted">已选中 {{ selectedIds.length }} 条数据</span>
|
||||
|
||||
<!-- 使用select标签实现批量操作选择 -->
|
||||
<select
|
||||
class="form-select"
|
||||
v-model="batchAction"
|
||||
:disabled="selectedIds.length === 0"
|
||||
@change="handleBatchOperation"
|
||||
style="width: auto; min-width: 160px;"
|
||||
>
|
||||
<option value="">-- 批量操作 --</option>
|
||||
<option value="delete">批量删除</option>
|
||||
<option value="enable">批量启用</option>
|
||||
<option value="disable">批量禁用</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="clearSelected()"
|
||||
:disabled="selectedIds.length === 0"
|
||||
>
|
||||
<i class="bi bi-x-circle me-1"></i> 取消选择
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div class="page-info">
|
||||
共 {{ total }} 条数据,当前第 {{ pageNum }}/{{ totalPages }} 页
|
||||
</div>
|
||||
<nav>
|
||||
<ul class="pagination pagination-sm mb-0">
|
||||
<li class="page-item" :class="{ disabled: pageNum === 1 }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(1)">首页</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === 1 }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(pageNum - 1)">上一页</a>
|
||||
</li>
|
||||
<li class="page-item active" v-for="page in pageList" :key="page">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(page)">{{ page }}</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === totalPages }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(pageNum + 1)">下一页</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === totalPages }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(totalPages)">末页</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 修改密码 -->
|
||||
<div class="modal fade" id="resetPwdModal" tabindex="-1" aria-labelledby="resetPwdModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="resetPwdModalLabel">修改密码</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<label for="recipient-name" class="col-form-label">昵称:</label>
|
||||
<input type="text" class="form-control" disabled :value="resetPasswordDto.nickName">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="recipient-name" class="col-form-label">用户名:</label>
|
||||
<input type="text" class="form-control" disabled :value="resetPasswordDto.username">
|
||||
</div>
|
||||
|
||||
<div class="mb-3 ">
|
||||
<label for="togglePassword" class="form-label">密码</label>
|
||||
<div class="position-relative">
|
||||
<!-- 密码输入框 -->
|
||||
<input :type="isPasswordVisible ? 'text' : 'password'" class="form-control pe-10" id="togglePassword"
|
||||
placeholder="请输入密码" v-model="resetPasswordDto.password">
|
||||
<!-- 切换按钮(绝对定位在输入框右侧) -->
|
||||
<button type="button"
|
||||
@click="togglePasswordVisibility"
|
||||
class="btn btn-transparent position-absolute end-0 top-0 h-100 px-3 border-0 bg-transparent"
|
||||
id="toggleBtn">
|
||||
<i :class="isPasswordVisible ? 'bi bi-eye-slash text-secondary' : 'bi bi-eye text-secondary'" id="toggleIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||
<button type="button" class="btn btn-primary" @click.prevent="resetPwd">修改</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 新增 -->
|
||||
<div class="modal fade" id="addModal" tabindex="-1" aria-labelledby="addModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="addModalLabel">New message</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<label for="recipient-name" class="col-form-label">Recipient:</label>
|
||||
<input type="text" class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="message-text" class="col-form-label">Message:</label>
|
||||
<textarea class="form-control"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary">Send message</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 修改 -->
|
||||
<div class="modal fade" id="editModel" tabindex="-1" aria-labelledby="editModelModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="editModelModalLabel">New message</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<label for="recipient-name" class="col-form-label">Recipient:</label>
|
||||
<input type="text" class="form-control" id="recipient-name">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="message-text" class="col-form-label">Message:</label>
|
||||
<textarea class="form-control" id="message-text"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary">Send message</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 外部引入库文件 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- 外部引入模拟请求文件 -->
|
||||
<script src="/assets/js/axiosRequest.js"></script>
|
||||
|
||||
<!-- 页面核心逻辑 -->
|
||||
<script>
|
||||
const {createApp} = Vue;
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
tableData: [], // 表格数据源
|
||||
loading: false, // 加载状态
|
||||
// 多搜索参数绑定(与搜索栏对应)
|
||||
searchParams: {
|
||||
nickName: '',
|
||||
username: '',
|
||||
status: '',
|
||||
},
|
||||
// 分页参数
|
||||
pageNum: 1, // 当前页码
|
||||
pageSize: 10, // 每页条数
|
||||
total: 0, // 总数据量
|
||||
totalPages: 0, // 总页数
|
||||
// 批量操作
|
||||
selectedIds: [], // 选中的ID集合
|
||||
selectAll: false, // 全选状态
|
||||
batchAction: '', // 批量操作选择
|
||||
// 分页显示范围(最多显示5个页码)
|
||||
pageRange: 5,
|
||||
modalInstances: {},
|
||||
resetPasswordDto: {
|
||||
userId: null,
|
||||
nickName: null,
|
||||
username: null,
|
||||
password: null,
|
||||
},
|
||||
isPasswordVisible: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 计算当前显示的页码列表
|
||||
pageList() {
|
||||
const list = [];
|
||||
if (this.totalPages === 0) return list;
|
||||
|
||||
// 总页数小于等于显示范围,直接显示所有页码
|
||||
if (this.totalPages <= this.pageRange) {
|
||||
for (let i = 1; i <= this.totalPages; i++) {
|
||||
list.push(i);
|
||||
}
|
||||
} else {
|
||||
// 总页数大于显示范围,显示当前页前后2个页码
|
||||
let start = Math.max(1, this.pageNum - 2);
|
||||
let end = Math.min(this.totalPages, this.pageNum + 2);
|
||||
|
||||
// 确保显示5个页码
|
||||
if (end - start < this.pageRange - 1) {
|
||||
if (start === 1) {
|
||||
end = this.pageRange;
|
||||
} else if (end === this.totalPages) {
|
||||
start = this.totalPages - this.pageRange + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
list.push(i);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 格式化时间(毫秒转字符串)
|
||||
formatTime(time) {
|
||||
if (!time) return '-';
|
||||
const date = new Date(time);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
},
|
||||
|
||||
// 获取表格数据(核心方法)
|
||||
async fetchData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
// 调用模拟接口获取数据
|
||||
const response = await request.get('/biz/user/page', {
|
||||
...this.searchParams, // 传递所有搜索参数
|
||||
current: this.pageNum,
|
||||
size: this.pageSize
|
||||
});
|
||||
console.log(response)
|
||||
|
||||
if (response.code === 200) {
|
||||
this.tableData = response.data.records; // 列表数据
|
||||
this.total = response.data.total; // 总条数
|
||||
this.totalPages = response.data.pages; // 总页数
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取数据失败:', error);
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
this.totalPages = 0;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.clearSelected(); // 每次加载数据清空选中状态
|
||||
}
|
||||
},
|
||||
|
||||
// 切换页码
|
||||
changePage(page) {
|
||||
// 边界判断
|
||||
if (page < 1 || page > this.totalPages || page === this.pageNum) return;
|
||||
this.pageNum = page;
|
||||
this.fetchData(); // 切换页码后重新加载数据
|
||||
},
|
||||
|
||||
// 重置搜索
|
||||
resetSearch() {
|
||||
// 重置所有搜索参数
|
||||
this.searchParams = {
|
||||
nickName: '',
|
||||
username: '',
|
||||
status: '',
|
||||
};
|
||||
this.pageNum = 1; // 重置到第一页
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
// 全选/取消全选
|
||||
toggleSelectAll() {
|
||||
if (this.selectAll) {
|
||||
// 全选:收集所有数据的ID
|
||||
this.selectedIds = this.tableData.map(item => item.id);
|
||||
} else {
|
||||
// 取消全选:清空选中ID
|
||||
this.selectedIds = [];
|
||||
}
|
||||
},
|
||||
|
||||
// 单个选中/取消选中
|
||||
toggleSelectItem(id) {
|
||||
// 当选中状态变化时,更新全选状态
|
||||
this.selectAll = this.selectedIds.length === this.tableData.length && this.tableData.length > 0;
|
||||
},
|
||||
|
||||
// 清空选中状态
|
||||
clearSelected() {
|
||||
this.selectedIds = [];
|
||||
this.selectAll = false;
|
||||
this.batchAction = ''; // 重置批量操作选择
|
||||
},
|
||||
|
||||
// 处理批量操作
|
||||
handleBatchOperation() {
|
||||
if (!this.batchAction || this.selectedIds.length === 0) return;
|
||||
|
||||
switch (this.batchAction) {
|
||||
case 'delete':
|
||||
this.handleBatchDelete();
|
||||
break;
|
||||
case 'enable':
|
||||
this.handleBatchStatus(1);
|
||||
break;
|
||||
case 'disable':
|
||||
this.handleBatchStatus(0);
|
||||
break;
|
||||
}
|
||||
|
||||
// 执行操作后重置选择
|
||||
this.batchAction = '';
|
||||
},
|
||||
|
||||
// 批量删除
|
||||
async handleBatchDelete() {
|
||||
if (!confirm(`确定要删除选中的 ${this.selectedIds.length} 条数据吗?`)) return;
|
||||
|
||||
try {
|
||||
const response = await request.post('/sys/data/batchDelete', {
|
||||
ids: this.selectedIds
|
||||
});
|
||||
|
||||
if (response.code === 200) {
|
||||
alert('删除成功!');
|
||||
this.fetchData(); // 重新加载数据
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('批量删除失败:', error);
|
||||
alert('删除失败,请重试!');
|
||||
}
|
||||
},
|
||||
|
||||
// 批量修改状态(启用/禁用)
|
||||
async handleBatchStatus(status) {
|
||||
try {
|
||||
const response = await request.post('/sys/data/batchUpdateStatus', {
|
||||
ids: this.selectedIds,
|
||||
status: status
|
||||
});
|
||||
|
||||
if (response.code === 200) {
|
||||
alert(`已${status === 1 ? '启用' : '禁用'}选中数据!`);
|
||||
this.fetchData(); // 重新加载数据
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('批量更新状态失败:', error);
|
||||
alert('操作失败,请重试!');
|
||||
}
|
||||
},
|
||||
|
||||
// 单个删除
|
||||
async handleDelete(id) {
|
||||
if (!confirm('确定要删除这条数据吗?')) return;
|
||||
|
||||
try {
|
||||
const response = await request.post('/sys/data/delete', {id});
|
||||
if (response.code === 200) {
|
||||
alert('删除成功!');
|
||||
this.fetchData(); // 重新加载数据
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
alert('删除失败,请重试!');
|
||||
}
|
||||
},
|
||||
// 打开重置密码墨台框
|
||||
openPwdModal(item) {
|
||||
console.log(item)
|
||||
this.resetPasswordDto.userId = item.id;
|
||||
this.resetPasswordDto.username = item.username;
|
||||
this.resetPasswordDto.nickName = item.nickName;
|
||||
this.modalInstances['resetPwdModal'].show();
|
||||
},
|
||||
resetPwd() {
|
||||
request.put("/biz/user/resetPassword", this.resetPasswordDto)
|
||||
.then((result) => {
|
||||
console.log(result)
|
||||
if (result.code === 200) {
|
||||
alert('修改成功!');
|
||||
this.modalInstances['resetPwdModal'].hide();
|
||||
} else {
|
||||
alert('修改失败!');
|
||||
}
|
||||
this.resetPasswordDto = {};
|
||||
});
|
||||
|
||||
},
|
||||
// 打开新增模态框
|
||||
openAddModal() {
|
||||
alert('打开新增模态框');
|
||||
// 实际项目中添加模态框显示逻辑
|
||||
},
|
||||
|
||||
// 打开编辑模态框
|
||||
openEditModal(item) {
|
||||
alert(`打开编辑模态框,编辑ID: ${item.id}`);
|
||||
// 实际项目中添加模态框显示和数据回显逻辑
|
||||
},
|
||||
// 切换密码可见性的方法
|
||||
togglePasswordVisibility() {
|
||||
this.isPasswordVisible = !this.isPasswordVisible; // 反转状态
|
||||
}
|
||||
|
||||
},
|
||||
mounted() {
|
||||
// 页面加载完成后初始化数据
|
||||
this.fetchData();
|
||||
const modalIds = ['resetPwdModal', 'editModel', 'addModel']; // 所有模态框的ID集合
|
||||
modalIds.forEach(id => {
|
||||
console.log(id)
|
||||
const modalElement = document.getElementById(id);
|
||||
// 先检查元素是否存在,避免报错
|
||||
if (modalElement) {
|
||||
this.modalInstances[id] = new bootstrap.Modal(modalElement, {
|
||||
backdrop: 'static', // 统一配置,也可单独设置
|
||||
keyboard: true // 按 ESC 不关闭(可选)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user