feat: 用户管理模块
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import com.inspur.llm.chat.base.base.BaseController;
|
|||||||
import com.inspur.llm.chat.gpt.constant.SysLogTypeConstant;
|
import com.inspur.llm.chat.gpt.constant.SysLogTypeConstant;
|
||||||
import com.inspur.llm.chat.gpt.enums.BusinessTypeEnum;
|
import com.inspur.llm.chat.gpt.enums.BusinessTypeEnum;
|
||||||
import com.inspur.llm.chat.gpt.pojo.annotation.Log;
|
import com.inspur.llm.chat.gpt.pojo.annotation.Log;
|
||||||
|
import com.inspur.llm.chat.gpt.pojo.command.SysUserPasswordCommand;
|
||||||
import com.inspur.llm.chat.gpt.pojo.command.UserCommand;
|
import com.inspur.llm.chat.gpt.pojo.command.UserCommand;
|
||||||
import com.inspur.llm.chat.gpt.pojo.entity.IPageInfo;
|
import com.inspur.llm.chat.gpt.pojo.entity.IPageInfo;
|
||||||
import com.inspur.llm.chat.gpt.pojo.entity.Query;
|
import com.inspur.llm.chat.gpt.pojo.entity.Query;
|
||||||
@@ -105,6 +106,19 @@ public class UserController extends BaseController {
|
|||||||
return userService.updateUser(command);
|
return userService.updateUser(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员重置用户密码
|
||||||
|
*
|
||||||
|
* @author: Auto
|
||||||
|
* @date: 2026-04-08
|
||||||
|
* @version: 1.0.0
|
||||||
|
*/
|
||||||
|
@PutMapping("/password/reset")
|
||||||
|
@Log(type = SysLogTypeConstant.DEFAULT, businessType = BusinessTypeEnum.UPDATE)
|
||||||
|
public ResponseInfo resetPassword(@RequestBody SysUserPasswordCommand command) {
|
||||||
|
return userService.resetPassword(command);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除会员用户
|
* 批量删除会员用户
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -104,6 +104,11 @@ public class User extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否管理员 0否 1是
|
||||||
|
*/
|
||||||
|
private Boolean admind;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否删除 0->未删除;1->已删除
|
* 是否删除 0->未删除;1->已删除
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -108,4 +108,9 @@ public class UserVO implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否管理员 0否 1是
|
||||||
|
*/
|
||||||
|
private Boolean admind;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,4 +119,12 @@ public interface IUserService extends IService<User> {
|
|||||||
*/
|
*/
|
||||||
ResponseInfo removeUserById(Long id);
|
ResponseInfo removeUserById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员重置用户密码
|
||||||
|
*
|
||||||
|
* @param command 包含用户id和新密码
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
ResponseInfo resetPassword(SysUserPasswordCommand command);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,10 +108,17 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
|
|||||||
}
|
}
|
||||||
user = DozerUtil.convertor(command, User.class);
|
user = DozerUtil.convertor(command, User.class);
|
||||||
user.setCreateUser(command.getOperater());
|
user.setCreateUser(command.getOperater());
|
||||||
String name = "手机用户" + RandomUtil.randomString(6);
|
|
||||||
user.setUid(UUID.fastUUID().toString());
|
user.setUid(UUID.fastUUID().toString());
|
||||||
user.setName(name);
|
if (ValidatorUtil.isNull(user.getName()) || user.getName().isEmpty()) {
|
||||||
user.setNickName(name);
|
String name = "手机用户" + RandomUtil.randomString(6);
|
||||||
|
user.setName(name);
|
||||||
|
user.setNickName(name);
|
||||||
|
} else if (ValidatorUtil.isNull(user.getNickName()) || user.getNickName().isEmpty()) {
|
||||||
|
user.setNickName(user.getName());
|
||||||
|
}
|
||||||
|
if (ValidatorUtil.isNotNull(command.getPassword()) && !command.getPassword().isEmpty()) {
|
||||||
|
user.setPassword(JWTPasswordEncoder.bcryptEncode(command.getPassword()));
|
||||||
|
}
|
||||||
user.setType(UserTypeEnum.TEL.getValue());
|
user.setType(UserTypeEnum.TEL.getValue());
|
||||||
userMapper.insert(user);
|
userMapper.insert(user);
|
||||||
return ResponseInfo.success();
|
return ResponseInfo.success();
|
||||||
@@ -185,4 +192,13 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
|
|||||||
return ResponseInfo.success();
|
return ResponseInfo.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class, transactionManager = "masterTransactionManager")
|
||||||
|
public ResponseInfo resetPassword(SysUserPasswordCommand command) {
|
||||||
|
User user = getUser(command.getId());
|
||||||
|
user.setPassword(JWTPasswordEncoder.bcryptEncode(command.getNewPassword()));
|
||||||
|
userMapper.updateById(user);
|
||||||
|
return ResponseInfo.success();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,14 @@
|
|||||||
<result column="share_id" property="shareId"/>
|
<result column="share_id" property="shareId"/>
|
||||||
<result column="type" property="type"/>
|
<result column="type" property="type"/>
|
||||||
<result column="status" property="status"/>
|
<result column="status" property="status"/>
|
||||||
|
<result column="admind" property="admind"/>
|
||||||
<result column="deleted" property="deleted"/>
|
<result column="deleted" property="deleted"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<!-- 通用查询结果列 -->
|
<!-- 通用查询结果列 -->
|
||||||
<sql id="Base_Column_List">
|
<sql id="Base_Column_List">
|
||||||
t.id, t.create_user, t.create_time, t.update_user, t.update_time, t.login_time, t.uid, t.name, t.nick_name, t.tel, t.password,
|
t.id, t.create_user, t.create_time, t.update_user, t.update_time, t.login_time, t.uid, t.name, t.nick_name, t.tel, t.password,
|
||||||
t.avatar, t.openid, t.unionid, t.ip, t.context, t.num, t.share_id, t.type, t.status, t.deleted
|
t.avatar, t.openid, t.unionid, t.ip, t.context, t.num, t.share_id, t.type, t.status, t.admind, t.deleted
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<!-- 通用查询条件 -->
|
<!-- 通用查询条件 -->
|
||||||
|
|||||||
@@ -996,4 +996,45 @@ export function fetchLoginStatus<T>() {
|
|||||||
return get<T>({
|
return get<T>({
|
||||||
url: '/app/api/auth/loginStatus'
|
url: '/app/api/auth/loginStatus'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ****************************用户管理 - 开始********************************
|
||||||
|
// 用户分页列表
|
||||||
|
export function fetchUserPage<T>(data: { current: number; size: number; keyword?: string }) {
|
||||||
|
return get<T>({
|
||||||
|
url: '/gpt/user/page',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增用户
|
||||||
|
export function createUser<T>(data: object) {
|
||||||
|
return post<T>({
|
||||||
|
url: '/gpt/user',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改用户
|
||||||
|
export function editUser<T>(data: object) {
|
||||||
|
return put<T>({
|
||||||
|
url: '/gpt/user',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除用户
|
||||||
|
export function deleteUser<T>(ids: string) {
|
||||||
|
return post<T>({
|
||||||
|
url: `/gpt/user/${ids}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 管理员重置用户密码
|
||||||
|
export function resetUserPassword<T>(data: { id: number; newPassword: string }) {
|
||||||
|
return put<T>({
|
||||||
|
url: '/gpt/user/password/reset',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// ****************************用户管理 - 结束********************************
|
||||||
@@ -15,13 +15,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<el-divider />
|
<el-divider />
|
||||||
<div class="operateBottom">
|
<div class="operateBottom">
|
||||||
<img src="../assets/images/operates/user.png" class="userImg" alt="" />
|
<el-popover placement="right" :width="140" trigger="click">
|
||||||
<img
|
<template #reference>
|
||||||
src="../assets/images/operates/quit.png"
|
<img src="../assets/images/operates/user.png" class="userImg pointer" alt="" />
|
||||||
@click="quit"
|
</template>
|
||||||
class="quit"
|
<div class="userMenu">
|
||||||
alt=""
|
<div class="userMenuItem" @click="goProfile">
|
||||||
/>
|
<el-icon><User /></el-icon>
|
||||||
|
<span>个人中心</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="isAdmin" class="userMenuItem" @click="goUserManage">
|
||||||
|
<el-icon><Setting /></el-icon>
|
||||||
|
<span>用户管理</span>
|
||||||
|
</div>
|
||||||
|
<el-divider style="margin: 6px 0" />
|
||||||
|
<div class="userMenuItem danger" @click="quit">
|
||||||
|
<el-icon><SwitchButton /></el-icon>
|
||||||
|
<span>退出登录</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,10 +42,12 @@
|
|||||||
|
|
||||||
<script setup lang='ts'>
|
<script setup lang='ts'>
|
||||||
import { useAuthStore } from "@/store";
|
import { useAuthStore } from "@/store";
|
||||||
import { reactive, watch } from "vue";
|
import { reactive, watch, computed } from "vue";
|
||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
|
import { User, Setting, SwitchButton } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
const isAdmin = computed(() => authStore.session?.admind === true);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const menuList = reactive([
|
const menuList = reactive([
|
||||||
@@ -79,6 +94,14 @@ const quit = () => {
|
|||||||
router.push("/login");
|
router.push("/login");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const goProfile = () => {
|
||||||
|
router.push("/profile");
|
||||||
|
};
|
||||||
|
|
||||||
|
const goUserManage = () => {
|
||||||
|
router.push("/userManage");
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回首页
|
* 返回首页
|
||||||
*/
|
*/
|
||||||
@@ -253,12 +276,6 @@ watch(
|
|||||||
width: 42px;
|
width: 42px;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
}
|
}
|
||||||
.quit {
|
|
||||||
margin-top: 36px;
|
|
||||||
width: 18.85px;
|
|
||||||
height: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.el-divider--horizontal {
|
.el-divider--horizontal {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
@@ -266,4 +283,26 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.userMenu {
|
||||||
|
.userMenuItem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
&:hover {
|
||||||
|
background: #f0f2f5;
|
||||||
|
color: #004ea0;
|
||||||
|
}
|
||||||
|
&.danger:hover {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -39,6 +39,16 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'Application',
|
name: 'Application',
|
||||||
component: () => import('@/views/applications/index.vue'),
|
component: () => import('@/views/applications/index.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/profile',
|
||||||
|
name: 'Profile',
|
||||||
|
component: () => import('@/views/profile/index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/userManage',
|
||||||
|
name: 'UserManage',
|
||||||
|
component: () => import('@/views/userManage/index.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/translate',
|
path: '/translate',
|
||||||
name: 'Translate',
|
name: 'Translate',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ interface SessionResponse {
|
|||||||
password: string | null
|
password: string | null
|
||||||
context: boolean | false
|
context: boolean | false
|
||||||
num: number | 0
|
num: number | 0
|
||||||
|
admind: boolean | false
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthState {
|
export interface AuthState {
|
||||||
|
|||||||
210
chat_web_front/src/views/profile/index.vue
Normal file
210
chat_web_front/src/views/profile/index.vue
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
<template>
|
||||||
|
<div class="profileWrap">
|
||||||
|
<div class="profileCard">
|
||||||
|
<h2 class="profileTitle">个人中心</h2>
|
||||||
|
|
||||||
|
<div class="profileSection">
|
||||||
|
<div class="avatarSection">
|
||||||
|
<el-avatar :size="80" :src="userInfo.avatar || defaultAvatar" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-divider />
|
||||||
|
|
||||||
|
<div class="profileSection">
|
||||||
|
<h3>基本信息</h3>
|
||||||
|
<el-form :model="userForm" label-width="80px" style="max-width: 400px">
|
||||||
|
<el-form-item label="手机号">
|
||||||
|
<el-input :value="userInfo.tel" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="姓名">
|
||||||
|
<el-input v-model="userForm.name" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="昵称">
|
||||||
|
<el-input v-model="userForm.nickName" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSaveInfo" :loading="saving">保存</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-divider />
|
||||||
|
|
||||||
|
<div class="profileSection">
|
||||||
|
<h3>修改密码</h3>
|
||||||
|
<el-form
|
||||||
|
ref="passwordFormRef"
|
||||||
|
:model="passwordForm"
|
||||||
|
:rules="passwordRules"
|
||||||
|
label-width="80px"
|
||||||
|
style="max-width: 400px"
|
||||||
|
>
|
||||||
|
<el-form-item label="旧密码" prop="oldPassword">
|
||||||
|
<el-input v-model="passwordForm.oldPassword" type="password" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="新密码" prop="newPassword">
|
||||||
|
<el-input v-model="passwordForm.newPassword" type="password" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="确认密码" prop="confirmPassword">
|
||||||
|
<el-input v-model="passwordForm.confirmPassword" type="password" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleChangePassword" :loading="changingPwd">
|
||||||
|
修改密码
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import { fetchSession, updateUser, updatePassword } from '@/api'
|
||||||
|
import { useAuthStore } from '@/store'
|
||||||
|
import defaultAvatarImg from '@/assets/images/operates/user.png'
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const defaultAvatar = defaultAvatarImg
|
||||||
|
|
||||||
|
const userInfo = reactive({
|
||||||
|
id: 0,
|
||||||
|
tel: '',
|
||||||
|
name: '',
|
||||||
|
nickName: '',
|
||||||
|
avatar: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const userForm = reactive({
|
||||||
|
name: '',
|
||||||
|
nickName: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const passwordForm = reactive({
|
||||||
|
oldPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const passwordFormRef = ref<FormInstance>()
|
||||||
|
const saving = ref(false)
|
||||||
|
const changingPwd = ref(false)
|
||||||
|
|
||||||
|
const validateConfirm = (rule: any, value: any, callback: any) => {
|
||||||
|
if (value !== passwordForm.newPassword) {
|
||||||
|
callback(new Error('两次输入的密码不一致'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordRules = reactive<FormRules>({
|
||||||
|
oldPassword: [{ required: true, message: '请输入旧密码', trigger: 'blur' }],
|
||||||
|
newPassword: [
|
||||||
|
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||||
|
{ min: 6, message: '密码长度至少6位', trigger: 'blur' },
|
||||||
|
],
|
||||||
|
confirmPassword: [
|
||||||
|
{ required: true, message: '请确认新密码', trigger: 'blur' },
|
||||||
|
{ validator: validateConfirm, trigger: 'blur' },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const loadUserInfo = async () => {
|
||||||
|
const res = await fetchSession<any>()
|
||||||
|
if (res.code === 200) {
|
||||||
|
Object.assign(userInfo, res.data)
|
||||||
|
userForm.name = res.data.name || ''
|
||||||
|
userForm.nickName = res.data.nickName || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSaveInfo = async () => {
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
const res = await updateUser<any>({
|
||||||
|
name: userForm.name,
|
||||||
|
nickName: userForm.nickName,
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('信息保存成功')
|
||||||
|
userInfo.name = userForm.name
|
||||||
|
userInfo.nickName = userForm.nickName
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '保存失败')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChangePassword = async () => {
|
||||||
|
const valid = await passwordFormRef.value?.validate().catch(() => false)
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
changingPwd.value = true
|
||||||
|
try {
|
||||||
|
const res = await updatePassword<any>({
|
||||||
|
oldPassword: passwordForm.oldPassword,
|
||||||
|
newPassword: passwordForm.newPassword,
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('密码修改成功,请重新登录')
|
||||||
|
passwordForm.oldPassword = ''
|
||||||
|
passwordForm.newPassword = ''
|
||||||
|
passwordForm.confirmPassword = ''
|
||||||
|
authStore.removeToken()
|
||||||
|
window.location.hash = '#/login'
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '密码修改失败')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
changingPwd.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadUserInfo()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.profileWrap {
|
||||||
|
flex: 1;
|
||||||
|
padding: 30px;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileCard {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 30px 40px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileTitle {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profileSection {
|
||||||
|
h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarSection {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
296
chat_web_front/src/views/userManage/index.vue
Normal file
296
chat_web_front/src/views/userManage/index.vue
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
<template>
|
||||||
|
<div class="userManageWrap">
|
||||||
|
<div class="userManageCard">
|
||||||
|
<div class="headerRow">
|
||||||
|
<h2>用户管理</h2>
|
||||||
|
<el-button type="primary" @click="handleAdd">新增用户</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table :data="tableData" v-loading="loading" stripe style="width: 100%">
|
||||||
|
<el-table-column prop="id" label="ID" width="70" />
|
||||||
|
<el-table-column prop="tel" label="手机号" width="140" />
|
||||||
|
<el-table-column prop="name" label="姓名" width="140" />
|
||||||
|
<el-table-column prop="nickName" label="昵称" width="140" />
|
||||||
|
<el-table-column prop="status" label="状态" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small">
|
||||||
|
{{ row.status === 1 ? '正常' : '禁用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||||
|
<el-table-column label="操作" min-width="200" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button link type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button link type="warning" size="small" @click="handleResetPwd(row)">重置密码</el-button>
|
||||||
|
<el-popconfirm title="确定删除该用户?" @confirm="handleDelete(row)">
|
||||||
|
<template #reference>
|
||||||
|
<el-button link type="danger" size="small">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div class="paginationRow">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="currentPage"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
:total="total"
|
||||||
|
:page-sizes="[10, 20, 50]"
|
||||||
|
layout="total, sizes, prev, pager, next"
|
||||||
|
@size-change="loadUsers"
|
||||||
|
@current-change="loadUsers"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 新增/编辑用户弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogType === 'add' ? '新增用户' : '编辑用户'"
|
||||||
|
width="460px"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="userFormRef"
|
||||||
|
:model="userForm"
|
||||||
|
:rules="userRules"
|
||||||
|
label-width="80px"
|
||||||
|
>
|
||||||
|
<el-form-item label="手机号" prop="tel">
|
||||||
|
<el-input v-model="userForm.tel" :disabled="dialogType === 'edit'" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="姓名" prop="name">
|
||||||
|
<el-input v-model="userForm.name" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="昵称">
|
||||||
|
<el-input v-model="userForm.nickName" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="dialogType === 'add'" label="密码" prop="password">
|
||||||
|
<el-input v-model="userForm.password" type="password" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit" :loading="submitting">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 重置密码弹窗 -->
|
||||||
|
<el-dialog v-model="resetPwdVisible" title="重置密码" width="400px" destroy-on-close>
|
||||||
|
<el-form ref="resetPwdFormRef" :model="resetPwdForm" :rules="resetPwdRules" label-width="80px">
|
||||||
|
<el-form-item label="用户">
|
||||||
|
<el-input :value="resetPwdForm.tel" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="新密码" prop="newPassword">
|
||||||
|
<el-input v-model="resetPwdForm.newPassword" type="password" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="resetPwdVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleResetPwdSubmit" :loading="resettingPwd">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import { fetchUserPage, createUser, editUser, deleteUser, resetUserPassword } from '@/api'
|
||||||
|
|
||||||
|
const tableData = ref<any[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(10)
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
// 新增/编辑
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogType = ref<'add' | 'edit'>('add')
|
||||||
|
const userFormRef = ref<FormInstance>()
|
||||||
|
const submitting = ref(false)
|
||||||
|
const userForm = reactive({
|
||||||
|
id: 0,
|
||||||
|
tel: '',
|
||||||
|
name: '',
|
||||||
|
nickName: '',
|
||||||
|
password: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const userRules = reactive<FormRules>({
|
||||||
|
tel: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
|
||||||
|
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||||
|
password: [
|
||||||
|
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||||
|
{ min: 6, message: '密码长度至少6位', trigger: 'blur' },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
// 重置密码
|
||||||
|
const resetPwdVisible = ref(false)
|
||||||
|
const resetPwdFormRef = ref<FormInstance>()
|
||||||
|
const resettingPwd = ref(false)
|
||||||
|
const resetPwdForm = reactive({
|
||||||
|
id: 0,
|
||||||
|
tel: '',
|
||||||
|
newPassword: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const resetPwdRules = reactive<FormRules>({
|
||||||
|
newPassword: [
|
||||||
|
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||||
|
{ min: 6, message: '密码长度至少6位', trigger: 'blur' },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const loadUsers = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await fetchUserPage<any>({
|
||||||
|
current: currentPage.value,
|
||||||
|
size: pageSize.value,
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
tableData.value = res.data.records || []
|
||||||
|
total.value = res.data.total || 0
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error('加载用户列表失败:', e)
|
||||||
|
ElMessage.error('加载用户列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
dialogType.value = 'add'
|
||||||
|
userForm.id = 0
|
||||||
|
userForm.tel = ''
|
||||||
|
userForm.name = ''
|
||||||
|
userForm.nickName = ''
|
||||||
|
userForm.password = ''
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = (row: any) => {
|
||||||
|
dialogType.value = 'edit'
|
||||||
|
userForm.id = row.id
|
||||||
|
userForm.tel = row.tel
|
||||||
|
userForm.name = row.name || ''
|
||||||
|
userForm.nickName = row.nickName || ''
|
||||||
|
userForm.password = ''
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const valid = await userFormRef.value?.validate().catch(() => false)
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
let res: any
|
||||||
|
if (dialogType.value === 'add') {
|
||||||
|
res = await createUser<any>({
|
||||||
|
tel: userForm.tel,
|
||||||
|
name: userForm.name,
|
||||||
|
nickName: userForm.nickName,
|
||||||
|
password: userForm.password,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res = await editUser<any>({
|
||||||
|
id: userForm.id,
|
||||||
|
name: userForm.name,
|
||||||
|
nickName: userForm.nickName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '编辑成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
loadUsers()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '操作失败')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResetPwd = (row: any) => {
|
||||||
|
resetPwdForm.id = row.id
|
||||||
|
resetPwdForm.tel = row.tel
|
||||||
|
resetPwdForm.newPassword = ''
|
||||||
|
resetPwdVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResetPwdSubmit = async () => {
|
||||||
|
const valid = await resetPwdFormRef.value?.validate().catch(() => false)
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
resettingPwd.value = true
|
||||||
|
try {
|
||||||
|
const res = await resetUserPassword<any>({
|
||||||
|
id: resetPwdForm.id,
|
||||||
|
newPassword: resetPwdForm.newPassword,
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('密码重置成功')
|
||||||
|
resetPwdVisible.value = false
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '重置失败')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
resettingPwd.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = async (row: any) => {
|
||||||
|
const res = await deleteUser<any>(row.id)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
loadUsers()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadUsers()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.userManageWrap {
|
||||||
|
flex: 1;
|
||||||
|
padding: 30px;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userManageCard {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px 30px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerRow {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.paginationRow {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user