965 lines
30 KiB
Vue
965 lines
30 KiB
Vue
<template>
|
||
<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 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";
|
||
|
||
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>
|