628 lines
17 KiB
Vue
628 lines
17 KiB
Vue
<template>
|
||
<div class="container-box">
|
||
<div class="header-bar">
|
||
<h2>用户管理</h2>
|
||
<div class="header-actions">
|
||
<el-button type="primary" @click="handleAddUser">
|
||
<el-icon><Plus /></el-icon>
|
||
添加用户
|
||
</el-button>
|
||
<el-button @click="refresh">
|
||
<el-icon><Refresh /></el-icon>
|
||
刷新
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<el-divider></el-divider>
|
||
|
||
<el-table :data="users" style="width: 100%" v-loading="loading">
|
||
<el-table-column
|
||
prop="username"
|
||
label="用户名"
|
||
width="150"
|
||
align="center"
|
||
/>
|
||
<el-table-column
|
||
prop="nickname"
|
||
label="昵称"
|
||
width="150"
|
||
align="center"
|
||
/>
|
||
<el-table-column
|
||
prop="email"
|
||
label="邮箱"
|
||
align="center"
|
||
min-width="200"
|
||
/>
|
||
<el-table-column prop="role" label="角色" width="150" align="center">
|
||
<template #default="scope">
|
||
<el-tag :type="getRoleTagType(scope.row.roleName)">
|
||
{{ scope.row.roleName || '未分配角色' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态" width="100">
|
||
<template #default="scope">
|
||
<el-tag :type="scope.row.status === 'active' ? 'success' : 'danger'">
|
||
{{ scope.row.status === "active" ? "启用" : "禁用" }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="lastLoginTime"
|
||
label="最后登录"
|
||
width="180"
|
||
align="center"
|
||
/>
|
||
<el-table-column label="操作" width="240" align="center" fixed="right">
|
||
<template #default="scope">
|
||
<el-button size="small" @click="handleEdit(scope.row)"
|
||
>编辑</el-button
|
||
>
|
||
<el-button size="small" type="warning" @click="handleChangePassword(scope.row)">
|
||
修改密码
|
||
</el-button>
|
||
<el-button size="small" type="danger" @click="handleDelete(scope.row)"
|
||
>删除</el-button
|
||
>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="pagination-bar">
|
||
<el-pagination
|
||
background
|
||
:current-page="page"
|
||
:page-size="pageSize"
|
||
:total="total"
|
||
@current-change="handlePageChange"
|
||
layout="total, prev, pager, next"
|
||
/>
|
||
</div>
|
||
|
||
<!-- Dialog for add/edit -->
|
||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px">
|
||
<el-form :model="currentForm">
|
||
<el-form-item label="用户名" v-show="dialogTitle !== '修改密码'">
|
||
<el-input v-model="form.username" />
|
||
</el-form-item>
|
||
<el-form-item label="昵称" v-if="dialogTitle !== '修改密码'">
|
||
<el-input v-model="form.nickname" />
|
||
</el-form-item>
|
||
<el-form-item label="密码" v-if="dialogTitle === '添加用户'">
|
||
<el-input
|
||
v-model="form.password"
|
||
type="password"
|
||
autocomplete="new-password"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="旧密码" v-if="dialogTitle === '修改密码'">
|
||
<el-input
|
||
v-model="passwordForm.oldPassword"
|
||
type="password"
|
||
autocomplete="current-password"
|
||
show-password
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="新密码" v-if="dialogTitle === '修改密码'">
|
||
<el-input
|
||
v-model="passwordForm.newPassword"
|
||
type="password"
|
||
autocomplete="new-password"
|
||
show-password
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="确认密码" v-if="dialogTitle === '修改密码'">
|
||
<el-input
|
||
v-model="passwordForm.confirmPassword"
|
||
type="password"
|
||
autocomplete="new-password"
|
||
show-password
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item v-if="dialogTitle === '修改密码' && passwordError">
|
||
<el-alert :title="passwordError" type="error" :closable="false" style="color: #f56c6c;" />
|
||
</el-form-item>
|
||
<el-form-item label="邮箱" v-if="dialogTitle !== '修改密码'">
|
||
<el-input v-model="form.email" />
|
||
</el-form-item>
|
||
<el-form-item label="角色" v-if="dialogTitle !== '修改密码'">
|
||
<el-select
|
||
v-model="form.roleId"
|
||
placeholder="请选择角色"
|
||
style="width: 100%"
|
||
:loading="loadingRoles"
|
||
>
|
||
<el-option
|
||
v-for="role in roleList"
|
||
:key="role.roleId"
|
||
:label="role.roleName"
|
||
:value="role.roleId"
|
||
>
|
||
<span>{{ role.roleName }}</span>
|
||
<span style="color: #8492a6; font-size: 13px; margin-left: 8px;">
|
||
({{ role.roleCode }})
|
||
</span>
|
||
</el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="状态" v-if="dialogTitle !== '修改密码'">
|
||
<el-select v-model="form.status">
|
||
<el-option label="启用" value="active" />
|
||
<el-option label="禁用" value="disabled" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="dialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="submitForm">保存</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted } from "vue";
|
||
import { ElMessage, ElMessageBox } from "element-plus";
|
||
import { Plus, Refresh } from "@element-plus/icons-vue";
|
||
import {
|
||
getAllUsers,
|
||
addUser,
|
||
editUser,
|
||
deleteUser,
|
||
getUserInfo,
|
||
changePassword,
|
||
} from "@/api/user";
|
||
import { getRoleByTenantId } from "@/api/role";
|
||
import { useAuthStore } from "@/stores/auth";
|
||
|
||
interface User {
|
||
id: number;
|
||
username: string;
|
||
nickname: string;
|
||
email: string;
|
||
role: string;
|
||
status: string;
|
||
lastLogin: string;
|
||
tenant_id: number;
|
||
}
|
||
|
||
const authStore = useAuthStore();
|
||
|
||
const page = ref(1);
|
||
const pageSize = ref(10);
|
||
const total = ref(0);
|
||
|
||
const users = ref<any[]>([]);
|
||
const roleList = ref<any[]>([]);
|
||
const loadingRoles = ref(false);
|
||
const loading = ref(false);
|
||
|
||
// 获取当前登录用户的租户ID
|
||
const getCurrentTenantId = () => {
|
||
if (authStore.user && authStore.user.tenant_id) {
|
||
return authStore.user.tenant_id;
|
||
}
|
||
const userInfo = localStorage.getItem('userInfo');
|
||
if (userInfo) {
|
||
try {
|
||
const user = JSON.parse(userInfo);
|
||
return user.tenant_id || user.tenantId || 0;
|
||
} catch (e) {
|
||
console.error('Failed to parse user info:', e);
|
||
}
|
||
}
|
||
return 0;
|
||
};
|
||
|
||
// 获取角色列表
|
||
const fetchRoles = async () => {
|
||
loadingRoles.value = true;
|
||
try {
|
||
const tenantId = getCurrentTenantId();
|
||
const res = await getRoleByTenantId(tenantId);
|
||
if (res.code === 0 && res.data) {
|
||
roleList.value = Array.isArray(res.data) ? res.data : [];
|
||
} else {
|
||
roleList.value = [];
|
||
}
|
||
} catch (error: any) {
|
||
console.error('获取角色列表失败:', error);
|
||
roleList.value = [];
|
||
} finally {
|
||
loadingRoles.value = false;
|
||
}
|
||
};
|
||
|
||
// 获取角色标签类型
|
||
const getRoleTagType = (roleName: string) => {
|
||
if (!roleName) return 'info';
|
||
if (roleName.includes('管理员')) return 'danger';
|
||
if (roleName.includes('用户')) return 'primary';
|
||
return 'success';
|
||
};
|
||
|
||
//校验密码
|
||
const validatePassword = (password: string) => {
|
||
if (!password) {
|
||
return "请输入密码";
|
||
}
|
||
if (password.length < 6) {
|
||
return "密码长度不能小于6位";
|
||
}
|
||
if (password.length > 16) {
|
||
return "密码长度不能大于16位";
|
||
}
|
||
return true;
|
||
};
|
||
|
||
// 校验确认密码,需传递新密码和确认密码
|
||
const validateConfirmPassword = (password: string, confirmPassword: string) => {
|
||
if (!confirmPassword) {
|
||
return "请再次输入密码";
|
||
}
|
||
if (confirmPassword !== password) {
|
||
return "两次输入的密码不一致";
|
||
}
|
||
return true;
|
||
};
|
||
|
||
const fetchUsers = async () => {
|
||
loading.value = true;
|
||
try {
|
||
const res = await getAllUsers();
|
||
// 兼容接口返回的数据结构
|
||
let userList: any[] = [];
|
||
if (Array.isArray(res)) {
|
||
userList = res;
|
||
} else if (res?.data && Array.isArray(res.data)) {
|
||
userList = res.data;
|
||
} else if (res?.data?.data && Array.isArray(res.data.data)) {
|
||
userList = res.data.data;
|
||
} else if (res?.data) {
|
||
userList = res.data;
|
||
}
|
||
// 映射接口字段到表格所需结构
|
||
users.value = userList.map((item: any) => {
|
||
// 查找角色名称
|
||
let roleName = '';
|
||
let roleId = item.role_id || item.roleId || null;
|
||
if (roleId) {
|
||
const role = roleList.value.find(r => r.roleId === roleId);
|
||
roleName = role ? role.roleName : '';
|
||
} else if (item.role) {
|
||
// 兼容旧的role字段,尝试从roleList中查找
|
||
const role = roleList.value.find(r => r.roleCode === item.role);
|
||
roleName = role ? role.roleName : (item.role === 'admin' ? '管理员' : '普通用户');
|
||
}
|
||
|
||
return {
|
||
id: item.id,
|
||
username: item.username,
|
||
nickname: item.nickname,
|
||
email: item.email,
|
||
role: item.role || '',
|
||
roleId: roleId,
|
||
roleName: roleName,
|
||
status: item.status || "active",
|
||
lastLoginTime: item.lastLoginTime
|
||
? new Date(item.lastLoginTime).toLocaleString("zh-CN", {
|
||
year: "numeric",
|
||
month: "2-digit",
|
||
day: "2-digit",
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
second: "2-digit",
|
||
hour12: false,
|
||
})
|
||
: "",
|
||
};
|
||
});
|
||
total.value = users.value.length;
|
||
} catch (e) {
|
||
users.value = [];
|
||
total.value = 0;
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
onMounted(async () => {
|
||
// 先加载角色列表,再加载用户列表
|
||
await fetchRoles();
|
||
fetchUsers();
|
||
});
|
||
|
||
const handlePageChange = (p: number) => {
|
||
page.value = p;
|
||
// 分页仅前端做演示
|
||
};
|
||
|
||
// 为添加 / 编辑对话框而添加
|
||
const dialogVisible = ref(false);
|
||
const dialogTitle = ref("");
|
||
const isEdit = ref(false);
|
||
const form = ref<any>({
|
||
id: 0,
|
||
username: "",
|
||
nickname: "",
|
||
password: "",
|
||
email: "",
|
||
roleId: null,
|
||
role: "",
|
||
status: "active",
|
||
tenant_id: null,
|
||
});
|
||
const passwordForm = ref<any>({
|
||
oldPassword: "",
|
||
newPassword: "",
|
||
confirmPassword: "",
|
||
});
|
||
|
||
const passwordError = ref("");
|
||
|
||
// 根据对话框类型返回对应的表单数据
|
||
const currentForm = computed(() => {
|
||
return dialogTitle.value === '修改密码' ? passwordForm.value : form.value;
|
||
});
|
||
|
||
//刷新界面
|
||
const refresh = async () => {
|
||
loading.value = true;
|
||
try {
|
||
await fetchRoles();
|
||
await fetchUsers();
|
||
ElMessage.success('刷新成功');
|
||
} catch (error) {
|
||
ElMessage.error('刷新失败');
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
const handleAddUser = () => {
|
||
dialogTitle.value = "添加用户";
|
||
isEdit.value = false;
|
||
|
||
// 从本地缓存读取tenant_id
|
||
let tenantId = null;
|
||
const cachedUser = localStorage.getItem("userInfo");
|
||
if (cachedUser) {
|
||
try {
|
||
const userInfo = JSON.parse(cachedUser);
|
||
tenantId = userInfo.tenant_id || null;
|
||
} catch (e) {
|
||
tenantId = null;
|
||
}
|
||
}
|
||
|
||
form.value = {
|
||
id: 0,
|
||
username: "",
|
||
nickname: "",
|
||
password: "",
|
||
email: "",
|
||
roleId: null,
|
||
role: "",
|
||
status: "active",
|
||
tenant_id: tenantId,
|
||
};
|
||
dialogVisible.value = true;
|
||
};
|
||
|
||
const handleEdit = async (user: User) => {
|
||
dialogTitle.value = "编辑用户";
|
||
isEdit.value = true;
|
||
try {
|
||
const res = await getUserInfo(user.id);
|
||
const data = res.data || res;
|
||
|
||
// 获取当前租户ID
|
||
const tenantId = getCurrentTenantId();
|
||
|
||
// 处理角色ID,兼容旧数据
|
||
let roleId = data.role_id || data.roleId || null;
|
||
if (!roleId && data.role) {
|
||
// 如果只有role代码,尝试从roleList中查找对应的roleId
|
||
const role = roleList.value.find(r => r.roleCode === data.role);
|
||
roleId = role ? role.roleId : null;
|
||
}
|
||
|
||
form.value = {
|
||
id: data.id,
|
||
username: data.username,
|
||
nickname: data.nickname,
|
||
password: "",
|
||
email: data.email,
|
||
roleId: roleId,
|
||
role: data.role || "",
|
||
status: data.status || "active",
|
||
tenant_id: data.tenant_id || tenantId,
|
||
};
|
||
} catch (e) {
|
||
ElMessage.error("加载用户失败");
|
||
return;
|
||
}
|
||
dialogVisible.value = true;
|
||
};
|
||
|
||
const submitForm = async () => {
|
||
// 清除之前的错误
|
||
passwordError.value = "";
|
||
|
||
try {
|
||
if (dialogTitle.value === "修改密码") {
|
||
// 校验旧密码
|
||
if (!passwordForm.value.oldPassword) {
|
||
passwordError.value = "请输入旧密码";
|
||
return;
|
||
}
|
||
|
||
// 校验密码格式
|
||
const passwordCheck = validatePassword(passwordForm.value.newPassword);
|
||
if (passwordCheck !== true) {
|
||
passwordError.value = passwordCheck;
|
||
return;
|
||
}
|
||
|
||
// 校验确认密码
|
||
const confirmCheck = validateConfirmPassword(
|
||
passwordForm.value.newPassword,
|
||
passwordForm.value.confirmPassword
|
||
);
|
||
if (confirmCheck !== true) {
|
||
passwordError.value = confirmCheck;
|
||
return;
|
||
}
|
||
|
||
const res = await changePassword(form.value.id, passwordForm.value);
|
||
|
||
// 检查返回结果
|
||
if (res.code === 0) {
|
||
// 先显示成功消息
|
||
ElMessage.success({
|
||
message: "密码修改成功",
|
||
type: "success",
|
||
customClass: "my-el-message-success",
|
||
showClose: true,
|
||
center: true,
|
||
offset: 60,
|
||
});
|
||
// 延迟关闭弹窗,确保消息已经渲染
|
||
setTimeout(() => {
|
||
dialogVisible.value = false;
|
||
// 重置密码表单
|
||
passwordForm.value = {
|
||
oldPassword: "",
|
||
newPassword: "",
|
||
confirmPassword: "",
|
||
};
|
||
}, 100);
|
||
} else {
|
||
passwordError.value = res.message || "密码修改失败";
|
||
}
|
||
} else if (isEdit.value) {
|
||
// 构建提交数据,优先使用roleId
|
||
const submitData: any = {
|
||
username: form.value.username,
|
||
nickname: form.value.nickname,
|
||
email: form.value.email,
|
||
status: form.value.status,
|
||
};
|
||
|
||
if (form.value.roleId) {
|
||
submitData.roleId = form.value.roleId;
|
||
} else if (form.value.role) {
|
||
// 兼容旧数据,如果没有roleId但有role代码,也传递
|
||
submitData.role = form.value.role;
|
||
}
|
||
|
||
if (form.value.tenant_id) {
|
||
submitData.tenant_id = form.value.tenant_id;
|
||
}
|
||
|
||
await editUser(form.value.id, submitData);
|
||
ElMessage.success({
|
||
message: "更新成功",
|
||
type: "success",
|
||
customClass: "my-el-message-success",
|
||
showClose: true,
|
||
center: true,
|
||
offset: 60,
|
||
});
|
||
dialogVisible.value = false;
|
||
fetchUsers();
|
||
} else {
|
||
// 构建提交数据,优先使用roleId
|
||
const submitData: any = {
|
||
username: form.value.username,
|
||
nickname: form.value.nickname,
|
||
password: form.value.password,
|
||
email: form.value.email,
|
||
status: form.value.status,
|
||
};
|
||
|
||
if (form.value.roleId) {
|
||
submitData.roleId = form.value.roleId;
|
||
} else if (form.value.role) {
|
||
submitData.role = form.value.role;
|
||
}
|
||
|
||
if (form.value.tenant_id) {
|
||
submitData.tenant_id = form.value.tenant_id;
|
||
}
|
||
|
||
await addUser(submitData);
|
||
ElMessage.success({
|
||
message: "添加成功",
|
||
type: "success",
|
||
customClass: "my-el-message-success",
|
||
showClose: true,
|
||
center: true,
|
||
offset: 60,
|
||
});
|
||
dialogVisible.value = false;
|
||
fetchUsers();
|
||
}
|
||
} catch (e: any) {
|
||
// 显示具体的错误信息
|
||
const errorMsg = e?.response?.data?.message || e?.message || "操作失败";
|
||
if (dialogTitle.value === "修改密码") {
|
||
passwordError.value = errorMsg;
|
||
} else {
|
||
ElMessage.error(errorMsg);
|
||
}
|
||
}
|
||
};
|
||
|
||
const handleDelete = async (user: User) => {
|
||
ElMessageBox.confirm("确认删除该用户?", "提示", {
|
||
confirmButtonText: "确定",
|
||
cancelButtonText: "取消",
|
||
type: "warning",
|
||
}).then(async () => {
|
||
try {
|
||
await deleteUser(user.id);
|
||
ElMessage.success("删除成功");
|
||
fetchUsers();
|
||
} catch (e) {
|
||
ElMessage.error("删除失败");
|
||
}
|
||
});
|
||
};
|
||
|
||
//修改密码
|
||
const handleChangePassword = async (user: User) => {
|
||
dialogTitle.value = "修改密码";
|
||
isEdit.value = true;
|
||
passwordError.value = "";
|
||
form.value = {
|
||
id: user.id,
|
||
username: user.username,
|
||
};
|
||
passwordForm.value = {
|
||
oldPassword: "",
|
||
newPassword: "",
|
||
confirmPassword: "",
|
||
};
|
||
dialogVisible.value = true;
|
||
};
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
span {
|
||
font-size: 1.4rem;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
|
||
:deep(.el-alert__title) {
|
||
color: #f56c6c !important;
|
||
}
|
||
</style>
|