批量修复,增加组织架构

This commit is contained in:
2025-11-04 17:35:01 +08:00
parent 86c4060843
commit a7bde8bb72
29 changed files with 5200 additions and 88 deletions
+44
View File
@@ -0,0 +1,44 @@
import request from '@/utils/request';
// 获取租户下的所有部门
export function getTenantDepartments(tenantId) {
return request({
url: `/api/departments/tenant/${tenantId}`,
method: 'get',
});
}
// 获取部门详情
export function getDepartmentInfo(departmentId) {
return request({
url: `/api/departments/${departmentId}`,
method: 'get',
});
}
// 添加部门
export function addDepartment(data) {
return request({
url: '/api/departments',
method: 'post',
data,
});
}
// 更新部门信息
export function editDepartment(departmentId, data) {
return request({
url: `/api/departments/${departmentId}`,
method: 'put',
data,
});
}
// 删除部门
export function deleteDepartment(departmentId) {
return request({
url: `/api/departments/${departmentId}`,
method: 'delete',
});
}
+69
View File
@@ -0,0 +1,69 @@
import request from '@/utils/request';
// 获取所有员工信息
export function getAllEmployees() {
return request({
url: '/api/employees',
method: 'get',
});
}
// 获取员工信息
export function getEmployeeInfo(employeeId) {
return request({
url: `/api/employees/${employeeId}`,
method: 'get',
});
}
// 获取租户员工信息
export function getTenantEmployees(tenantId) {
return request({
url: `/api/employees/tenant/${tenantId}`,
method: 'get',
});
}
// 添加员工
export function addEmployee(data) {
return request({
url: '/api/employees',
method: 'post',
data,
});
}
// 更新员工信息
export function editEmployee(employeeId, data) {
return request({
url: `/api/employees/${employeeId}`,
method: 'put',
data,
});
}
// 删除员工
export function deleteEmployee(employeeId) {
return request({
url: `/api/employees/${employeeId}`,
method: 'delete',
});
}
// 重置员工密码
export function resetEmployeePassword(employeeId) {
return request({
url: `/api/employees/${employeeId}/reset-password`,
method: 'post',
});
}
// 修改员工密码
export function changeEmployeePassword(employeeId, data) {
return request({
url: `/api/employees/${employeeId}/change-password`,
method: 'post',
data,
});
}
+52
View File
@@ -0,0 +1,52 @@
import request from '@/utils/request';
// 获取租户下的所有职位
export function getTenantPositions(tenantId) {
return request({
url: `/api/positions/tenant/${tenantId}`,
method: 'get',
});
}
// 根据部门ID获取职位列表
export function getPositionsByDepartment(departmentId) {
return request({
url: `/api/positions/department/${departmentId}`,
method: 'get',
});
}
// 获取职位详情
export function getPositionInfo(positionId) {
return request({
url: `/api/positions/${positionId}`,
method: 'get',
});
}
// 添加职位
export function addPosition(data) {
return request({
url: '/api/positions',
method: 'post',
data,
});
}
// 更新职位信息
export function editPosition(positionId, data) {
return request({
url: `/api/positions/${positionId}`,
method: 'put',
data,
});
}
// 删除职位
export function deletePosition(positionId) {
return request({
url: `/api/positions/${positionId}`,
method: 'delete',
});
}
+36
View File
@@ -38,6 +38,7 @@
<el-dropdown trigger="click" @command="handleCommand">
<span class="el-dropdown-link" style="cursor: pointer;">
<img :src="getImageUrl('user')" class="user" />
<span class="user-name">{{ displayName }}</span>
</span>
<template #dropdown>
<el-dropdown-menu>
@@ -207,6 +208,25 @@ const getImageUrl = (user) => {
return new URL(`/src/assets/images/default_avatar.png`, import.meta.url).href;
};
// 计算显示名称:优先显示昵称(用户)或姓名(员工),否则显示用户名
const displayName = computed(() => {
const user = authStore.user;
if (!user) return '';
// 如果是员工登录,优先显示name
if (user.type === 'employee' && user.name) {
return user.name;
}
// 如果是用户登录,优先显示nickname
if (user.nickname) {
return user.nickname;
}
// 最后显示username
return user.username || '';
});
const handleCollapse = () => {
store.state.isCollapse = !store.state.isCollapse;
};
@@ -390,6 +410,22 @@ onUnmounted(() => {
display: flex;
align-items: center;
cursor: pointer;
gap: 12px;
.user-name {
font-size: 14px;
color: var(--el-text-color-primary);
font-weight: 500;
white-space: nowrap;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
// 亮色主题下使用白色
html:not(.dark) & {
color: #ffffff;
}
}
}
}
+372
View File
@@ -0,0 +1,372 @@
<template>
<div class="container-box">
<div class="header-bar">
<h2>部门管理</h2>
<div class="header-actions">
<el-button type="primary" @click="handleAddDepartment">
<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="departments" style="width: 100%" v-loading="loading">
<el-table-column
prop="name"
label="部门名称"
width="200"
align="center"
/>
<el-table-column
prop="code"
label="部门编码"
width="150"
align="center"
/>
<el-table-column
prop="description"
label="部门描述"
align="center"
min-width="200"
/>
<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="sortOrder"
label="排序"
width="100"
align="center"
/>
<el-table-column
prop="createTime"
label="创建时间"
width="180"
align="center"
/>
<el-table-column label="操作" width="180" align="center" fixed="right">
<template #default="scope">
<el-button size="small" @click="handleEdit(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="form" label-width="80px">
<el-form-item label="部门名称">
<el-input v-model="form.name" placeholder="请输入部门名称" />
</el-form-item>
<el-form-item label="部门编码">
<el-input v-model="form.code" placeholder="请输入部门编码" />
</el-form-item>
<el-form-item label="部门描述">
<el-input
v-model="form.description"
type="textarea"
:rows="3"
placeholder="请输入部门描述"
/>
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="form.sort_order" :min="0" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="form.status" placeholder="请选择状态">
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</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, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Refresh } from "@element-plus/icons-vue";
import {
getTenantDepartments,
addDepartment,
editDepartment,
deleteDepartment,
getDepartmentInfo,
} from "@/api/department";
import { useAuthStore } from "@/stores/auth";
interface Department {
id: number;
name: string;
code: string;
description: string;
status: number;
sort_order: number;
tenant_id: number;
}
const authStore = useAuthStore();
const page = ref(1);
const pageSize = ref(10);
const total = ref(0);
const departments = ref<any[]>([]);
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 fetchDepartments = async () => {
loading.value = true;
let tenantId = getCurrentTenantId ? getCurrentTenantId() : null;
try {
const res = await getTenantDepartments(tenantId);
let deptList: any[] = [];
if (Array.isArray(res)) {
deptList = res;
} else if (res?.data && Array.isArray(res.data)) {
deptList = res.data;
} else if (res?.data?.data && Array.isArray(res.data.data)) {
deptList = res.data.data;
} else if (res?.data) {
deptList = res.data;
}
departments.value = deptList.map((item: any) => {
const createTime = item.create_time || item.createTime || null;
return {
id: item.id,
name: item.name || '',
code: item.code || '',
description: item.description || '',
status: item.status || 1,
sort_order: item.sort_order || item.sortOrder || 0,
createTime: createTime
? new Date(createTime).toLocaleString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
})
: "",
};
});
total.value = departments.value.length;
} catch (e) {
departments.value = [];
total.value = 0;
ElMessage.error("获取部门列表失败");
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchDepartments();
});
const handlePageChange = (p: number) => {
page.value = p;
};
const dialogVisible = ref(false);
const dialogTitle = ref("");
const isEdit = ref(false);
const form = ref<any>({
id: null,
name: "",
code: "",
description: "",
sort_order: 0,
status: 1,
tenant_id: null,
});
const refresh = async () => {
loading.value = true;
try {
await fetchDepartments();
ElMessage.success('刷新成功');
} catch (error) {
ElMessage.error('刷新失败');
} finally {
loading.value = false;
}
};
const handleAddDepartment = () => {
dialogTitle.value = "添加部门";
isEdit.value = false;
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,
name: "",
code: "",
description: "",
sort_order: 0,
status: 1,
tenant_id: tenantId,
};
dialogVisible.value = true;
};
const handleEdit = async (department: Department) => {
dialogTitle.value = "编辑部门";
isEdit.value = true;
try {
const res = await getDepartmentInfo(department.id);
const data = res.data || res;
const tenantId = getCurrentTenantId();
form.value = {
id: data.id,
name: data.name || '',
code: data.code || '',
description: data.description || '',
sort_order: data.sort_order || data.sortOrder || 0,
status: data.status || 1,
tenant_id: data.tenant_id || tenantId,
};
} catch (e) {
ElMessage.error("加载部门信息失败");
return;
}
dialogVisible.value = true;
};
const submitForm = async () => {
try {
if (isEdit.value) {
if (!form.value.id || form.value.id === 0) {
ElMessage.error("部门ID不能为空,请重新选择部门");
return;
}
const submitData: any = {
name: form.value.name,
code: form.value.code,
description: form.value.description,
sort_order: form.value.sort_order,
status: form.value.status,
};
await editDepartment(form.value.id, submitData);
ElMessage.success("更新成功");
dialogVisible.value = false;
fetchDepartments();
} else {
const submitData: any = {
name: form.value.name,
code: form.value.code,
description: form.value.description,
sort_order: form.value.sort_order,
status: form.value.status,
};
if (form.value.tenant_id) {
submitData.tenant_id = form.value.tenant_id;
}
await addDepartment(submitData);
ElMessage.success("添加成功");
dialogVisible.value = false;
fetchDepartments();
}
} catch (e: any) {
const errorMsg = e?.response?.data?.message || e?.message || "操作失败";
ElMessage.error(errorMsg);
}
};
const handleDelete = async (department: Department) => {
ElMessageBox.confirm("确认删除该部门?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
await deleteDepartment(department.id);
ElMessage.success("删除成功");
fetchDepartments();
} catch (e) {
ElMessage.error("删除失败");
}
});
};
</script>
<style lang="less" scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-size: 1.4rem;
font-weight: 700;
}
}
</style>
+960 -3
View File
@@ -1,7 +1,964 @@
<template>
<div>员工管理</div>
<div class="container-box">
<div class="header-bar">
<h2>员工管理</h2>
<div class="header-actions">
<el-button type="primary" @click="handleAddEmployee">
<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="employees" style="width: 100%" v-loading="loading">
<el-table-column
prop="employeeNo"
label="工号"
width="120"
align="center"
/>
<el-table-column
prop="name"
label="姓名"
width="120"
align="center"
/>
<el-table-column
prop="phone"
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="bankName" label="开户行" width="150" align="center">
<template #default="scope">
<span>{{ scope.row.bankName || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="bankAccount" label="卡号" width="180" align="center">
<template #default="scope">
<span>{{ scope.row.bankAccount || '-' }}</span>
</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="createTime"
label="入职时间"
width="180"
align="center"
/>
<el-table-column label="操作" width="280" align="center" fixed="right">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)"
>编辑</el-button
>
<el-button
size="small"
type="warning"
@click="handleResetPassword(scope.row)"
>重置密码</el-button
>
<el-button
size="small"
type="info"
@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="form" label-width="80px">
<el-form-item label="工号">
<el-input v-model="form.employeeNo" placeholder="请输入工号" />
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="form.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="手机号">
<el-input v-model="form.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="form.email" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="部门">
<el-tree-select
v-model="form.department_id"
:data="departmentTree"
:props="{ label: 'name', children: 'children' }"
value-key="id"
placeholder="请选择部门"
check-strictly
clearable
style="width: 100%"
:loading="loadingDepartments"
@change="handleDepartmentChange"
:render-after-expand="false"
/>
<div v-if="departmentTree.length === 0 && !loadingDepartments" style="color: #999; font-size: 12px; margin-top: 4px;">
暂无部门数据请先创建部门
</div>
</el-form-item>
<el-form-item label="职位">
<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="开户行">
<el-input v-model="form.bank_name" placeholder="请输入工资卡开户行" />
</el-form-item>
<el-form-item label="卡号">
<el-input v-model="form.bank_account" placeholder="请输入工资卡卡号" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="form.status" placeholder="请选择状态">
<el-option label="在职" :value="1" />
<el-option label="离职" :value="0" />
</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>
<!-- 修改密码对话框 -->
<el-dialog v-model="passwordDialogVisible" title="修改密码" width="400px">
<el-form :model="passwordForm" label-width="100px">
<el-form-item label="旧密码">
<el-input
v-model="passwordForm.old_password"
type="password"
placeholder="请输入旧密码"
show-password
/>
</el-form-item>
<el-form-item label="新密码">
<el-input
v-model="passwordForm.new_password"
type="password"
placeholder="请输入新密码"
show-password
/>
</el-form-item>
<el-form-item label="确认密码">
<el-input
v-model="passwordForm.confirm_password"
type="password"
placeholder="请再次输入新密码"
show-password
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="passwordDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitPasswordForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup></script>
<script setup lang="ts">
import { ref, onMounted, nextTick } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Refresh } from "@element-plus/icons-vue";
import {
getAllEmployees,
getTenantEmployees,
addEmployee,
editEmployee,
deleteEmployee,
getEmployeeInfo,
resetEmployeePassword,
changeEmployeePassword,
} from "@/api/employee";
import { getTenantDepartments, getDepartmentInfo } from "@/api/department";
import { getTenantPositions, getPositionsByDepartment, getPositionInfo } from "@/api/position";
import { useAuthStore } from "@/stores/auth";
<style lang="scss" scoped></style>
interface Employee {
id: number;
employeeNo: string;
name: string;
phone: string;
email: string;
department: string;
position: string;
status: number;
createTime: string;
tenant_id: number;
}
const authStore = useAuthStore();
const page = ref(1);
const pageSize = ref(10);
const total = ref(0);
const employees = ref<any[]>([]);
const departmentList = ref<any[]>([]);
const departmentTree = 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 fetchEmployees = async () => {
loading.value = true;
let tenantId = getCurrentTenantId ? getCurrentTenantId() : null;
try {
const res = await getTenantEmployees(tenantId);
// 兼容接口返回的数据结构
let employeeList: any[] = [];
if (Array.isArray(res)) {
employeeList = res;
} else if (res?.data && Array.isArray(res.data)) {
employeeList = res.data;
} else if (res?.data?.data && Array.isArray(res.data.data)) {
employeeList = res.data.data;
} else if (res?.data) {
employeeList = res.data;
}
// 映射接口字段到表格所需结构
employees.value = employeeList.map((item: any) => {
// 查找部门名称
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 : '';
}
// 处理入职时间
const createTime = item.create_time || item.createTime || null;
return {
id: item.id,
employeeNo: item.employee_no || item.employeeNo || '',
name: item.name || '',
phone: item.phone || '',
email: item.email || '',
department_id: departmentId,
departmentName: departmentName,
position_id: positionId,
positionName: positionName,
bankName: item.bank_name || item.bankName || '',
bankAccount: item.bank_account || item.bankAccount || '',
status: item.status || 1,
createTime: createTime
? new Date(createTime).toLocaleString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
})
: "",
};
});
total.value = employees.value.length;
} catch (e) {
employees.value = [];
total.value = 0;
ElMessage.error("获取员工列表失败");
} finally {
loading.value = false;
}
};
// 构建部门树(参考组织架构页面的实现)
const buildDepartmentTree = (deptList: any[], tenantId?: number) => {
if (!deptList || deptList.length === 0) {
console.warn('部门列表为空');
return [];
}
// 先按tenant_id过滤(双重保险,确保只显示当前租户的部门)
let filteredList = deptList;
if (tenantId !== undefined && tenantId !== null) {
filteredList = deptList.filter((dept) => {
const deptTenantId = dept.tenant_id || dept.tenantId;
return deptTenantId === tenantId || deptTenantId === 0;
});
}
const tree: any[] = [];
const map = new Map<number, any>();
// 第一遍:创建所有节点的映射
filteredList.forEach((dept) => {
if (!dept.id) {
console.warn('部门数据缺少id字段:', dept);
return;
}
map.set(dept.id, {
...dept,
children: [],
});
});
// 第二遍:构建树结构
// 先处理parent_id为0或null的节点(作为根节点)
filteredList.forEach((dept) => {
if (!dept.id) return;
const node = map.get(dept.id);
if (!node) return;
const parentId = dept.parent_id || 0;
// 先判断parent_id,如果为0或null,直接作为根节点
if (parentId === 0 || parentId === null || parentId === undefined) {
tree.push(node);
} else {
// 查找父节点,如果父节点存在且在当前租户下,则作为子节点
const parent = map.get(parentId);
if (parent) {
parent.children.push(node);
} else {
// 如果找不到父节点(可能是其他租户的部门),作为根节点显示
tree.push(node);
}
}
});
// 排序
const sortTree = (nodes: any[]) => {
nodes.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0));
nodes.forEach((node) => {
if (node.children && node.children.length > 0) {
sortTree(node.children);
}
});
};
sortTree(tree);
return tree;
};
// 获取部门列表
const fetchDepartments = async () => {
loadingDepartments.value = true;
try {
const tenantId = getCurrentTenantId();
const res = await getTenantDepartments(tenantId);
let deptList: any[] = [];
if (Array.isArray(res)) {
deptList = res;
} else if (res?.data && Array.isArray(res.data)) {
deptList = res.data;
} else if (res?.data?.data && Array.isArray(res.data.data)) {
deptList = res.data.data;
} else if (res?.data) {
deptList = res.data;
}
departmentList.value = deptList;
// 构建部门树时传入tenant_id,确保先按租户过滤,再处理parent_id=0的情况
const tree = buildDepartmentTree(deptList, tenantId);
departmentTree.value = tree;
// 详细调试日志
console.log('=== 部门数据加载 ===');
console.log('租户ID:', tenantId);
console.log('部门原始数据:', JSON.stringify(deptList, null, 2));
console.log('构建的部门树:', JSON.stringify(tree, null, 2));
console.log('部门树长度:', tree.length);
if (tree.length > 0) {
console.log('第一个部门节点:', tree[0]);
console.log('第一个部门节点的结构:', {
id: tree[0].id,
name: tree[0].name,
children: tree[0].children,
allKeys: Object.keys(tree[0])
});
}
} catch (error: any) {
console.error('获取部门列表失败:', error);
departmentList.value = [];
departmentTree.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);
}
let posList: any[] = [];
if (Array.isArray(res)) {
posList = res;
} else if (res?.data && Array.isArray(res.data)) {
posList = res.data;
} else if (res?.data?.data && Array.isArray(res.data.data)) {
posList = res.data.data;
} else if (res?.data) {
posList = res.data;
}
// 去重:确保职位列表中没有重复的ID
const uniquePositions = new Map<number, any>();
posList.forEach((pos: any) => {
const posId = Number(pos.id);
if (posId && !uniquePositions.has(posId)) {
uniquePositions.set(posId, pos);
}
});
positionList.value = Array.from(uniquePositions.values());
} 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(); // 加载所有职位
}
};
onMounted(async () => {
await Promise.all([
fetchDepartments(),
fetchPositions(),
]);
fetchEmployees();
});
const handlePageChange = (p: number) => {
page.value = p;
// 分页仅前端做演示
};
// 为添加 / 编辑对话框而添加
const dialogVisible = ref(false);
const dialogTitle = ref("");
const isEdit = ref(false);
const form = ref<any>({
id: null,
employeeNo: "",
name: "",
phone: "",
email: "",
department_id: null,
position_id: null,
bank_name: "",
bank_account: "",
status: 1,
tenant_id: null,
});
// 修改密码对话框
const passwordDialogVisible = ref(false);
const currentEmployeeId = ref<number | null>(null);
const passwordForm = ref<any>({
old_password: "",
new_password: "",
confirm_password: "",
});
//刷新界面
const refresh = async () => {
loading.value = true;
try {
await fetchEmployees();
ElMessage.success('刷新成功');
} catch (error) {
ElMessage.error('刷新失败');
} finally {
loading.value = false;
}
};
const handleAddEmployee = () => {
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,
employeeNo: "",
name: "",
phone: "",
email: "",
department_id: null,
position_id: null,
bank_name: "",
bank_account: "",
status: 1,
tenant_id: tenantId,
};
// 重新加载职位列表(所有职位)
fetchPositions();
dialogVisible.value = true;
};
const handleEdit = async (employee: Employee) => {
dialogTitle.value = "编辑员工";
isEdit.value = true;
try {
// 获取当前租户ID
const tenantId = getCurrentTenantId();
// 强制重新获取部门列表(确保使用最新的部门数据)
await fetchDepartments();
// 先获取员工信息
const res = await getEmployeeInfo(employee.id);
const data = res.data || res;
// 确保department_id是数字类型,与树中的id类型匹配
const departmentId = data.department_id ? Number(data.department_id) : null;
form.value = {
id: data.id,
employeeNo: data.employee_no || data.employeeNo || '',
name: data.name || '',
phone: data.phone || '',
email: data.email || '',
department_id: departmentId,
position_id: data.position_id ? Number(data.position_id) : null,
bank_name: data.bank_name || data.bankName || '',
bank_account: data.bank_account || data.bankAccount || '',
status: data.status || 1,
tenant_id: data.tenant_id || tenantId,
};
// 验证员工的部门ID是否在当前租户的部门树中存在
console.log('=== 编辑员工数据 ===');
console.log('员工数据:', data);
console.log('员工department_id:', data.department_id, typeof data.department_id);
console.log('当前部门树:', departmentTree.value);
console.log('form.department_id (设置前):', form.value.department_id);
if (form.value.department_id) {
// 检查部门是否在部门树中(递归查找)
const findDepartmentInTree = (tree: any[], deptId: number): any => {
for (const node of tree) {
// 确保id类型匹配
const nodeId = Number(node.id);
console.log(`比较节点: nodeId=${nodeId}, deptId=${deptId}, 匹配=${nodeId === deptId}`);
if (nodeId === deptId) {
return node;
}
if (node.children && node.children.length > 0) {
const found = findDepartmentInTree(node.children, deptId);
if (found) {
return found;
}
}
}
return null;
};
const deptId = Number(form.value.department_id);
const foundNode = findDepartmentInTree(departmentTree.value, deptId);
if (!foundNode) {
console.warn(`⚠️ 员工的部门ID ${deptId} 不在当前租户的部门树中,尝试通过API获取部门信息`);
// 如果部门不在树中,尝试通过API获取该部门信息
try {
const deptRes = await getDepartmentInfo(deptId);
const deptData = deptRes.data || deptRes;
if (deptData && deptData.name) {
console.log(`✅ 通过API获取到部门信息: ${deptData.name}`);
// 如果获取到部门信息,将该部门添加到部门树中(作为临时节点)
const tempDept = {
id: deptId,
name: deptData.name,
tenant_id: deptData.tenant_id,
parent_id: deptData.parent_id || 0,
children: []
};
// 如果parent_id为0,添加到根节点;否则尝试找到父节点
if (tempDept.parent_id === 0 || !tempDept.parent_id) {
departmentTree.value.push(tempDept);
} else {
// 尝试找到父节点并添加到其children中
const findParent = (tree: any[], parentId: number): any => {
for (const node of tree) {
if (Number(node.id) === parentId) {
return node;
}
if (node.children && node.children.length > 0) {
const found = findParent(node.children, parentId);
if (found) return found;
}
}
return null;
};
const parent = findParent(departmentTree.value, tempDept.parent_id);
if (parent) {
parent.children.push(tempDept);
} else {
// 找不到父节点,作为根节点添加
departmentTree.value.push(tempDept);
}
}
form.value.department_id = deptId;
} else {
// 如果获取不到部门信息,保留ID值,让el-tree-select尝试显示
form.value.department_id = deptId;
}
} catch (error) {
console.error('获取部门信息失败:', error);
// 即使获取失败,也保留ID值
form.value.department_id = deptId;
}
} else {
console.log('✅ 找到部门节点:', foundNode);
// 确保使用数字类型
form.value.department_id = deptId;
console.log('form.department_id (设置后):', form.value.department_id);
}
}
// 先加载职位列表
if (form.value.department_id) {
await fetchPositions(form.value.department_id);
} else {
await fetchPositions(); // 加载所有职位
}
// 如果职位ID存在,确保职位列表中有该职位,如果没有则通过API获取
if (form.value.position_id) {
const positionId = Number(form.value.position_id);
// 检查职位是否已存在(避免重复添加)
const positionExists = positionList.value.some((pos: any) => Number(pos.id) === positionId);
if (!positionExists) {
console.warn(`⚠️ 职位ID ${positionId} 不在当前职位列表中,尝试通过API获取职位信息`);
try {
const posRes = await getPositionInfo(positionId);
const posData = posRes.data || posRes;
if (posData && posData.name) {
console.log(`✅ 通过API获取到职位信息: ${posData.name}`);
// 再次检查是否已存在(防止异步操作期间重复添加)
const alreadyExists = positionList.value.some((pos: any) => Number(pos.id) === positionId);
if (!alreadyExists) {
// 将职位添加到职位列表中,然后去重
positionList.value.push({
id: posData.id || positionId,
name: posData.name,
code: posData.code || '',
level: posData.level || 0,
description: posData.description || '',
status: posData.status || 1,
});
// 再次去重,确保没有重复
const uniquePositions = new Map<number, any>();
positionList.value.forEach((pos: any) => {
const posId = Number(pos.id);
if (posId && !uniquePositions.has(posId)) {
uniquePositions.set(posId, pos);
}
});
positionList.value = Array.from(uniquePositions.values());
}
}
} catch (error) {
console.error('获取职位信息失败:', error);
}
}
}
// 等待一个tick,确保DOM更新完成
await nextTick();
console.log('=== 编辑完成,等待DOM更新 ===');
} catch (e) {
console.error('加载员工信息失败:', e);
ElMessage.error("加载员工信息失败");
return;
}
dialogVisible.value = true;
};
const submitForm = async () => {
try {
if (isEdit.value) {
// 检查ID是否存在
if (!form.value.id || form.value.id === 0) {
ElMessage.error("员工ID不能为空,请重新选择员工");
return;
}
// 构建提交数据
const submitData: any = {
id: form.value.id,
employee_no: form.value.employeeNo,
name: form.value.name,
phone: form.value.phone,
email: form.value.email,
department_id: form.value.department_id || 0,
position_id: form.value.position_id || 0,
bank_name: form.value.bank_name || '',
bank_account: form.value.bank_account || '',
status: form.value.status,
};
if (form.value.tenant_id) {
submitData.tenant_id = form.value.tenant_id;
}
await editEmployee(form.value.id, submitData);
ElMessage.success({
message: "更新成功",
type: "success",
customClass: "my-el-message-success",
showClose: true,
center: true,
offset: 60,
});
dialogVisible.value = false;
fetchEmployees();
} else {
// 构建提交数据
const submitData: any = {
employee_no: form.value.employeeNo,
name: form.value.name,
phone: form.value.phone,
email: form.value.email,
department_id: form.value.department_id || 0,
position_id: form.value.position_id || 0,
bank_name: form.value.bank_name || '',
bank_account: form.value.bank_account || '',
status: form.value.status,
};
if (form.value.tenant_id) {
submitData.tenant_id = form.value.tenant_id;
}
await addEmployee(submitData);
ElMessage.success({
message: "添加成功",
type: "success",
customClass: "my-el-message-success",
showClose: true,
center: true,
offset: 60,
});
dialogVisible.value = false;
fetchEmployees();
}
} catch (e: any) {
// 显示具体的错误信息
const errorMsg = e?.response?.data?.message || e?.message || "操作失败";
ElMessage.error(errorMsg);
}
};
const handleDelete = async (employee: Employee) => {
ElMessageBox.confirm("确认删除该员工?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
await deleteEmployee(employee.id);
ElMessage.success("删除成功");
fetchEmployees();
} catch (e) {
ElMessage.error("删除失败");
}
});
};
// 重置密码
const handleResetPassword = async (employee: Employee) => {
ElMessageBox.confirm("确认重置该员工密码为默认密码(yunzer123)?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
const res = await resetEmployeePassword(employee.id);
if (res.code === 0 || res.success) {
ElMessage.success("密码重置成功,新密码为: yunzer123");
} else {
ElMessage.error(res.message || "重置密码失败");
}
} catch (e: any) {
const errorMsg = e?.response?.data?.message || e?.message || "重置密码失败";
ElMessage.error(errorMsg);
}
});
};
// 修改密码
const handleChangePassword = (employee: Employee) => {
currentEmployeeId.value = employee.id;
passwordForm.value = {
old_password: "",
new_password: "",
confirm_password: "",
};
passwordDialogVisible.value = true;
};
// 提交密码修改
const submitPasswordForm = async () => {
if (!passwordForm.value.new_password) {
ElMessage.error("新密码不能为空");
return;
}
if (passwordForm.value.new_password !== passwordForm.value.confirm_password) {
ElMessage.error("两次输入的新密码不一致");
return;
}
if (passwordForm.value.new_password.length < 6) {
ElMessage.error("新密码长度不能少于6位");
return;
}
if (!currentEmployeeId.value) {
ElMessage.error("员工ID无效");
return;
}
try {
const res = await changeEmployeePassword(currentEmployeeId.value, {
old_password: passwordForm.value.old_password,
new_password: passwordForm.value.new_password,
});
if (res.code === 0 || res.success) {
ElMessage.success("密码修改成功");
passwordDialogVisible.value = false;
passwordForm.value = {
old_password: "",
new_password: "",
confirm_password: "",
};
} else {
ElMessage.error(res.message || "修改密码失败");
}
} catch (e: any) {
const errorMsg = e?.response?.data?.message || e?.message || "修改密码失败";
ElMessage.error(errorMsg);
}
};
</script>
<style lang="less" scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-size: 1.4rem;
font-weight: 700;
}
}
</style>
+3 -9
View File
@@ -1,13 +1,7 @@
<template>
<div>
</div>
<router-view />
</template>
<script setup>
<script setup></script>
</script>
<style lang="scss" scoped>
</style>
<style lang="scss" scoped></style>
File diff suppressed because it is too large Load Diff
+450
View File
@@ -0,0 +1,450 @@
<template>
<div class="container-box">
<div class="header-bar">
<h2>职位管理</h2>
<div class="header-actions">
<el-button type="primary" @click="handleAddPosition">
<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="positions" style="width: 100%" v-loading="loading">
<el-table-column
prop="name"
label="职位名称"
width="200"
align="center"
/>
<el-table-column
prop="code"
label="职位编码"
width="150"
align="center"
/>
<el-table-column
prop="departmentName"
label="所属部门"
width="150"
align="center"
/>
<el-table-column
prop="level"
label="职位级别"
width="120"
align="center"
/>
<el-table-column
prop="description"
label="职位描述"
align="center"
min-width="200"
/>
<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="sortOrder"
label="排序"
width="100"
align="center"
/>
<el-table-column
prop="createTime"
label="创建时间"
width="180"
align="center"
/>
<el-table-column label="操作" width="180" align="center" fixed="right">
<template #default="scope">
<el-button size="small" @click="handleEdit(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="form" label-width="80px">
<el-form-item label="职位名称">
<el-input v-model="form.name" placeholder="请输入职位名称" />
</el-form-item>
<el-form-item label="职位编码">
<el-input v-model="form.code" placeholder="请输入职位编码" />
</el-form-item>
<el-form-item label="所属部门">
<el-select
v-model="form.department_id"
placeholder="请选择部门"
style="width: 100%"
:loading="loadingDepartments"
clearable
>
<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="职位级别">
<el-input-number v-model="form.level" :min="0" />
</el-form-item>
<el-form-item label="职位描述">
<el-input
v-model="form.description"
type="textarea"
:rows="3"
placeholder="请输入职位描述"
/>
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="form.sort_order" :min="0" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="form.status" placeholder="请选择状态">
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</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, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Refresh } from "@element-plus/icons-vue";
import {
getTenantPositions,
addPosition,
editPosition,
deletePosition,
getPositionInfo,
} from "@/api/position";
import { getTenantDepartments } from "@/api/department";
import { useAuthStore } from "@/stores/auth";
interface Position {
id: number;
name: string;
code: string;
department_id: number;
level: number;
description: string;
status: number;
sort_order: number;
tenant_id: number;
}
const authStore = useAuthStore();
const page = ref(1);
const pageSize = ref(10);
const total = ref(0);
const positions = ref<any[]>([]);
const departmentList = ref<any[]>([]);
const loadingDepartments = 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 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 () => {
loading.value = true;
let tenantId = getCurrentTenantId ? getCurrentTenantId() : null;
try {
const res = await getTenantPositions(tenantId);
let posList: any[] = [];
if (Array.isArray(res)) {
posList = res;
} else if (res?.data && Array.isArray(res.data)) {
posList = res.data;
} else if (res?.data?.data && Array.isArray(res.data.data)) {
posList = res.data.data;
} else if (res?.data) {
posList = res.data;
}
positions.value = posList.map((item: any) => {
// 查找部门名称
let departmentName = '';
const departmentId = item.department_id || null;
if (departmentId) {
const deptInfo = departmentList.value.find(d => d.id === departmentId);
departmentName = deptInfo ? deptInfo.name : '';
}
const createTime = item.create_time || item.createTime || null;
return {
id: item.id,
name: item.name || '',
code: item.code || '',
department_id: departmentId,
departmentName: departmentName,
level: item.level || 0,
description: item.description || '',
status: item.status || 1,
sort_order: item.sort_order || item.sortOrder || 0,
createTime: createTime
? new Date(createTime).toLocaleString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
})
: "",
};
});
total.value = positions.value.length;
} catch (e) {
positions.value = [];
total.value = 0;
ElMessage.error("获取职位列表失败");
} finally {
loading.value = false;
}
};
onMounted(async () => {
await fetchDepartments();
fetchPositions();
});
const handlePageChange = (p: number) => {
page.value = p;
};
const dialogVisible = ref(false);
const dialogTitle = ref("");
const isEdit = ref(false);
const form = ref<any>({
id: null,
name: "",
code: "",
department_id: null,
level: 0,
description: "",
sort_order: 0,
status: 1,
tenant_id: null,
});
const refresh = async () => {
loading.value = true;
try {
await fetchDepartments();
await fetchPositions();
ElMessage.success('刷新成功');
} catch (error) {
ElMessage.error('刷新失败');
} finally {
loading.value = false;
}
};
const handleAddPosition = () => {
dialogTitle.value = "添加职位";
isEdit.value = false;
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,
name: "",
code: "",
department_id: null,
level: 0,
description: "",
sort_order: 0,
status: 1,
tenant_id: tenantId,
};
dialogVisible.value = true;
};
const handleEdit = async (position: Position) => {
dialogTitle.value = "编辑职位";
isEdit.value = true;
try {
const res = await getPositionInfo(position.id);
const data = res.data || res;
const tenantId = getCurrentTenantId();
form.value = {
id: data.id,
name: data.name || '',
code: data.code || '',
department_id: data.department_id || null,
level: data.level || 0,
description: data.description || '',
sort_order: data.sort_order || data.sortOrder || 0,
status: data.status || 1,
tenant_id: data.tenant_id || tenantId,
};
} catch (e) {
ElMessage.error("加载职位信息失败");
return;
}
dialogVisible.value = true;
};
const submitForm = async () => {
try {
if (isEdit.value) {
if (!form.value.id || form.value.id === 0) {
ElMessage.error("职位ID不能为空,请重新选择职位");
return;
}
const submitData: any = {
name: form.value.name,
code: form.value.code,
department_id: form.value.department_id || 0,
level: form.value.level,
description: form.value.description,
sort_order: form.value.sort_order,
status: form.value.status,
};
await editPosition(form.value.id, submitData);
ElMessage.success("更新成功");
dialogVisible.value = false;
fetchPositions();
} else {
const submitData: any = {
name: form.value.name,
code: form.value.code,
department_id: form.value.department_id || 0,
level: form.value.level,
description: form.value.description,
sort_order: form.value.sort_order,
status: form.value.status,
};
if (form.value.tenant_id) {
submitData.tenant_id = form.value.tenant_id;
}
await addPosition(submitData);
ElMessage.success("添加成功");
dialogVisible.value = false;
fetchPositions();
}
} catch (e: any) {
const errorMsg = e?.response?.data?.message || e?.message || "操作失败";
ElMessage.error(errorMsg);
}
};
const handleDelete = async (position: Position) => {
ElMessageBox.confirm("确认删除该职位?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
await deletePosition(position.id);
ElMessage.success("删除成功");
fetchPositions();
} catch (e) {
ElMessage.error("删除失败");
}
});
};
</script>
<style lang="less" scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-size: 1.4rem;
font-weight: 700;
}
}
</style>
+162 -3
View File
@@ -35,6 +35,16 @@
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)">
@@ -140,6 +150,39 @@
<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"
@@ -211,6 +254,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
@@ -249,6 +296,61 @@ const fetchRoles = async () => {
}
};
// 获取部门列表
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';
@@ -311,6 +413,22 @@ const fetchUsers = async () => {
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
@@ -323,6 +441,10 @@ const fetchUsers = async () => {
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", {
@@ -348,8 +470,12 @@ const fetchUsers = async () => {
};
onMounted(async () => {
// 先加载角色列表,再加载用户列表
await fetchRoles();
// 先加载角色、部门、职位列表,再加载用户列表
await Promise.all([
fetchRoles(),
fetchDepartments(),
fetchPositions(),
]);
fetchUsers();
});
@@ -389,7 +515,11 @@ const currentForm = computed(() => {
const refresh = async () => {
loading.value = true;
try {
await fetchRoles();
await Promise.all([
fetchRoles(),
fetchDepartments(),
fetchPositions(),
]);
await fetchUsers();
ElMessage.success('刷新成功');
} catch (error) {
@@ -422,9 +552,13 @@ const handleAddUser = () => {
password: "",
email: "",
role: null, // role 字段存储角色ID
department_id: null,
position_id: null,
status: "active",
tenant_id: tenantId,
};
// 重新加载职位列表(所有职位)
fetchPositions();
dialogVisible.value = true;
};
@@ -456,9 +590,18 @@ const handleEdit = async (user: User) => {
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;
@@ -542,6 +685,14 @@ const submitForm = async () => {
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;
}
@@ -573,6 +724,14 @@ const submitForm = async () => {
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;
}