批量修复,增加组织架构
This commit is contained in:
parent
86c4060843
commit
a7bde8bb72
44
pc/src/api/department.js
Normal file
44
pc/src/api/department.js
Normal 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
pc/src/api/employee.js
Normal file
69
pc/src/api/employee.js
Normal 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
pc/src/api/position.js
Normal file
52
pc/src/api/position.js
Normal 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',
|
||||
});
|
||||
}
|
||||
|
||||
@ -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
pc/src/views/apps/oa/departments/index.vue
Normal file
372
pc/src/views/apps/oa/departments/index.vue
Normal 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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
1034
pc/src/views/apps/oa/organization/index.vue
Normal file
1034
pc/src/views/apps/oa/organization/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
450
pc/src/views/apps/oa/positions/index.vue
Normal file
450
pc/src/views/apps/oa/positions/index.vue
Normal 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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -62,8 +62,8 @@ func (c *AuthController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
// 验证用户(先验证租户,再验证租户下的用户)
|
||||
user, err := models.ValidateUser(username, password, tenantName)
|
||||
// 验证用户(先检查用户表,找不到再检查员工表)
|
||||
user, employee, err := models.ValidateUser(username, password, tenantName)
|
||||
|
||||
if err != nil {
|
||||
// 登录失败
|
||||
@ -72,8 +72,54 @@ func (c *AuthController) Login() {
|
||||
"message": err.Error(),
|
||||
}
|
||||
} else {
|
||||
var tokenString string
|
||||
var userId int
|
||||
var usernameForToken string
|
||||
var tenantId int
|
||||
var userInfo map[string]interface{}
|
||||
|
||||
// 判断是用户登录还是员工登录
|
||||
if user != nil {
|
||||
// 用户登录
|
||||
userId = user.Id
|
||||
usernameForToken = user.Username
|
||||
tenantId = user.TenantId
|
||||
userInfo = map[string]interface{}{
|
||||
"id": user.Id,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"avatar": user.Avatar,
|
||||
"nickname": user.Nickname,
|
||||
"tenant_id": user.TenantId,
|
||||
"type": "user", // 标识是用户登录
|
||||
}
|
||||
} else if employee != nil {
|
||||
// 员工登录
|
||||
userId = employee.Id
|
||||
usernameForToken = employee.EmployeeNo
|
||||
tenantId = employee.TenantId
|
||||
userInfo = map[string]interface{}{
|
||||
"id": employee.Id,
|
||||
"username": employee.EmployeeNo,
|
||||
"name": employee.Name,
|
||||
"email": employee.Email,
|
||||
"phone": employee.Phone,
|
||||
"tenant_id": employee.TenantId,
|
||||
"department_id": employee.DepartmentId,
|
||||
"position_id": employee.PositionId,
|
||||
"type": "employee", // 标识是员工登录
|
||||
}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "登录验证失败",
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 使用models包中的GenerateToken函数生成token
|
||||
tokenString, err := models.GenerateToken(user.Id, user.Username, user.TenantId)
|
||||
tokenString, err = models.GenerateToken(userId, usernameForToken, tenantId)
|
||||
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
@ -125,7 +171,15 @@ func (c *AuthController) Login() {
|
||||
}
|
||||
|
||||
o := orm.NewOrm()
|
||||
_, _ = o.Raw("UPDATE yz_users SET last_login_time = ?, last_login_ip = ?, login_count = IFNULL(login_count,0)+1 WHERE id = ?", loginTime, clientIP, user.Id).Exec()
|
||||
// 更新登录信息
|
||||
if user != nil {
|
||||
// 更新用户表
|
||||
_, _ = o.Raw("UPDATE yz_users SET last_login_time = ?, last_login_ip = ?, login_count = IFNULL(login_count,0)+1 WHERE id = ?", loginTime, clientIP, user.Id).Exec()
|
||||
} else if employee != nil {
|
||||
// 更新员工表(如果员工表有last_login_time和last_login_ip字段)
|
||||
// 注意:如果员工表没有这些字段,需要先添加字段
|
||||
_, _ = o.Raw("UPDATE yz_tenant_employees SET last_login_time = ?, last_login_ip = ? WHERE id = ?", loginTime, clientIP, employee.Id).Exec()
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
@ -133,14 +187,7 @@ func (c *AuthController) Login() {
|
||||
"data": map[string]interface{}{
|
||||
"accessToken": tokenString,
|
||||
"token": tokenString, // 兼容性
|
||||
"user": map[string]interface{}{
|
||||
"id": user.Id,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"avatar": user.Avatar,
|
||||
"nickname": user.Nickname,
|
||||
"tenant_id": user.TenantId,
|
||||
},
|
||||
"user": userInfo,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
270
server/controllers/department.go
Normal file
270
server/controllers/department.go
Normal file
@ -0,0 +1,270 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"server/models"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// DepartmentController 部门控制器
|
||||
type DepartmentController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// GetTenantDepartments 获取租户下的所有部门
|
||||
// @router /departments/tenant/:tenantId [get]
|
||||
func (c *DepartmentController) GetTenantDepartments() {
|
||||
tenantId, err := c.GetInt(":tenantId")
|
||||
if err != nil || tenantId <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "租户ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
departments, err := models.GetTenantDepartments(tenantId)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "获取部门列表失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 格式化返回数据
|
||||
deptList := make([]map[string]interface{}, 0)
|
||||
for _, dept := range departments {
|
||||
deptList = append(deptList, map[string]interface{}{
|
||||
"id": dept.Id,
|
||||
"tenant_id": dept.TenantId,
|
||||
"name": dept.Name,
|
||||
"code": dept.Code,
|
||||
"parent_id": dept.ParentId,
|
||||
"description": dept.Description,
|
||||
"manager_id": dept.ManagerId,
|
||||
"sort_order": dept.SortOrder,
|
||||
"status": dept.Status,
|
||||
})
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "获取部门列表成功",
|
||||
"data": deptList,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetDepartmentInfo 获取部门详情
|
||||
// @router /departments/:id [get]
|
||||
func (c *DepartmentController) GetDepartmentInfo() {
|
||||
id, err := c.GetInt(":id")
|
||||
if err != nil || id <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "部门ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
department, err := models.GetDepartmentById(id)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "部门不存在",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "获取部门信息成功",
|
||||
"data": map[string]interface{}{
|
||||
"id": department.Id,
|
||||
"tenant_id": department.TenantId,
|
||||
"name": department.Name,
|
||||
"code": department.Code,
|
||||
"parent_id": department.ParentId,
|
||||
"description": department.Description,
|
||||
"manager_id": department.ManagerId,
|
||||
"sort_order": department.SortOrder,
|
||||
"status": department.Status,
|
||||
},
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddDepartment 添加部门
|
||||
// @router /departments [post]
|
||||
func (c *DepartmentController) AddDepartment() {
|
||||
var deptData struct {
|
||||
TenantId int `json:"tenant_id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
ParentId int `json:"parent_id"`
|
||||
Description string `json:"description"`
|
||||
ManagerId int `json:"manager_id"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &deptData)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "请求参数错误: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
department := &models.Department{
|
||||
TenantId: deptData.TenantId,
|
||||
Name: deptData.Name,
|
||||
Code: deptData.Code,
|
||||
ParentId: deptData.ParentId,
|
||||
Description: deptData.Description,
|
||||
ManagerId: deptData.ManagerId,
|
||||
SortOrder: deptData.SortOrder,
|
||||
Status: deptData.Status,
|
||||
}
|
||||
|
||||
if department.TenantId == 0 {
|
||||
department.TenantId = 1
|
||||
}
|
||||
|
||||
id, err := models.AddDepartment(department)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "添加部门失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
department.Id = int(id)
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "添加部门成功",
|
||||
"data": department,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateDepartment 更新部门信息
|
||||
// @router /departments/:id [put]
|
||||
func (c *DepartmentController) UpdateDepartment() {
|
||||
id, err := c.GetInt(":id")
|
||||
if err != nil || id <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "部门ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
var updateData struct {
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
ParentId int `json:"parent_id"`
|
||||
Description string `json:"description"`
|
||||
ManagerId int `json:"manager_id"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal(c.Ctx.Input.RequestBody, &updateData)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "请求参数错误: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
department, err := models.GetDepartmentById(id)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "部门不存在",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
department.Name = updateData.Name
|
||||
department.Code = updateData.Code
|
||||
department.ParentId = updateData.ParentId
|
||||
department.Description = updateData.Description
|
||||
department.ManagerId = updateData.ManagerId
|
||||
department.SortOrder = updateData.SortOrder
|
||||
department.Status = updateData.Status
|
||||
|
||||
if err := models.UpdateDepartment(department); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "更新部门信息失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "更新部门信息成功",
|
||||
"data": department,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteDepartment 删除部门
|
||||
// @router /departments/:id [delete]
|
||||
func (c *DepartmentController) DeleteDepartment() {
|
||||
id, err := c.GetInt(":id")
|
||||
if err != nil || id <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "部门ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
if err := models.DeleteDepartment(id); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "删除部门失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "删除部门成功",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
403
server/controllers/employee.go
Normal file
403
server/controllers/employee.go
Normal file
@ -0,0 +1,403 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"server/models"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// EmployeeController 员工控制器
|
||||
type EmployeeController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// GetAllEmployees 获取所有员工(可选,用于管理员查看所有员工)
|
||||
// @router /employees [get]
|
||||
func (c *EmployeeController) GetAllEmployees() {
|
||||
employees, err := models.GetAllEmployees()
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "获取员工列表失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
} else {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "获取员工列表成功",
|
||||
"data": employees,
|
||||
}
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetTenantEmployees 获取租户下的所有员工
|
||||
// @router /employees/tenant/:tenantId [get]
|
||||
func (c *EmployeeController) GetTenantEmployees() {
|
||||
tenantId, err := c.GetInt(":tenantId")
|
||||
if err != nil || tenantId <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "租户ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
employees, err := models.GetTenantEmployees(tenantId)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "获取员工列表失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 格式化返回数据
|
||||
employeeList := make([]map[string]interface{}, 0)
|
||||
for _, emp := range employees {
|
||||
employeeList = append(employeeList, map[string]interface{}{
|
||||
"id": emp.Id,
|
||||
"tenant_id": emp.TenantId,
|
||||
"employee_no": emp.EmployeeNo,
|
||||
"name": emp.Name,
|
||||
"phone": emp.Phone,
|
||||
"email": emp.Email,
|
||||
"department_id": emp.DepartmentId,
|
||||
"position_id": emp.PositionId,
|
||||
"bank_name": emp.BankName,
|
||||
"bank_account": emp.BankAccount,
|
||||
"status": emp.Status,
|
||||
"create_time": emp.CreateTime,
|
||||
})
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "获取员工列表成功",
|
||||
"data": employeeList,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetEmployeeInfo 获取员工详情
|
||||
// @router /employees/:id [get]
|
||||
func (c *EmployeeController) GetEmployeeInfo() {
|
||||
id, err := c.GetInt(":id")
|
||||
if err != nil || id <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "员工ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
employee, err := models.GetEmployeeById(id)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "员工不存在",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "获取员工信息成功",
|
||||
"data": map[string]interface{}{
|
||||
"id": employee.Id,
|
||||
"tenant_id": employee.TenantId,
|
||||
"employee_no": employee.EmployeeNo,
|
||||
"name": employee.Name,
|
||||
"phone": employee.Phone,
|
||||
"email": employee.Email,
|
||||
"department_id": employee.DepartmentId,
|
||||
"position_id": employee.PositionId,
|
||||
"bank_name": employee.BankName,
|
||||
"bank_account": employee.BankAccount,
|
||||
"status": employee.Status,
|
||||
"create_time": employee.CreateTime,
|
||||
},
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddEmployee 添加员工
|
||||
// @router /employees [post]
|
||||
func (c *EmployeeController) AddEmployee() {
|
||||
var employeeData struct {
|
||||
TenantId int `json:"tenant_id"`
|
||||
EmployeeNo string `json:"employee_no"`
|
||||
Name string `json:"name"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
DepartmentId int `json:"department_id"`
|
||||
PositionId int `json:"position_id"`
|
||||
BankName string `json:"bank_name"`
|
||||
BankAccount string `json:"bank_account"`
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &employeeData)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "请求参数错误: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
employee := &models.Employee{
|
||||
TenantId: employeeData.TenantId,
|
||||
EmployeeNo: employeeData.EmployeeNo,
|
||||
Name: employeeData.Name,
|
||||
Phone: employeeData.Phone,
|
||||
Email: employeeData.Email,
|
||||
DepartmentId: employeeData.DepartmentId,
|
||||
PositionId: employeeData.PositionId,
|
||||
BankName: employeeData.BankName,
|
||||
BankAccount: employeeData.BankAccount,
|
||||
Status: employeeData.Status,
|
||||
}
|
||||
|
||||
// 如果没有指定租户ID,从当前登录用户获取(需要JWT中间件支持)
|
||||
// 这里暂时使用传入的tenant_id,如果为0则使用默认值
|
||||
if employee.TenantId == 0 {
|
||||
// 可以从JWT token中获取租户ID,这里暂时设为1作为默认值
|
||||
employee.TenantId = 1
|
||||
}
|
||||
|
||||
// 默认密码
|
||||
defaultPassword := "yunzer123"
|
||||
id, err := models.AddEmployee(employee, defaultPassword)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "添加员工失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
employee.Id = int(id)
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "添加员工成功",
|
||||
"data": employee,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateEmployee 更新员工信息
|
||||
// @router /employees/:id [put]
|
||||
func (c *EmployeeController) UpdateEmployee() {
|
||||
id, err := c.GetInt(":id")
|
||||
if err != nil || id <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "员工ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
var updateData struct {
|
||||
EmployeeNo string `json:"employee_no"`
|
||||
Name string `json:"name"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
DepartmentId int `json:"department_id"`
|
||||
PositionId int `json:"position_id"`
|
||||
BankName string `json:"bank_name"`
|
||||
BankAccount string `json:"bank_account"`
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal(c.Ctx.Input.RequestBody, &updateData)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "请求参数错误: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
employee, err := models.GetEmployeeById(id)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "员工不存在",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
employee.EmployeeNo = updateData.EmployeeNo
|
||||
employee.Name = updateData.Name
|
||||
employee.Phone = updateData.Phone
|
||||
employee.Email = updateData.Email
|
||||
employee.DepartmentId = updateData.DepartmentId
|
||||
employee.PositionId = updateData.PositionId
|
||||
employee.BankName = updateData.BankName
|
||||
employee.BankAccount = updateData.BankAccount
|
||||
employee.Status = updateData.Status
|
||||
|
||||
if err := models.UpdateEmployee(employee); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "更新员工信息失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "更新员工信息成功",
|
||||
"data": employee,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteEmployee 删除员工
|
||||
// @router /employees/:id [delete]
|
||||
func (c *EmployeeController) DeleteEmployee() {
|
||||
id, err := c.GetInt(":id")
|
||||
if err != nil || id <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "员工ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
if err := models.DeleteEmployee(id); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "删除员工失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "删除员工成功",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// ResetEmployeePassword 重置员工密码
|
||||
// @router /employees/:id/reset-password [post]
|
||||
func (c *EmployeeController) ResetEmployeePassword() {
|
||||
id, err := c.GetInt(":id")
|
||||
if err != nil || id <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "员工ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 默认密码
|
||||
defaultPassword := "yunzer123"
|
||||
|
||||
if err := models.ResetEmployeePassword(id, defaultPassword); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "重置密码失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "密码重置成功,新密码为: " + defaultPassword,
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// ChangeEmployeePassword 修改员工密码
|
||||
// @router /employees/:id/change-password [post]
|
||||
func (c *EmployeeController) ChangeEmployeePassword() {
|
||||
id, err := c.GetInt(":id")
|
||||
if err != nil || id <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "员工ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
var passwordData struct {
|
||||
OldPassword string `json:"old_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal(c.Ctx.Input.RequestBody, &passwordData)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "请求参数错误: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
if passwordData.NewPassword == "" {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "新密码不能为空",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
if err := models.ChangeEmployeePassword(id, passwordData.OldPassword, passwordData.NewPassword); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "修改密码失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "密码修改成功",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
319
server/controllers/position.go
Normal file
319
server/controllers/position.go
Normal file
@ -0,0 +1,319 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"server/models"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// PositionController 职位控制器
|
||||
type PositionController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// GetTenantPositions 获取租户下的所有职位
|
||||
// @router /positions/tenant/:tenantId [get]
|
||||
func (c *PositionController) GetTenantPositions() {
|
||||
tenantId, err := c.GetInt(":tenantId")
|
||||
if err != nil || tenantId <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "租户ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
positions, err := models.GetTenantPositions(tenantId)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "获取职位列表失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 格式化返回数据
|
||||
posList := make([]map[string]interface{}, 0)
|
||||
for _, pos := range positions {
|
||||
posList = append(posList, map[string]interface{}{
|
||||
"id": pos.Id,
|
||||
"tenant_id": pos.TenantId,
|
||||
"name": pos.Name,
|
||||
"code": pos.Code,
|
||||
"department_id": pos.DepartmentId,
|
||||
"level": pos.Level,
|
||||
"description": pos.Description,
|
||||
"sort_order": pos.SortOrder,
|
||||
"status": pos.Status,
|
||||
})
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "获取职位列表成功",
|
||||
"data": posList,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetPositionsByDepartment 根据部门ID获取职位列表
|
||||
// @router /positions/department/:departmentId [get]
|
||||
func (c *PositionController) GetPositionsByDepartment() {
|
||||
departmentId, err := c.GetInt(":departmentId")
|
||||
if err != nil || departmentId <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "部门ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
positions, err := models.GetPositionsByDepartment(departmentId)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "获取职位列表失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 格式化返回数据
|
||||
posList := make([]map[string]interface{}, 0)
|
||||
for _, pos := range positions {
|
||||
posList = append(posList, map[string]interface{}{
|
||||
"id": pos.Id,
|
||||
"tenant_id": pos.TenantId,
|
||||
"name": pos.Name,
|
||||
"code": pos.Code,
|
||||
"department_id": pos.DepartmentId,
|
||||
"level": pos.Level,
|
||||
"description": pos.Description,
|
||||
"sort_order": pos.SortOrder,
|
||||
"status": pos.Status,
|
||||
})
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "获取职位列表成功",
|
||||
"data": posList,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetPositionInfo 获取职位详情
|
||||
// @router /positions/:id [get]
|
||||
func (c *PositionController) GetPositionInfo() {
|
||||
id, err := c.GetInt(":id")
|
||||
if err != nil || id <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "职位ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
position, err := models.GetPositionById(id)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "职位不存在",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "获取职位信息成功",
|
||||
"data": map[string]interface{}{
|
||||
"id": position.Id,
|
||||
"tenant_id": position.TenantId,
|
||||
"name": position.Name,
|
||||
"code": position.Code,
|
||||
"department_id": position.DepartmentId,
|
||||
"level": position.Level,
|
||||
"description": position.Description,
|
||||
"sort_order": position.SortOrder,
|
||||
"status": position.Status,
|
||||
},
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddPosition 添加职位
|
||||
// @router /positions [post]
|
||||
func (c *PositionController) AddPosition() {
|
||||
var posData struct {
|
||||
TenantId int `json:"tenant_id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
DepartmentId int `json:"department_id"`
|
||||
Level int `json:"level"`
|
||||
Description string `json:"description"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &posData)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "请求参数错误: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
position := &models.Position{
|
||||
TenantId: posData.TenantId,
|
||||
Name: posData.Name,
|
||||
Code: posData.Code,
|
||||
DepartmentId: posData.DepartmentId,
|
||||
Level: posData.Level,
|
||||
Description: posData.Description,
|
||||
SortOrder: posData.SortOrder,
|
||||
Status: posData.Status,
|
||||
}
|
||||
|
||||
if position.TenantId == 0 {
|
||||
position.TenantId = 1
|
||||
}
|
||||
|
||||
id, err := models.AddPosition(position)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "添加职位失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
position.Id = int(id)
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "添加职位成功",
|
||||
"data": position,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdatePosition 更新职位信息
|
||||
// @router /positions/:id [put]
|
||||
func (c *PositionController) UpdatePosition() {
|
||||
id, err := c.GetInt(":id")
|
||||
if err != nil || id <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "职位ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
var updateData struct {
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
DepartmentId int `json:"department_id"`
|
||||
Level int `json:"level"`
|
||||
Description string `json:"description"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal(c.Ctx.Input.RequestBody, &updateData)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "请求参数错误: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
position, err := models.GetPositionById(id)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "职位不存在",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
position.Name = updateData.Name
|
||||
position.Code = updateData.Code
|
||||
position.DepartmentId = updateData.DepartmentId
|
||||
position.Level = updateData.Level
|
||||
position.Description = updateData.Description
|
||||
position.SortOrder = updateData.SortOrder
|
||||
position.Status = updateData.Status
|
||||
|
||||
if err := models.UpdatePosition(position); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "更新职位信息失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "更新职位信息成功",
|
||||
"data": position,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeletePosition 删除职位
|
||||
// @router /positions/:id [delete]
|
||||
func (c *PositionController) DeletePosition() {
|
||||
id, err := c.GetInt(":id")
|
||||
if err != nil || id <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "职位ID无效",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
if err := models.DeletePosition(id); err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "删除职位失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "删除职位成功",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@ -208,13 +208,15 @@ func (c *UserController) GetUserInfo() {
|
||||
func (c *UserController) AddUser() {
|
||||
// 定义接收用户数据的结构体
|
||||
var userData struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
TenantId int `json:"tenant_id"`
|
||||
Role int `json:"role"` // 角色ID
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
TenantId int `json:"tenant_id"`
|
||||
Role int `json:"role"` // 角色ID
|
||||
DepartmentId int `json:"department_id"`
|
||||
PositionId int `json:"position_id"`
|
||||
}
|
||||
|
||||
// 解析请求体JSON数据
|
||||
@ -266,7 +268,9 @@ func (c *UserController) AddUser() {
|
||||
userData.Nickname,
|
||||
userData.Avatar,
|
||||
userData.TenantId,
|
||||
userData.Role, // 添加 role 参数
|
||||
userData.Role,
|
||||
userData.DepartmentId,
|
||||
userData.PositionId,
|
||||
)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
@ -295,13 +299,15 @@ func (c *UserController) AddUser() {
|
||||
func (c *UserController) EditUser() {
|
||||
// 定义接收更新数据的结构体
|
||||
var updateData struct {
|
||||
Id int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Status string `json:"status"`
|
||||
Role int `json:"role"` // 改为 role,存储角色ID
|
||||
Id int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Status string `json:"status"`
|
||||
Role int `json:"role"` // 角色ID
|
||||
DepartmentId int `json:"department_id"`
|
||||
PositionId int `json:"position_id"`
|
||||
}
|
||||
|
||||
// 解析请求体JSON
|
||||
@ -335,7 +341,9 @@ func (c *UserController) EditUser() {
|
||||
updateData.Nickname,
|
||||
updateData.Avatar,
|
||||
updateData.Status,
|
||||
updateData.Role, // 改为 Role
|
||||
updateData.Role,
|
||||
updateData.DepartmentId,
|
||||
updateData.PositionId,
|
||||
)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
|
||||
108
server/database/add_employee_fields.sql
Normal file
108
server/database/add_employee_fields.sql
Normal file
@ -0,0 +1,108 @@
|
||||
-- 为员工表添加工资卡信息和密码字段
|
||||
-- 创建时间: 2025
|
||||
|
||||
SET @dbname = DATABASE();
|
||||
SET @tablename = 'yz_tenant_employees';
|
||||
|
||||
-- 添加工资卡开户行字段
|
||||
SET @columnname = 'bank_name';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
(TABLE_SCHEMA = @dbname)
|
||||
AND (TABLE_NAME = @tablename)
|
||||
AND (COLUMN_NAME = @columnname)
|
||||
) > 0,
|
||||
'SELECT "Column bank_name already exists in yz_tenant_employees" AS "";',
|
||||
'ALTER TABLE yz_tenant_employees ADD COLUMN bank_name VARCHAR(100) DEFAULT NULL COMMENT ''工资卡开户行'' AFTER position_id;'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
-- 添加工资卡卡号字段
|
||||
SET @columnname = 'bank_account';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
(TABLE_SCHEMA = @dbname)
|
||||
AND (TABLE_NAME = @tablename)
|
||||
AND (COLUMN_NAME = @columnname)
|
||||
) > 0,
|
||||
'SELECT "Column bank_account already exists in yz_tenant_employees" AS "";',
|
||||
'ALTER TABLE yz_tenant_employees ADD COLUMN bank_account VARCHAR(50) DEFAULT NULL COMMENT ''工资卡卡号'' AFTER bank_name;'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
-- 添加登录密码字段
|
||||
SET @columnname = 'password';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
(TABLE_SCHEMA = @dbname)
|
||||
AND (TABLE_NAME = @tablename)
|
||||
AND (COLUMN_NAME = @columnname)
|
||||
) > 0,
|
||||
'SELECT "Column password already exists in yz_tenant_employees" AS "";',
|
||||
'ALTER TABLE yz_tenant_employees ADD COLUMN password VARCHAR(255) DEFAULT NULL COMMENT ''登录密码(加密后)'' AFTER bank_account;'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
-- 添加盐值字段
|
||||
SET @columnname = 'salt';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
(TABLE_SCHEMA = @dbname)
|
||||
AND (TABLE_NAME = @tablename)
|
||||
AND (COLUMN_NAME = @columnname)
|
||||
) > 0,
|
||||
'SELECT "Column salt already exists in yz_tenant_employees" AS "";',
|
||||
'ALTER TABLE yz_tenant_employees ADD COLUMN salt VARCHAR(100) DEFAULT NULL COMMENT ''密码盐值'' AFTER password;'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
-- 添加最后登录时间字段
|
||||
SET @columnname = 'last_login_time';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
(TABLE_SCHEMA = @dbname)
|
||||
AND (TABLE_NAME = @tablename)
|
||||
AND (COLUMN_NAME = @columnname)
|
||||
) > 0,
|
||||
'SELECT "Column last_login_time already exists in yz_tenant_employees" AS "";',
|
||||
'ALTER TABLE yz_tenant_employees ADD COLUMN last_login_time DATETIME DEFAULT NULL COMMENT ''最后登录时间'' AFTER salt;'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
-- 添加最后登录IP字段
|
||||
SET @columnname = 'last_login_ip';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
(TABLE_SCHEMA = @dbname)
|
||||
AND (TABLE_NAME = @tablename)
|
||||
AND (COLUMN_NAME = @columnname)
|
||||
) > 0,
|
||||
'SELECT "Column last_login_ip already exists in yz_tenant_employees" AS "";',
|
||||
'ALTER TABLE yz_tenant_employees ADD COLUMN last_login_ip VARCHAR(50) DEFAULT NULL COMMENT ''最后登录IP'' AFTER last_login_time;'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
38
server/database/add_menu_delete_time.sql
Normal file
38
server/database/add_menu_delete_time.sql
Normal file
@ -0,0 +1,38 @@
|
||||
-- 为菜单表添加 delete_time 字段(软删除)
|
||||
-- 如果字段已存在,则不会重复添加
|
||||
|
||||
SET @dbname = DATABASE();
|
||||
SET @tablename = 'yz_menus';
|
||||
SET @columnname = 'delete_time';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
(TABLE_SCHEMA = @dbname)
|
||||
AND (TABLE_NAME = @tablename)
|
||||
AND (COLUMN_NAME = @columnname)
|
||||
) > 0,
|
||||
'SELECT "Column delete_time already exists in yz_menus" AS "";',
|
||||
'ALTER TABLE yz_menus ADD COLUMN delete_time DATETIME DEFAULT NULL COMMENT ''删除时间(软删除)'' AFTER update_time;'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
-- 添加索引以优化查询性能
|
||||
SET @indexname = 'idx_delete_time';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE
|
||||
(TABLE_SCHEMA = @dbname)
|
||||
AND (TABLE_NAME = @tablename)
|
||||
AND (INDEX_NAME = @indexname)
|
||||
) > 0,
|
||||
'SELECT "Index idx_delete_time already exists in yz_menus" AS "";',
|
||||
'ALTER TABLE yz_menus ADD INDEX idx_delete_time (delete_time);'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
15
server/database/add_organization_menu.sql
Normal file
15
server/database/add_organization_menu.sql
Normal file
@ -0,0 +1,15 @@
|
||||
-- 添加组织架构管理菜单
|
||||
-- 注意:请根据实际情况修改 parent_id(92 是 OA 模块的 ID,请确认)
|
||||
|
||||
INSERT INTO yz_menus (name, path, parent_id, icon, `order`, status, component_path, menu_type, description)
|
||||
VALUES ('组织架构', '/apps/oa/organization', 92, 'fa-solid fa-sitemap', 1, 1, '@/views/apps/oa/organization/index.vue', 1, '组织架构管理(部门与职位)')
|
||||
ON DUPLICATE KEY UPDATE
|
||||
name = VALUES(name),
|
||||
path = VALUES(path),
|
||||
icon = VALUES(icon),
|
||||
`order` = VALUES(`order`),
|
||||
status = VALUES(status),
|
||||
component_path = VALUES(component_path),
|
||||
menu_type = VALUES(menu_type),
|
||||
description = VALUES(description);
|
||||
|
||||
163
server/database/create_oa_tables.sql
Normal file
163
server/database/create_oa_tables.sql
Normal file
@ -0,0 +1,163 @@
|
||||
-- 创建OA模块相关表(租户应用)
|
||||
-- 创建时间: 2025
|
||||
-- 描述: 创建租户部门表、租户职位表和租户员工表
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- =============================================
|
||||
-- 1. 创建租户部门表
|
||||
-- =============================================
|
||||
CREATE TABLE IF NOT EXISTS yz_tenant_departments (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '部门ID',
|
||||
tenant_id INT NOT NULL DEFAULT 0 COMMENT '租户ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '部门名称',
|
||||
code VARCHAR(50) DEFAULT NULL COMMENT '部门编码',
|
||||
parent_id INT DEFAULT 0 COMMENT '父部门ID,0表示顶级部门',
|
||||
description TEXT DEFAULT NULL COMMENT '部门描述',
|
||||
manager_id INT DEFAULT NULL COMMENT '部门经理ID',
|
||||
sort_order INT DEFAULT 0 COMMENT '排序序号',
|
||||
status TINYINT DEFAULT 1 COMMENT '状态:1-启用,0-禁用',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
delete_time DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
|
||||
|
||||
-- 索引
|
||||
INDEX idx_tenant_id (tenant_id),
|
||||
INDEX idx_code (code),
|
||||
INDEX idx_parent_id (parent_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_sort_order (sort_order)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户部门表';
|
||||
|
||||
-- =============================================
|
||||
-- 2. 创建租户职位表
|
||||
-- =============================================
|
||||
CREATE TABLE IF NOT EXISTS yz_tenant_positions (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '职位ID',
|
||||
tenant_id INT NOT NULL DEFAULT 0 COMMENT '租户ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '职位名称',
|
||||
code VARCHAR(50) DEFAULT NULL COMMENT '职位编码',
|
||||
department_id INT DEFAULT NULL COMMENT '所属部门ID',
|
||||
level INT DEFAULT 0 COMMENT '职位级别',
|
||||
description TEXT DEFAULT NULL COMMENT '职位描述',
|
||||
sort_order INT DEFAULT 0 COMMENT '排序序号',
|
||||
status TINYINT DEFAULT 1 COMMENT '状态:1-启用,0-禁用',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
delete_time DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
|
||||
|
||||
-- 索引
|
||||
INDEX idx_tenant_id (tenant_id),
|
||||
INDEX idx_code (code),
|
||||
INDEX idx_department_id (department_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_sort_order (sort_order)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户职位表';
|
||||
|
||||
-- =============================================
|
||||
-- 3. 创建租户员工表(如果不存在)
|
||||
-- =============================================
|
||||
CREATE TABLE IF NOT EXISTS yz_tenant_employees (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID',
|
||||
tenant_id INT NOT NULL DEFAULT 0 COMMENT '租户ID',
|
||||
employee_no VARCHAR(50) NOT NULL COMMENT '工号',
|
||||
name VARCHAR(50) NOT NULL COMMENT '姓名',
|
||||
phone VARCHAR(20) DEFAULT NULL COMMENT '手机号',
|
||||
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
|
||||
department_id INT DEFAULT NULL COMMENT '部门ID',
|
||||
position_id INT DEFAULT NULL COMMENT '职位ID',
|
||||
status TINYINT DEFAULT 1 COMMENT '状态:1-在职,0-离职',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
delete_time DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
|
||||
|
||||
-- 索引
|
||||
INDEX idx_tenant_id (tenant_id),
|
||||
INDEX idx_employee_no (employee_no),
|
||||
INDEX idx_name (name),
|
||||
INDEX idx_department_id (department_id),
|
||||
INDEX idx_position_id (position_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_create_time (create_time)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户员工表';
|
||||
|
||||
-- =============================================
|
||||
-- 4. 更新用户表,添加部门和职位字段(如果不存在)
|
||||
-- =============================================
|
||||
-- 检查并添加 department_id 字段
|
||||
SET @dbname = DATABASE();
|
||||
SET @tablename = 'yz_users';
|
||||
SET @columnname = 'department_id';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
(TABLE_SCHEMA = @dbname)
|
||||
AND (TABLE_NAME = @tablename)
|
||||
AND (COLUMN_NAME = @columnname)
|
||||
) > 0,
|
||||
'SELECT "Column department_id already exists in yz_users" AS "";',
|
||||
'ALTER TABLE yz_users ADD COLUMN department_id INT DEFAULT NULL COMMENT ''部门ID'' AFTER role;'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
-- 检查并添加 position_id 字段
|
||||
SET @columnname = 'position_id';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
(TABLE_SCHEMA = @dbname)
|
||||
AND (TABLE_NAME = @tablename)
|
||||
AND (COLUMN_NAME = @columnname)
|
||||
) > 0,
|
||||
'SELECT "Column position_id already exists in yz_users" AS "";',
|
||||
'ALTER TABLE yz_users ADD COLUMN position_id INT DEFAULT NULL COMMENT ''职位ID'' AFTER department_id;'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
-- =============================================
|
||||
-- 5. 更新租户员工表,添加部门和职位字段(如果不存在)
|
||||
-- =============================================
|
||||
-- 检查并添加 department_id 字段
|
||||
SET @tablename = 'yz_tenant_employees';
|
||||
SET @columnname = 'department_id';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
(TABLE_SCHEMA = @dbname)
|
||||
AND (TABLE_NAME = @tablename)
|
||||
AND (COLUMN_NAME = @columnname)
|
||||
) > 0,
|
||||
'SELECT "Column department_id already exists in yz_employees" AS "";',
|
||||
'ALTER TABLE yz_tenant_employees ADD COLUMN department_id INT DEFAULT NULL COMMENT ''部门ID'' AFTER email;'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
-- 检查并添加 position_id 字段
|
||||
SET @columnname = 'position_id';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
(TABLE_SCHEMA = @dbname)
|
||||
AND (TABLE_NAME = @tablename)
|
||||
AND (COLUMN_NAME = @columnname)
|
||||
) > 0,
|
||||
'SELECT "Column position_id already exists in yz_tenant_employees" AS "";',
|
||||
'ALTER TABLE yz_tenant_employees ADD COLUMN position_id INT DEFAULT NULL COMMENT ''职位ID'' AFTER department_id;'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
32
server/database/yz_departments.sql
Normal file
32
server/database/yz_departments.sql
Normal file
@ -0,0 +1,32 @@
|
||||
-- 创建部门表
|
||||
-- 创建时间: 2025
|
||||
-- 描述: OA系统部门管理表
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- 检查并创建租户部门表(如果不存在)
|
||||
CREATE TABLE IF NOT EXISTS yz_tenant_departments (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '部门ID',
|
||||
tenant_id INT NOT NULL DEFAULT 0 COMMENT '租户ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '部门名称',
|
||||
code VARCHAR(50) DEFAULT NULL COMMENT '部门编码',
|
||||
parent_id INT DEFAULT 0 COMMENT '父部门ID,0表示顶级部门',
|
||||
description TEXT DEFAULT NULL COMMENT '部门描述',
|
||||
manager_id INT DEFAULT NULL COMMENT '部门经理ID',
|
||||
sort_order INT DEFAULT 0 COMMENT '排序序号',
|
||||
status TINYINT DEFAULT 1 COMMENT '状态:1-启用,0-禁用',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
delete_time DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
|
||||
|
||||
-- 索引
|
||||
INDEX idx_tenant_id (tenant_id),
|
||||
INDEX idx_code (code),
|
||||
INDEX idx_parent_id (parent_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_sort_order (sort_order)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户部门表';
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
34
server/database/yz_employees.sql
Normal file
34
server/database/yz_employees.sql
Normal file
@ -0,0 +1,34 @@
|
||||
-- 创建员工表
|
||||
-- 创建时间: 2025
|
||||
-- 描述: OA系统员工管理表
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- 检查并创建租户员工表(如果不存在)
|
||||
CREATE TABLE IF NOT EXISTS yz_tenant_employees (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID',
|
||||
tenant_id INT NOT NULL DEFAULT 0 COMMENT '租户ID',
|
||||
employee_no VARCHAR(50) NOT NULL COMMENT '工号',
|
||||
name VARCHAR(50) NOT NULL COMMENT '姓名',
|
||||
phone VARCHAR(20) DEFAULT NULL COMMENT '手机号',
|
||||
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
|
||||
department_id INT DEFAULT NULL COMMENT '部门ID',
|
||||
position_id INT DEFAULT NULL COMMENT '职位ID',
|
||||
status TINYINT DEFAULT 1 COMMENT '状态:1-在职,0-离职',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
delete_time DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
|
||||
|
||||
-- 索引
|
||||
INDEX idx_tenant_id (tenant_id),
|
||||
INDEX idx_employee_no (employee_no),
|
||||
INDEX idx_name (name),
|
||||
INDEX idx_department_id (department_id),
|
||||
INDEX idx_position_id (position_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_create_time (create_time)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户员工表';
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
32
server/database/yz_positions.sql
Normal file
32
server/database/yz_positions.sql
Normal file
@ -0,0 +1,32 @@
|
||||
-- 创建职位表
|
||||
-- 创建时间: 2025
|
||||
-- 描述: OA系统职位管理表
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- 检查并创建租户职位表(如果不存在)
|
||||
CREATE TABLE IF NOT EXISTS yz_tenant_positions (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '职位ID',
|
||||
tenant_id INT NOT NULL DEFAULT 0 COMMENT '租户ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '职位名称',
|
||||
code VARCHAR(50) DEFAULT NULL COMMENT '职位编码',
|
||||
department_id INT DEFAULT NULL COMMENT '所属部门ID',
|
||||
level INT DEFAULT 0 COMMENT '职位级别',
|
||||
description TEXT DEFAULT NULL COMMENT '职位描述',
|
||||
sort_order INT DEFAULT 0 COMMENT '排序序号',
|
||||
status TINYINT DEFAULT 1 COMMENT '状态:1-启用,0-禁用',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
delete_time DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
|
||||
|
||||
-- 索引
|
||||
INDEX idx_tenant_id (tenant_id),
|
||||
INDEX idx_code (code),
|
||||
INDEX idx_department_id (department_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_sort_order (sort_order)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户职位表';
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
87
server/models/department.go
Normal file
87
server/models/department.go
Normal file
@ -0,0 +1,87 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
// Department 部门模型
|
||||
type Department struct {
|
||||
Id int `orm:"auto" json:"id"`
|
||||
TenantId int `orm:"column(tenant_id);default(0)" json:"tenant_id"`
|
||||
Name string `orm:"size(100)" json:"name"`
|
||||
Code string `orm:"size(50);null" json:"code"`
|
||||
ParentId int `orm:"column(parent_id);default(0)" json:"parent_id"`
|
||||
Description string `orm:"type(text);null" json:"description"`
|
||||
ManagerId int `orm:"column(manager_id);null" json:"manager_id"`
|
||||
SortOrder int `orm:"column(sort_order);default(0)" json:"sort_order"`
|
||||
Status int8 `orm:"default(1)" json:"status"` // 1-启用,0-禁用
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);null;type(datetime)" json:"delete_time,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 设置表名
|
||||
func (d *Department) TableName() string {
|
||||
return "yz_tenant_departments"
|
||||
}
|
||||
|
||||
func init() {
|
||||
orm.RegisterModel(new(Department))
|
||||
}
|
||||
|
||||
// GetTenantDepartments 获取租户下的所有部门
|
||||
func GetTenantDepartments(tenantId int) ([]*Department, error) {
|
||||
o := orm.NewOrm()
|
||||
var departments []*Department
|
||||
_, err := o.QueryTable("yz_tenant_departments").
|
||||
Filter("tenant_id", tenantId).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("sort_order", "create_time").
|
||||
All(&departments)
|
||||
return departments, err
|
||||
}
|
||||
|
||||
// GetDepartmentById 根据ID获取部门信息
|
||||
func GetDepartmentById(id int) (*Department, error) {
|
||||
o := orm.NewOrm()
|
||||
department := &Department{Id: id}
|
||||
err := o.Read(department)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 检查是否已删除
|
||||
if department.DeleteTime != nil {
|
||||
return nil, orm.ErrNoRows
|
||||
}
|
||||
return department, nil
|
||||
}
|
||||
|
||||
// AddDepartment 添加部门
|
||||
func AddDepartment(department *Department) (int64, error) {
|
||||
o := orm.NewOrm()
|
||||
id, err := o.Insert(department)
|
||||
return id, err
|
||||
}
|
||||
|
||||
// UpdateDepartment 更新部门信息
|
||||
func UpdateDepartment(department *Department) error {
|
||||
o := orm.NewOrm()
|
||||
_, err := o.Update(department, "name", "code", "parent_id", "description", "manager_id", "sort_order", "status", "update_time")
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteDepartment 软删除部门
|
||||
func DeleteDepartment(id int) error {
|
||||
o := orm.NewOrm()
|
||||
department := &Department{Id: id}
|
||||
if err := o.Read(department); err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
department.DeleteTime = &now
|
||||
_, err := o.Update(department, "delete_time")
|
||||
return err
|
||||
}
|
||||
|
||||
242
server/models/employee.go
Normal file
242
server/models/employee.go
Normal file
@ -0,0 +1,242 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
// Employee 员工模型
|
||||
type Employee struct {
|
||||
Id int `orm:"auto" json:"id"`
|
||||
TenantId int `orm:"column(tenant_id);default(0)" json:"tenant_id"`
|
||||
EmployeeNo string `orm:"column(employee_no);size(50)" json:"employee_no"`
|
||||
Name string `orm:"size(50)" json:"name"`
|
||||
Phone string `orm:"size(20);null" json:"phone"`
|
||||
Email string `orm:"size(100);null" json:"email"`
|
||||
DepartmentId int `orm:"column(department_id);null;default(0)" json:"department_id"`
|
||||
PositionId int `orm:"column(position_id);null;default(0)" json:"position_id"`
|
||||
BankName string `orm:"column(bank_name);size(100);null" json:"bank_name"`
|
||||
BankAccount string `orm:"column(bank_account);size(50);null" json:"bank_account"`
|
||||
Password string `orm:"size(255);null" json:"-"` // 不返回给前端
|
||||
Salt string `orm:"size(100);null" json:"-"` // 不返回给前端
|
||||
LastLoginTime *time.Time `orm:"column(last_login_time);null;type(datetime)" json:"last_login_time,omitempty"`
|
||||
LastLoginIp string `orm:"column(last_login_ip);null;size(50)" json:"last_login_ip,omitempty"`
|
||||
Status int8 `orm:"column(status);default(1)" json:"status"` // 1-在职,0-离职
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);null;type(datetime)" json:"delete_time,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 设置表名
|
||||
func (e *Employee) TableName() string {
|
||||
return "yz_tenant_employees"
|
||||
}
|
||||
|
||||
func init() {
|
||||
orm.RegisterModel(new(Employee))
|
||||
}
|
||||
|
||||
// GetTenantEmployees 获取租户下的所有员工
|
||||
func GetTenantEmployees(tenantId int) ([]*Employee, error) {
|
||||
o := orm.NewOrm()
|
||||
var employees []*Employee
|
||||
_, err := o.QueryTable("yz_tenant_employees").
|
||||
Filter("tenant_id", tenantId).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("-create_time").
|
||||
All(&employees)
|
||||
return employees, err
|
||||
}
|
||||
|
||||
// GetEmployeeById 根据ID获取员工信息
|
||||
func GetEmployeeById(id int) (*Employee, error) {
|
||||
o := orm.NewOrm()
|
||||
employee := &Employee{Id: id}
|
||||
err := o.Read(employee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 检查是否已删除
|
||||
if employee.DeleteTime != nil {
|
||||
return nil, orm.ErrNoRows
|
||||
}
|
||||
return employee, nil
|
||||
}
|
||||
|
||||
// generateSalt 生成随机盐值
|
||||
func generateEmployeeSalt() (string, error) {
|
||||
salt := make([]byte, 16)
|
||||
_, err := rand.Read(salt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(salt), nil
|
||||
}
|
||||
|
||||
// hashEmployeePassword 使用scrypt算法对密码进行加密
|
||||
func hashEmployeePassword(password, salt string) (string, error) {
|
||||
saltBytes, err := base64.URLEncoding.DecodeString(salt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
const (
|
||||
N = 16384
|
||||
r = 8
|
||||
p = 1
|
||||
)
|
||||
hashBytes, err := scrypt.Key([]byte(password), saltBytes, N, r, p, 32)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(hashBytes), nil
|
||||
}
|
||||
|
||||
// AddEmployee 添加员工(自动设置默认密码)
|
||||
func AddEmployee(employee *Employee, defaultPassword string) (int64, error) {
|
||||
// 生成盐值
|
||||
salt, err := generateEmployeeSalt()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("生成盐值失败: %v", err)
|
||||
}
|
||||
employee.Salt = salt
|
||||
|
||||
// 加密默认密码
|
||||
hashedPassword, err := hashEmployeePassword(defaultPassword, salt)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("密码加密失败: %v", err)
|
||||
}
|
||||
employee.Password = hashedPassword
|
||||
|
||||
o := orm.NewOrm()
|
||||
id, err := o.Insert(employee)
|
||||
return id, err
|
||||
}
|
||||
|
||||
// UpdateEmployee 更新员工信息
|
||||
func UpdateEmployee(employee *Employee) error {
|
||||
o := orm.NewOrm()
|
||||
_, err := o.Update(employee, "employee_no", "name", "phone", "email", "department_id", "position_id", "bank_name", "bank_account", "status", "update_time")
|
||||
return err
|
||||
}
|
||||
|
||||
// ResetEmployeePassword 重置员工密码为默认密码
|
||||
func ResetEmployeePassword(employeeId int, defaultPassword string) error {
|
||||
o := orm.NewOrm()
|
||||
employee := &Employee{Id: employeeId}
|
||||
if err := o.Read(employee); err != nil {
|
||||
return fmt.Errorf("员工不存在: %v", err)
|
||||
}
|
||||
|
||||
// 生成新盐值
|
||||
salt, err := generateEmployeeSalt()
|
||||
if err != nil {
|
||||
return fmt.Errorf("生成盐值失败: %v", err)
|
||||
}
|
||||
employee.Salt = salt
|
||||
|
||||
// 加密默认密码
|
||||
hashedPassword, err := hashEmployeePassword(defaultPassword, salt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("密码加密失败: %v", err)
|
||||
}
|
||||
employee.Password = hashedPassword
|
||||
|
||||
_, err = o.Update(employee, "Password", "Salt")
|
||||
return err
|
||||
}
|
||||
|
||||
// ChangeEmployeePassword 修改员工密码
|
||||
func ChangeEmployeePassword(employeeId int, oldPassword, newPassword string) error {
|
||||
o := orm.NewOrm()
|
||||
employee := &Employee{Id: employeeId}
|
||||
if err := o.Read(employee); err != nil {
|
||||
return fmt.Errorf("员工不存在: %v", err)
|
||||
}
|
||||
|
||||
// 验证旧密码
|
||||
if !verifyEmployeePassword(oldPassword, employee.Salt, employee.Password) {
|
||||
return errors.New("旧密码不正确")
|
||||
}
|
||||
|
||||
// 加密新密码
|
||||
hashedPassword, err := hashEmployeePassword(newPassword, employee.Salt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("密码加密失败: %v", err)
|
||||
}
|
||||
employee.Password = hashedPassword
|
||||
|
||||
_, err = o.Update(employee, "Password")
|
||||
return err
|
||||
}
|
||||
|
||||
// verifyEmployeePassword 验证密码是否正确
|
||||
func verifyEmployeePassword(password, salt, storedHash string) bool {
|
||||
hash, err := hashEmployeePassword(password, salt)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return hash == storedHash
|
||||
}
|
||||
|
||||
// ValidateEmployee 验证员工登录信息(使用工号作为登录账号)
|
||||
func ValidateEmployee(employeeNo, password string, tenantId int) (*Employee, error) {
|
||||
o := orm.NewOrm()
|
||||
|
||||
// 1. 根据工号和租户ID查询员工(排除已删除的)
|
||||
var employee Employee
|
||||
err := o.QueryTable("yz_tenant_employees").
|
||||
Filter("employee_no", employeeNo).
|
||||
Filter("tenant_id", tenantId).
|
||||
Filter("delete_time__isnull", true).
|
||||
Filter("status", 1). // 只允许在职员工登录
|
||||
One(&employee)
|
||||
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, errors.New("员工不存在或已离职")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询员工失败: %v", err)
|
||||
}
|
||||
|
||||
// 2. 检查密码和盐是否存在
|
||||
if employee.Password == "" || employee.Salt == "" {
|
||||
return nil, errors.New("员工密码未设置,请联系管理员")
|
||||
}
|
||||
|
||||
// 3. 验证密码
|
||||
if verifyEmployeePassword(password, employee.Salt, employee.Password) {
|
||||
return &employee, nil
|
||||
}
|
||||
return nil, errors.New("密码不正确")
|
||||
}
|
||||
|
||||
// DeleteEmployee 软删除员工
|
||||
func DeleteEmployee(id int) error {
|
||||
o := orm.NewOrm()
|
||||
employee := &Employee{Id: id}
|
||||
if err := o.Read(employee); err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
employee.DeleteTime = &now
|
||||
_, err := o.Update(employee, "delete_time")
|
||||
return err
|
||||
}
|
||||
|
||||
// GetAllEmployees 获取所有员工(排除已删除的)
|
||||
func GetAllEmployees() ([]*Employee, error) {
|
||||
o := orm.NewOrm()
|
||||
var employees []*Employee
|
||||
_, err := o.QueryTable("yz_tenant_employees").
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("-create_time").
|
||||
All(&employees)
|
||||
return employees, err
|
||||
}
|
||||
|
||||
@ -8,20 +8,21 @@ import (
|
||||
|
||||
// Menu 菜单模型
|
||||
type Menu struct {
|
||||
Id int `orm:"auto"`
|
||||
Name string `orm:"size(100)"`
|
||||
Path string `orm:"size(255)"`
|
||||
ParentId int `orm:"default(0)"`
|
||||
Icon string `orm:"size(100)"`
|
||||
Order int `orm:"default(0)"`
|
||||
Status int8 `orm:"default(1)"`
|
||||
ComponentPath string `orm:"size(500);null"`
|
||||
IsExternal int8 `orm:"default(0)"`
|
||||
ExternalUrl string `orm:"size(1000);null"`
|
||||
MenuType int8 `orm:"default(1)"`
|
||||
Permission string `orm:"size(200);null"`
|
||||
CreateTime time.Time `orm:"auto_now_add;type(datetime)"`
|
||||
UpdateTime time.Time `orm:"auto_now;type(datetime)"`
|
||||
Id int `orm:"auto"`
|
||||
Name string `orm:"size(100)"`
|
||||
Path string `orm:"size(255)"`
|
||||
ParentId int `orm:"default(0)"`
|
||||
Icon string `orm:"size(100)"`
|
||||
Order int `orm:"default(0)"`
|
||||
Status int8 `orm:"default(1)"`
|
||||
ComponentPath string `orm:"size(500);null"`
|
||||
IsExternal int8 `orm:"default(0)"`
|
||||
ExternalUrl string `orm:"size(1000);null"`
|
||||
MenuType int8 `orm:"default(1)"`
|
||||
Permission string `orm:"size(200);null"`
|
||||
CreateTime time.Time `orm:"auto_now_add;type(datetime)"`
|
||||
UpdateTime time.Time `orm:"auto_now;type(datetime)"`
|
||||
DeleteTime *time.Time `orm:"null;type(datetime)"`
|
||||
}
|
||||
|
||||
// TableName 设置表名
|
||||
@ -29,11 +30,11 @@ func (m *Menu) TableName() string {
|
||||
return "yz_menus"
|
||||
}
|
||||
|
||||
// GetAllMenus 获取所有菜单
|
||||
// GetAllMenus 获取所有菜单(未删除的)
|
||||
func GetAllMenus() ([]map[string]interface{}, error) {
|
||||
o := orm.NewOrm()
|
||||
var menus []*Menu
|
||||
_, err := o.QueryTable("yz_menus").Filter("Status", 1).All(&menus)
|
||||
_, err := o.QueryTable("yz_menus").Filter("delete_time__isnull", true).All(&menus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -46,6 +47,7 @@ func GetAllMenus() ([]map[string]interface{}, error) {
|
||||
"parentId": m.ParentId,
|
||||
"icon": m.Icon,
|
||||
"order": m.Order,
|
||||
"status": m.Status,
|
||||
"componentPath": m.ComponentPath,
|
||||
"isExternal": m.IsExternal,
|
||||
"externalUrl": m.ExternalUrl,
|
||||
@ -82,9 +84,15 @@ func UpdateMenuStatus(id int, status int8) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteMenu 删除菜单
|
||||
// DeleteMenu 删除菜单(软删除)
|
||||
func DeleteMenu(id int) error {
|
||||
o := orm.NewOrm()
|
||||
_, err := o.Delete(&Menu{Id: id})
|
||||
menu := Menu{Id: id}
|
||||
if err := o.Read(&menu); err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
menu.DeleteTime = &now
|
||||
_, err := o.Update(&menu, "DeleteTime")
|
||||
return err
|
||||
}
|
||||
|
||||
@ -99,12 +99,12 @@ func GetRolePermissions(roleId int) (*RolePermission, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAllMenuPermissions 获取所有菜单权限列表(用于分配权限时展示)
|
||||
// GetAllMenuPermissions 获取所有菜单权限列表(用于分配权限时展示,未删除的)
|
||||
func GetAllMenuPermissions() ([]*MenuPermission, error) {
|
||||
o := orm.NewOrm()
|
||||
var menus []*MenuPermission
|
||||
|
||||
_, err := o.Raw("SELECT id as menu_id, name as menu_name, path, menu_type, permission, parent_id FROM yz_menus WHERE status = 1 ORDER BY parent_id, `order`").QueryRows(&menus)
|
||||
_, err := o.Raw("SELECT id as menu_id, name as menu_name, path, menu_type, permission, parent_id FROM yz_menus WHERE delete_time IS NULL ORDER BY parent_id, `order`").QueryRows(&menus)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取菜单列表失败: %v", err)
|
||||
}
|
||||
@ -259,7 +259,7 @@ func GetUserMenuTree(userId int) ([]*MenuTreeNode, error) {
|
||||
placeholders[i] = "?"
|
||||
args[i] = id
|
||||
}
|
||||
query := fmt.Sprintf("SELECT * FROM yz_menus WHERE id IN (%s) AND menu_type = 1 AND status = 1 ORDER BY parent_id, `order`", strings.Join(placeholders, ","))
|
||||
query := fmt.Sprintf("SELECT * FROM yz_menus WHERE id IN (%s) AND menu_type = 1 AND delete_time IS NULL ORDER BY parent_id, `order`", strings.Join(placeholders, ","))
|
||||
_, err = o.Raw(query, args...).QueryRows(&menus)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取菜单列表失败: %v", err)
|
||||
|
||||
100
server/models/position.go
Normal file
100
server/models/position.go
Normal file
@ -0,0 +1,100 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
// Position 职位模型
|
||||
type Position struct {
|
||||
Id int `orm:"auto" json:"id"`
|
||||
TenantId int `orm:"column(tenant_id);default(0)" json:"tenant_id"`
|
||||
Name string `orm:"size(100)" json:"name"`
|
||||
Code string `orm:"size(50);null" json:"code"`
|
||||
DepartmentId int `orm:"column(department_id);null" json:"department_id"`
|
||||
Level int `orm:"default(0)" json:"level"`
|
||||
Description string `orm:"type(text);null" json:"description"`
|
||||
SortOrder int `orm:"column(sort_order);default(0)" json:"sort_order"`
|
||||
Status int8 `orm:"default(1)" json:"status"` // 1-启用,0-禁用
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);null;type(datetime)" json:"delete_time,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 设置表名
|
||||
func (p *Position) TableName() string {
|
||||
return "yz_tenant_positions"
|
||||
}
|
||||
|
||||
func init() {
|
||||
orm.RegisterModel(new(Position))
|
||||
}
|
||||
|
||||
// GetTenantPositions 获取租户下的所有职位
|
||||
func GetTenantPositions(tenantId int) ([]*Position, error) {
|
||||
o := orm.NewOrm()
|
||||
var positions []*Position
|
||||
_, err := o.QueryTable("yz_tenant_positions").
|
||||
Filter("tenant_id", tenantId).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("sort_order", "create_time").
|
||||
All(&positions)
|
||||
return positions, err
|
||||
}
|
||||
|
||||
// GetPositionsByDepartment 根据部门ID获取职位列表
|
||||
func GetPositionsByDepartment(departmentId int) ([]*Position, error) {
|
||||
o := orm.NewOrm()
|
||||
var positions []*Position
|
||||
_, err := o.QueryTable("yz_tenant_positions").
|
||||
Filter("department_id", departmentId).
|
||||
Filter("delete_time__isnull", true).
|
||||
Filter("status", 1).
|
||||
OrderBy("sort_order", "create_time").
|
||||
All(&positions)
|
||||
return positions, err
|
||||
}
|
||||
|
||||
// GetPositionById 根据ID获取职位信息
|
||||
func GetPositionById(id int) (*Position, error) {
|
||||
o := orm.NewOrm()
|
||||
position := &Position{Id: id}
|
||||
err := o.Read(position)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 检查是否已删除
|
||||
if position.DeleteTime != nil {
|
||||
return nil, orm.ErrNoRows
|
||||
}
|
||||
return position, err
|
||||
}
|
||||
|
||||
// AddPosition 添加职位
|
||||
func AddPosition(position *Position) (int64, error) {
|
||||
o := orm.NewOrm()
|
||||
id, err := o.Insert(position)
|
||||
return id, err
|
||||
}
|
||||
|
||||
// UpdatePosition 更新职位信息
|
||||
func UpdatePosition(position *Position) error {
|
||||
o := orm.NewOrm()
|
||||
_, err := o.Update(position, "name", "code", "department_id", "level", "description", "sort_order", "status", "update_time")
|
||||
return err
|
||||
}
|
||||
|
||||
// DeletePosition 软删除职位
|
||||
func DeletePosition(id int) error {
|
||||
o := orm.NewOrm()
|
||||
position := &Position{Id: id}
|
||||
if err := o.Read(position); err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
position.DeleteTime = &now
|
||||
_, err := o.Update(position, "delete_time")
|
||||
return err
|
||||
}
|
||||
|
||||
@ -26,6 +26,8 @@ type User struct {
|
||||
Nickname string
|
||||
Status int `orm:"column(status);default(1)" json:"status"`
|
||||
Role int `orm:"column(role);default(0)" json:"role"`
|
||||
DepartmentId int `orm:"column(department_id);null;default(0)" json:"department_id"`
|
||||
PositionId int `orm:"column(position_id);null;default(0)" json:"position_id"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);null;type(datetime)" json:"delete_time"`
|
||||
LastLoginTime *time.Time `orm:"column(last_login_time);null;type(datetime)" json:"last_login_time"`
|
||||
LastLoginIp string `orm:"column(last_login_ip);null;size(50)" json:"last_login_ip"`
|
||||
@ -193,8 +195,8 @@ func GetUserInfo(userId int, username string, tenantId int) (*User, error) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// ValidateUser 验证用户登录信息
|
||||
func ValidateUser(username, password string, tenantName string) (*User, error) {
|
||||
// ValidateUser 验证用户登录信息(先检查用户表,找不到再检查员工表)
|
||||
func ValidateUser(username, password string, tenantName string) (*User, *Employee, error) {
|
||||
o := orm.NewOrm()
|
||||
|
||||
// 1. 根据租户名称查询租户(只查询未删除的)
|
||||
@ -206,39 +208,45 @@ func ValidateUser(username, password string, tenantName string) (*User, error) {
|
||||
err := o.Raw("SELECT id, status, delete_time FROM yz_tenants WHERE name = ? AND delete_time IS NULL", tenantName).QueryRow(&tenant)
|
||||
if err == orm.ErrNoRows {
|
||||
// 租户不存在(数据库中根本没有这个名称)
|
||||
return nil, errors.New("租户不存在")
|
||||
return nil, nil, errors.New("租户不存在")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询租户失败: %v", err)
|
||||
return nil, nil, fmt.Errorf("查询租户失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查租户状态
|
||||
if tenant.Status == "disabled" {
|
||||
return nil, errors.New("租户已被禁用")
|
||||
return nil, nil, errors.New("租户已被禁用")
|
||||
}
|
||||
|
||||
if tenant.Status != "enabled" {
|
||||
return nil, fmt.Errorf("租户状态异常: %s", tenant.Status)
|
||||
return nil, nil, fmt.Errorf("租户状态异常: %s", tenant.Status)
|
||||
}
|
||||
|
||||
tenantId := tenant.Id
|
||||
|
||||
// 2. 获取租户下的用户
|
||||
// 2. 先尝试从用户表获取
|
||||
user, err := GetUserInfo(0, username, tenantId)
|
||||
if err != nil {
|
||||
// 用户不存在或查询失败
|
||||
return nil, err
|
||||
if err == nil && user != nil {
|
||||
// 用户存在,验证密码
|
||||
if verifyPassword(password, user.Salt, user.Password) {
|
||||
return user, nil, nil
|
||||
}
|
||||
return nil, nil, errors.New("密码不正确")
|
||||
}
|
||||
|
||||
// 3. 验证密码
|
||||
if verifyPassword(password, user.Salt, user.Password) {
|
||||
return user, nil
|
||||
// 3. 用户表中没有找到,尝试从员工表获取
|
||||
employee, err := ValidateEmployee(username, password, tenantId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, errors.New("密码不正确")
|
||||
|
||||
// 员工验证成功,返回员工信息(user为nil表示是员工登录)
|
||||
return nil, employee, nil
|
||||
}
|
||||
|
||||
// AddUser 向数据库添加新用户
|
||||
func AddUser(username, password, email, nickname, avatar string, tenantId, role int) (*User, error) {
|
||||
func AddUser(username, password, email, nickname, avatar string, tenantId, role, departmentId, positionId int) (*User, error) {
|
||||
// 1. 验证租户是否存在且有效
|
||||
o := orm.NewOrm()
|
||||
var tenantExists bool
|
||||
@ -273,14 +281,17 @@ func AddUser(username, password, email, nickname, avatar string, tenantId, role
|
||||
|
||||
// 4. 构建用户对象
|
||||
user := &User{
|
||||
TenantId: tenantId,
|
||||
Username: username,
|
||||
Password: hashedPassword,
|
||||
Salt: salt,
|
||||
Email: email,
|
||||
Nickname: nickname,
|
||||
Avatar: avatar,
|
||||
Role: role, // 设置角色ID
|
||||
TenantId: tenantId,
|
||||
Username: username,
|
||||
Password: hashedPassword,
|
||||
Salt: salt,
|
||||
Email: email,
|
||||
Nickname: nickname,
|
||||
Avatar: avatar,
|
||||
Role: role,
|
||||
DepartmentId: departmentId,
|
||||
PositionId: positionId,
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
// 5. 插入数据库(使用之前定义的 o)
|
||||
@ -294,7 +305,7 @@ func AddUser(username, password, email, nickname, avatar string, tenantId, role
|
||||
}
|
||||
|
||||
// EditUser 更新用户信息
|
||||
func EditUser(id int, username, email, nickname, avatar, status string, roleId int) (*User, error) {
|
||||
func EditUser(id int, username, email, nickname, avatar, status string, roleId, departmentId, positionId int) (*User, error) {
|
||||
// 根据ID查询用户
|
||||
o := orm.NewOrm()
|
||||
user := &User{}
|
||||
@ -339,6 +350,16 @@ func EditUser(id int, username, email, nickname, avatar, status string, roleId i
|
||||
user.Role = roleId
|
||||
}
|
||||
|
||||
// 更新部门ID
|
||||
if departmentId >= 0 {
|
||||
user.DepartmentId = departmentId
|
||||
}
|
||||
|
||||
// 更新职位ID
|
||||
if positionId >= 0 {
|
||||
user.PositionId = positionId
|
||||
}
|
||||
|
||||
// 执行数据库更新
|
||||
_, err = o.Update(user)
|
||||
if err != nil {
|
||||
|
||||
@ -71,6 +71,24 @@ func init() {
|
||||
beego.Router("/api/reset-password", &controllers.UserController{}, "post:ResetPassword")
|
||||
beego.Router("/api/tenantUsers/:tenantId", &controllers.UserController{}, "get:GetTenantUsers")
|
||||
|
||||
// 员工管理路由
|
||||
beego.Router("/api/employees", &controllers.EmployeeController{}, "get:GetAllEmployees;post:AddEmployee")
|
||||
beego.Router("/api/employees/:id", &controllers.EmployeeController{}, "get:GetEmployeeInfo;put:UpdateEmployee;delete:DeleteEmployee")
|
||||
beego.Router("/api/employees/tenant/:tenantId", &controllers.EmployeeController{}, "get:GetTenantEmployees")
|
||||
beego.Router("/api/employees/:id/reset-password", &controllers.EmployeeController{}, "post:ResetEmployeePassword")
|
||||
beego.Router("/api/employees/:id/change-password", &controllers.EmployeeController{}, "post:ChangeEmployeePassword")
|
||||
|
||||
// 部门管理路由
|
||||
beego.Router("/api/departments", &controllers.DepartmentController{}, "post:AddDepartment")
|
||||
beego.Router("/api/departments/:id", &controllers.DepartmentController{}, "get:GetDepartmentInfo;put:UpdateDepartment;delete:DeleteDepartment")
|
||||
beego.Router("/api/departments/tenant/:tenantId", &controllers.DepartmentController{}, "get:GetTenantDepartments")
|
||||
|
||||
// 职位管理路由
|
||||
beego.Router("/api/positions", &controllers.PositionController{}, "post:AddPosition")
|
||||
beego.Router("/api/positions/:id", &controllers.PositionController{}, "get:GetPositionInfo;put:UpdatePosition;delete:DeletePosition")
|
||||
beego.Router("/api/positions/tenant/:tenantId", &controllers.PositionController{}, "get:GetTenantPositions")
|
||||
beego.Router("/api/positions/department/:departmentId", &controllers.PositionController{}, "get:GetPositionsByDepartment")
|
||||
|
||||
// 认证路由
|
||||
beego.Router("/api/login", &controllers.AuthController{}, "post:Login")
|
||||
beego.Router("/api/logout", &controllers.AuthController{}, "post:Logout")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user