yunzer_go/pc/src/views/system/users/index.vue

815 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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="department" label="部门" width="150" align="center">
<template #default="scope">
<span>{{ scope.row.departmentName || '未分配' }}</span>
</template>
</el-table-column>
<el-table-column prop="position" label="职位" width="150" align="center">
<template #default="scope">
<span>{{ scope.row.positionName || '未分配' }}</span>
</template>
</el-table-column>
<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 === 1 ? 'success' : 'danger'">
{{ scope.row.status === 1 ? "启用" : "禁用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="lastLoginTime"
label="最后登录"
width="180"
align="center"
/>
<el-table-column
prop="lastLoginIp"
label="最后登录IP"
width="150"
align="center"
>
<template #default="scope">
<span>{{ scope.row.lastLoginIp || '未知' }}</span>
</template>
</el-table-column>
<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
v-if="scope.row.username !== 'admin'"
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.department_id"
placeholder="请选择部门"
style="width: 100%"
:loading="loadingDepartments"
clearable
@change="handleDepartmentChange"
>
<el-option
v-for="dept in departmentList"
:key="dept.id"
:label="dept.name"
:value="dept.id"
/>
</el-select>
</el-form-item>
<el-form-item label="职位" v-if="dialogTitle !== '修改密码'">
<el-select
v-model="form.position_id"
placeholder="请选择职位"
style="width: 100%"
:loading="loadingPositions"
clearable
>
<el-option
v-for="pos in positionList"
:key="pos.id"
:label="pos.name"
:value="pos.id"
/>
</el-select>
</el-form-item>
<el-form-item label="角色" v-if="dialogTitle !== '修改密码'">
<el-select
v-model="form.role"
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,
getTenantUsers,
addUser,
editUser,
deleteUser,
getUserInfo,
changePassword,
} from "@/api/user";
import { getRoleByTenantId } from "@/api/role";
import { getTenantDepartments } from "@/api/department";
import { getTenantPositions, getPositionsByDepartment } from "@/api/position";
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 departmentList = ref<any[]>([]);
const loadingDepartments = ref(false);
const positionList = ref<any[]>([]);
const loadingPositions = 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 fetchDepartments = async () => {
loadingDepartments.value = true;
try {
const tenantId = getCurrentTenantId();
const res = await getTenantDepartments(tenantId);
if (res.code === 0 && res.data) {
departmentList.value = Array.isArray(res.data) ? res.data : [];
} else {
departmentList.value = [];
}
} catch (error: any) {
console.error('获取部门列表失败:', error);
departmentList.value = [];
} finally {
loadingDepartments.value = false;
}
};
// 获取职位列表
const fetchPositions = async (departmentId?: number) => {
loadingPositions.value = true;
try {
const tenantId = getCurrentTenantId();
let res;
if (departmentId && departmentId > 0) {
// 根据部门获取职位
res = await getPositionsByDepartment(departmentId);
} else {
// 获取所有职位
res = await getTenantPositions(tenantId);
}
if (res.code === 0 && res.data) {
positionList.value = Array.isArray(res.data) ? res.data : [];
} else {
positionList.value = [];
}
} catch (error: any) {
console.error('获取职位列表失败:', error);
positionList.value = [];
} finally {
loadingPositions.value = false;
}
};
// 部门改变时,重新加载职位列表
const handleDepartmentChange = (departmentId: number | null) => {
form.value.position_id = null; // 清空职位选择
if (departmentId && departmentId > 0) {
fetchPositions(departmentId);
} else {
fetchPositions(); // 加载所有职位
}
};
// 获取角色标签类型
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;
// 传tenantid给接口
let tenantId = getCurrentTenantId ? getCurrentTenantId() : null;
try {
const res = await getTenantUsers(tenantId);
// 兼容接口返回的数据结构
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 roleValue = item.role || null; // role 字段存储的是角色ID数字
if (roleValue) {
// 通过角色ID查找角色信息
const roleInfo = roleList.value.find(r => r.roleId === roleValue);
roleName = roleInfo ? roleInfo.roleName : '';
}
// 查找部门名称
let departmentName = '';
const departmentId = item.department_id || null;
if (departmentId) {
const deptInfo = departmentList.value.find(d => d.id === departmentId);
departmentName = deptInfo ? deptInfo.name : '';
}
// 查找职位名称
let positionName = '';
const positionId = item.position_id || null;
if (positionId) {
const posInfo = positionList.value.find(p => p.id === positionId);
positionName = posInfo ? posInfo.name : '';
}
// 处理最后登录时间,后端返回的是 last_login_time
const lastLoginTime = item.last_login_time || item.lastLoginTime || null;
// 处理最后登录IP后端返回的是 last_login_ip
const lastLoginIp = item.last_login_ip || item.lastLoginIp || null;
return {
id: item.id,
username: item.username,
nickname: item.nickname,
email: item.email,
role: roleValue,
roleName: roleName,
department_id: departmentId,
departmentName: departmentName,
position_id: positionId,
positionName: positionName,
status: item.status,
lastLoginTime: lastLoginTime
? new Date(lastLoginTime).toLocaleString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
})
: "从未登录",
lastLoginIp: lastLoginIp || null,
};
});
total.value = users.value.length;
} catch (e) {
users.value = [];
total.value = 0;
} finally {
loading.value = false;
}
};
onMounted(async () => {
// 先加载角色、部门、职位列表,再加载用户列表
await Promise.all([
fetchRoles(),
fetchDepartments(),
fetchPositions(),
]);
fetchUsers();
});
const handlePageChange = (p: number) => {
page.value = p;
// 分页仅前端做演示
};
// 为添加 / 编辑对话框而添加
const dialogVisible = ref(false);
const dialogTitle = ref("");
const isEdit = ref(false);
const form = ref<any>({
id: null,
username: "",
nickname: "",
password: "",
email: "",
role: null, // role 字段存储角色ID
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 Promise.all([
fetchRoles(),
fetchDepartments(),
fetchPositions(),
]);
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, // 添加用户时ID为0
username: "",
nickname: "",
password: "",
email: "",
role: null, // role 字段存储角色ID
department_id: null,
position_id: null,
status: "active",
tenant_id: tenantId,
};
// 重新加载职位列表(所有职位)
fetchPositions();
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();
// 处理角色data.role 存储的是角色ID数字
let roleValue = data.role || null;
// 处理状态后端返回的是数字0或1前端使用字符串"active"或"inactive"
let statusStr = "active";
if (typeof data.status === 'number') {
statusStr = data.status === 1 ? "active" : "inactive";
} else if (typeof data.status === 'string') {
statusStr = data.status;
}
form.value = {
id: data.id,
username: data.username,
nickname: data.nickname,
password: "",
email: data.email,
role: roleValue, // role 字段存储角色ID
department_id: data.department_id || null,
position_id: data.position_id || null,
status: statusStr,
tenant_id: data.tenant_id || tenantId,
};
// 如果用户有部门,根据部门加载职位列表
if (form.value.department_id) {
fetchPositions(form.value.department_id);
} else {
fetchPositions(); // 加载所有职位
}
} 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) {
// 检查ID是否存在
if (!form.value.id || form.value.id === 0) {
ElMessage.error("用户ID不能为空请重新选择用户");
return;
}
// 构建提交数据
const submitData: any = {
id: form.value.id,
username: form.value.username,
nickname: form.value.nickname,
email: form.value.email,
status: form.value.status,
};
// role 字段存储角色ID
if (form.value.role) {
submitData.role = form.value.role; // 后端接收 role 参数
}
// 部门和职位ID
if (form.value.department_id) {
submitData.department_id = form.value.department_id;
}
if (form.value.position_id) {
submitData.position_id = form.value.position_id;
}
if (form.value.tenant_id) {
submitData.tenant_id = form.value.tenant_id;
}
console.log('编辑用户提交数据:', submitData); // 调试日志
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 {
// 构建提交数据
const submitData: any = {
username: form.value.username,
nickname: form.value.nickname,
password: form.value.password,
email: form.value.email,
status: form.value.status,
};
// role 字段存储角色ID
if (form.value.role) {
submitData.role = form.value.role; // 后端接收 role 参数
}
// 部门和职位ID
if (form.value.department_id) {
submitData.department_id = form.value.department_id;
}
if (form.value.position_id) {
submitData.position_id = form.value.position_id;
}
if (form.value.tenant_id) {
submitData.tenant_id = form.value.tenant_id;
}
console.log('添加用户提交数据:', submitData); // 调试日志
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>