批量修复,增加组织架构

This commit is contained in:
李志强 2025-11-04 17:35:01 +08:00
parent 86c4060843
commit a7bde8bb72
29 changed files with 5200 additions and 88 deletions

44
pc/src/api/department.js Normal file
View File

@ -0,0 +1,44 @@
import request from '@/utils/request';
// 获取租户下的所有部门
export function getTenantDepartments(tenantId) {
return request({
url: `/api/departments/tenant/${tenantId}`,
method: 'get',
});
}
// 获取部门详情
export function getDepartmentInfo(departmentId) {
return request({
url: `/api/departments/${departmentId}`,
method: 'get',
});
}
// 添加部门
export function addDepartment(data) {
return request({
url: '/api/departments',
method: 'post',
data,
});
}
// 更新部门信息
export function editDepartment(departmentId, data) {
return request({
url: `/api/departments/${departmentId}`,
method: 'put',
data,
});
}
// 删除部门
export function deleteDepartment(departmentId) {
return request({
url: `/api/departments/${departmentId}`,
method: 'delete',
});
}

69
pc/src/api/employee.js Normal file
View File

@ -0,0 +1,69 @@
import request from '@/utils/request';
// 获取所有员工信息
export function getAllEmployees() {
return request({
url: '/api/employees',
method: 'get',
});
}
// 获取员工信息
export function getEmployeeInfo(employeeId) {
return request({
url: `/api/employees/${employeeId}`,
method: 'get',
});
}
// 获取租户员工信息
export function getTenantEmployees(tenantId) {
return request({
url: `/api/employees/tenant/${tenantId}`,
method: 'get',
});
}
// 添加员工
export function addEmployee(data) {
return request({
url: '/api/employees',
method: 'post',
data,
});
}
// 更新员工信息
export function editEmployee(employeeId, data) {
return request({
url: `/api/employees/${employeeId}`,
method: 'put',
data,
});
}
// 删除员工
export function deleteEmployee(employeeId) {
return request({
url: `/api/employees/${employeeId}`,
method: 'delete',
});
}
// 重置员工密码
export function resetEmployeePassword(employeeId) {
return request({
url: `/api/employees/${employeeId}/reset-password`,
method: 'post',
});
}
// 修改员工密码
export function changeEmployeePassword(employeeId, data) {
return request({
url: `/api/employees/${employeeId}/change-password`,
method: 'post',
data,
});
}

52
pc/src/api/position.js Normal file
View File

@ -0,0 +1,52 @@
import request from '@/utils/request';
// 获取租户下的所有职位
export function getTenantPositions(tenantId) {
return request({
url: `/api/positions/tenant/${tenantId}`,
method: 'get',
});
}
// 根据部门ID获取职位列表
export function getPositionsByDepartment(departmentId) {
return request({
url: `/api/positions/department/${departmentId}`,
method: 'get',
});
}
// 获取职位详情
export function getPositionInfo(positionId) {
return request({
url: `/api/positions/${positionId}`,
method: 'get',
});
}
// 添加职位
export function addPosition(data) {
return request({
url: '/api/positions',
method: 'post',
data,
});
}
// 更新职位信息
export function editPosition(positionId, data) {
return request({
url: `/api/positions/${positionId}`,
method: 'put',
data,
});
}
// 删除职位
export function deletePosition(positionId) {
return request({
url: `/api/positions/${positionId}`,
method: 'delete',
});
}

View File

@ -38,6 +38,7 @@
<el-dropdown trigger="click" @command="handleCommand">
<span class="el-dropdown-link" style="cursor: pointer;">
<img :src="getImageUrl('user')" class="user" />
<span class="user-name">{{ displayName }}</span>
</span>
<template #dropdown>
<el-dropdown-menu>
@ -207,6 +208,25 @@ const getImageUrl = (user) => {
return new URL(`/src/assets/images/default_avatar.png`, import.meta.url).href;
};
//
const displayName = computed(() => {
const user = authStore.user;
if (!user) return '';
// name
if (user.type === 'employee' && user.name) {
return user.name;
}
// nickname
if (user.nickname) {
return user.nickname;
}
// username
return user.username || '';
});
const handleCollapse = () => {
store.state.isCollapse = !store.state.isCollapse;
};
@ -390,6 +410,22 @@ onUnmounted(() => {
display: flex;
align-items: center;
cursor: pointer;
gap: 12px;
.user-name {
font-size: 14px;
color: var(--el-text-color-primary);
font-weight: 500;
white-space: nowrap;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
// 使
html:not(.dark) & {
color: #ffffff;
}
}
}
}

View File

@ -0,0 +1,372 @@
<template>
<div class="container-box">
<div class="header-bar">
<h2>部门管理</h2>
<div class="header-actions">
<el-button type="primary" @click="handleAddDepartment">
<el-icon><Plus /></el-icon>
添加部门
</el-button>
<el-button @click="refresh">
<el-icon><Refresh /></el-icon>
刷新
</el-button>
</div>
</div>
<el-divider></el-divider>
<el-table :data="departments" style="width: 100%" v-loading="loading">
<el-table-column
prop="name"
label="部门名称"
width="200"
align="center"
/>
<el-table-column
prop="code"
label="部门编码"
width="150"
align="center"
/>
<el-table-column
prop="description"
label="部门描述"
align="center"
min-width="200"
/>
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
{{ scope.row.status === 1 ? "启用" : "禁用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="sortOrder"
label="排序"
width="100"
align="center"
/>
<el-table-column
prop="createTime"
label="创建时间"
width="180"
align="center"
/>
<el-table-column label="操作" width="180" align="center" fixed="right">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)"
>编辑</el-button
>
<el-button
size="small"
type="danger"
@click="handleDelete(scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="pagination-bar">
<el-pagination
background
:current-page="page"
:page-size="pageSize"
:total="total"
@current-change="handlePageChange"
layout="total, prev, pager, next"
/>
</div>
<!-- Dialog for add/edit -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px">
<el-form :model="form" label-width="80px">
<el-form-item label="部门名称">
<el-input v-model="form.name" placeholder="请输入部门名称" />
</el-form-item>
<el-form-item label="部门编码">
<el-input v-model="form.code" placeholder="请输入部门编码" />
</el-form-item>
<el-form-item label="部门描述">
<el-input
v-model="form.description"
type="textarea"
:rows="3"
placeholder="请输入部门描述"
/>
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="form.sort_order" :min="0" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="form.status" placeholder="请选择状态">
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Refresh } from "@element-plus/icons-vue";
import {
getTenantDepartments,
addDepartment,
editDepartment,
deleteDepartment,
getDepartmentInfo,
} from "@/api/department";
import { useAuthStore } from "@/stores/auth";
interface Department {
id: number;
name: string;
code: string;
description: string;
status: number;
sort_order: number;
tenant_id: number;
}
const authStore = useAuthStore();
const page = ref(1);
const pageSize = ref(10);
const total = ref(0);
const departments = ref<any[]>([]);
const loading = ref(false);
// ID
const getCurrentTenantId = () => {
if (authStore.user && authStore.user.tenant_id) {
return authStore.user.tenant_id;
}
const userInfo = localStorage.getItem('userInfo');
if (userInfo) {
try {
const user = JSON.parse(userInfo);
return user.tenant_id || user.tenantId || 0;
} catch (e) {
console.error('Failed to parse user info:', e);
}
}
return 0;
};
const fetchDepartments = async () => {
loading.value = true;
let tenantId = getCurrentTenantId ? getCurrentTenantId() : null;
try {
const res = await getTenantDepartments(tenantId);
let deptList: any[] = [];
if (Array.isArray(res)) {
deptList = res;
} else if (res?.data && Array.isArray(res.data)) {
deptList = res.data;
} else if (res?.data?.data && Array.isArray(res.data.data)) {
deptList = res.data.data;
} else if (res?.data) {
deptList = res.data;
}
departments.value = deptList.map((item: any) => {
const createTime = item.create_time || item.createTime || null;
return {
id: item.id,
name: item.name || '',
code: item.code || '',
description: item.description || '',
status: item.status || 1,
sort_order: item.sort_order || item.sortOrder || 0,
createTime: createTime
? new Date(createTime).toLocaleString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
})
: "",
};
});
total.value = departments.value.length;
} catch (e) {
departments.value = [];
total.value = 0;
ElMessage.error("获取部门列表失败");
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchDepartments();
});
const handlePageChange = (p: number) => {
page.value = p;
};
const dialogVisible = ref(false);
const dialogTitle = ref("");
const isEdit = ref(false);
const form = ref<any>({
id: null,
name: "",
code: "",
description: "",
sort_order: 0,
status: 1,
tenant_id: null,
});
const refresh = async () => {
loading.value = true;
try {
await fetchDepartments();
ElMessage.success('刷新成功');
} catch (error) {
ElMessage.error('刷新失败');
} finally {
loading.value = false;
}
};
const handleAddDepartment = () => {
dialogTitle.value = "添加部门";
isEdit.value = false;
let tenantId = null;
const cachedUser = localStorage.getItem("userInfo");
if (cachedUser) {
try {
const userInfo = JSON.parse(cachedUser);
tenantId = userInfo.tenant_id || null;
} catch (e) {
tenantId = null;
}
}
form.value = {
id: 0,
name: "",
code: "",
description: "",
sort_order: 0,
status: 1,
tenant_id: tenantId,
};
dialogVisible.value = true;
};
const handleEdit = async (department: Department) => {
dialogTitle.value = "编辑部门";
isEdit.value = true;
try {
const res = await getDepartmentInfo(department.id);
const data = res.data || res;
const tenantId = getCurrentTenantId();
form.value = {
id: data.id,
name: data.name || '',
code: data.code || '',
description: data.description || '',
sort_order: data.sort_order || data.sortOrder || 0,
status: data.status || 1,
tenant_id: data.tenant_id || tenantId,
};
} catch (e) {
ElMessage.error("加载部门信息失败");
return;
}
dialogVisible.value = true;
};
const submitForm = async () => {
try {
if (isEdit.value) {
if (!form.value.id || form.value.id === 0) {
ElMessage.error("部门ID不能为空请重新选择部门");
return;
}
const submitData: any = {
name: form.value.name,
code: form.value.code,
description: form.value.description,
sort_order: form.value.sort_order,
status: form.value.status,
};
await editDepartment(form.value.id, submitData);
ElMessage.success("更新成功");
dialogVisible.value = false;
fetchDepartments();
} else {
const submitData: any = {
name: form.value.name,
code: form.value.code,
description: form.value.description,
sort_order: form.value.sort_order,
status: form.value.status,
};
if (form.value.tenant_id) {
submitData.tenant_id = form.value.tenant_id;
}
await addDepartment(submitData);
ElMessage.success("添加成功");
dialogVisible.value = false;
fetchDepartments();
}
} catch (e: any) {
const errorMsg = e?.response?.data?.message || e?.message || "操作失败";
ElMessage.error(errorMsg);
}
};
const handleDelete = async (department: Department) => {
ElMessageBox.confirm("确认删除该部门?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
await deleteDepartment(department.id);
ElMessage.success("删除成功");
fetchDepartments();
} catch (e) {
ElMessage.error("删除失败");
}
});
};
</script>
<style lang="less" scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-size: 1.4rem;
font-weight: 700;
}
}
</style>

View File

@ -1,7 +1,964 @@
<template>
<div>员工管理</div>
<div class="container-box">
<div class="header-bar">
<h2>员工管理</h2>
<div class="header-actions">
<el-button type="primary" @click="handleAddEmployee">
<el-icon><Plus /></el-icon>
添加员工
</el-button>
<el-button @click="refresh">
<el-icon><Refresh /></el-icon>
刷新
</el-button>
</div>
</div>
<el-divider></el-divider>
<el-table :data="employees" style="width: 100%" v-loading="loading">
<el-table-column
prop="employeeNo"
label="工号"
width="120"
align="center"
/>
<el-table-column
prop="name"
label="姓名"
width="120"
align="center"
/>
<el-table-column
prop="phone"
label="手机号"
width="150"
align="center"
/>
<el-table-column
prop="email"
label="邮箱"
align="center"
min-width="200"
/>
<el-table-column prop="department" label="部门" width="150" align="center">
<template #default="scope">
<span>{{ scope.row.departmentName || '未分配' }}</span>
</template>
</el-table-column>
<el-table-column prop="position" label="职位" width="150" align="center">
<template #default="scope">
<span>{{ scope.row.positionName || '未分配' }}</span>
</template>
</el-table-column>
<el-table-column prop="bankName" label="开户行" width="150" align="center">
<template #default="scope">
<span>{{ scope.row.bankName || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="bankAccount" label="卡号" width="180" align="center">
<template #default="scope">
<span>{{ scope.row.bankAccount || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
{{ scope.row.status === 1 ? "在职" : "离职" }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="createTime"
label="入职时间"
width="180"
align="center"
/>
<el-table-column label="操作" width="280" align="center" fixed="right">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)"
>编辑</el-button
>
<el-button
size="small"
type="warning"
@click="handleResetPassword(scope.row)"
>重置密码</el-button
>
<el-button
size="small"
type="info"
@click="handleChangePassword(scope.row)"
>修改密码</el-button
>
<el-button
size="small"
type="danger"
@click="handleDelete(scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="pagination-bar">
<el-pagination
background
:current-page="page"
:page-size="pageSize"
:total="total"
@current-change="handlePageChange"
layout="total, prev, pager, next"
/>
</div>
<!-- Dialog for add/edit -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px">
<el-form :model="form" label-width="80px">
<el-form-item label="工号">
<el-input v-model="form.employeeNo" placeholder="请输入工号" />
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="form.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="手机号">
<el-input v-model="form.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="form.email" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="部门">
<el-tree-select
v-model="form.department_id"
:data="departmentTree"
:props="{ label: 'name', children: 'children' }"
value-key="id"
placeholder="请选择部门"
check-strictly
clearable
style="width: 100%"
:loading="loadingDepartments"
@change="handleDepartmentChange"
:render-after-expand="false"
/>
<div v-if="departmentTree.length === 0 && !loadingDepartments" style="color: #999; font-size: 12px; margin-top: 4px;">
暂无部门数据请先创建部门
</div>
</el-form-item>
<el-form-item label="职位">
<el-select
v-model="form.position_id"
placeholder="请选择职位"
style="width: 100%"
:loading="loadingPositions"
clearable
>
<el-option
v-for="pos in positionList"
:key="pos.id"
:label="pos.name"
:value="pos.id"
/>
</el-select>
</el-form-item>
<el-form-item label="开户行">
<el-input v-model="form.bank_name" placeholder="请输入工资卡开户行" />
</el-form-item>
<el-form-item label="卡号">
<el-input v-model="form.bank_account" placeholder="请输入工资卡卡号" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="form.status" placeholder="请选择状态">
<el-option label="在职" :value="1" />
<el-option label="离职" :value="0" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">保存</el-button>
</template>
</el-dialog>
<!-- 修改密码对话框 -->
<el-dialog v-model="passwordDialogVisible" title="修改密码" width="400px">
<el-form :model="passwordForm" label-width="100px">
<el-form-item label="旧密码">
<el-input
v-model="passwordForm.old_password"
type="password"
placeholder="请输入旧密码"
show-password
/>
</el-form-item>
<el-form-item label="新密码">
<el-input
v-model="passwordForm.new_password"
type="password"
placeholder="请输入新密码"
show-password
/>
</el-form-item>
<el-form-item label="确认密码">
<el-input
v-model="passwordForm.confirm_password"
type="password"
placeholder="请再次输入新密码"
show-password
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="passwordDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitPasswordForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup></script>
<script setup lang="ts">
import { ref, onMounted, nextTick } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Refresh } from "@element-plus/icons-vue";
import {
getAllEmployees,
getTenantEmployees,
addEmployee,
editEmployee,
deleteEmployee,
getEmployeeInfo,
resetEmployeePassword,
changeEmployeePassword,
} from "@/api/employee";
import { getTenantDepartments, getDepartmentInfo } from "@/api/department";
import { getTenantPositions, getPositionsByDepartment, getPositionInfo } from "@/api/position";
import { useAuthStore } from "@/stores/auth";
<style lang="scss" scoped></style>
interface Employee {
id: number;
employeeNo: string;
name: string;
phone: string;
email: string;
department: string;
position: string;
status: number;
createTime: string;
tenant_id: number;
}
const authStore = useAuthStore();
const page = ref(1);
const pageSize = ref(10);
const total = ref(0);
const employees = ref<any[]>([]);
const departmentList = ref<any[]>([]);
const departmentTree = ref<any[]>([]);
const loadingDepartments = ref(false);
const positionList = ref<any[]>([]);
const loadingPositions = ref(false);
const loading = ref(false);
// ID
const getCurrentTenantId = () => {
if (authStore.user && authStore.user.tenant_id) {
return authStore.user.tenant_id;
}
const userInfo = localStorage.getItem('userInfo');
if (userInfo) {
try {
const user = JSON.parse(userInfo);
return user.tenant_id || user.tenantId || 0;
} catch (e) {
console.error('Failed to parse user info:', e);
}
}
return 0;
};
const fetchEmployees = async () => {
loading.value = true;
let tenantId = getCurrentTenantId ? getCurrentTenantId() : null;
try {
const res = await getTenantEmployees(tenantId);
//
let employeeList: any[] = [];
if (Array.isArray(res)) {
employeeList = res;
} else if (res?.data && Array.isArray(res.data)) {
employeeList = res.data;
} else if (res?.data?.data && Array.isArray(res.data.data)) {
employeeList = res.data.data;
} else if (res?.data) {
employeeList = res.data;
}
//
employees.value = employeeList.map((item: any) => {
//
let departmentName = '';
const departmentId = item.department_id || null;
if (departmentId) {
const deptInfo = departmentList.value.find(d => d.id === departmentId);
departmentName = deptInfo ? deptInfo.name : '';
}
//
let positionName = '';
const positionId = item.position_id || null;
if (positionId) {
const posInfo = positionList.value.find(p => p.id === positionId);
positionName = posInfo ? posInfo.name : '';
}
//
const createTime = item.create_time || item.createTime || null;
return {
id: item.id,
employeeNo: item.employee_no || item.employeeNo || '',
name: item.name || '',
phone: item.phone || '',
email: item.email || '',
department_id: departmentId,
departmentName: departmentName,
position_id: positionId,
positionName: positionName,
bankName: item.bank_name || item.bankName || '',
bankAccount: item.bank_account || item.bankAccount || '',
status: item.status || 1,
createTime: createTime
? new Date(createTime).toLocaleString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
})
: "",
};
});
total.value = employees.value.length;
} catch (e) {
employees.value = [];
total.value = 0;
ElMessage.error("获取员工列表失败");
} finally {
loading.value = false;
}
};
//
const buildDepartmentTree = (deptList: any[], tenantId?: number) => {
if (!deptList || deptList.length === 0) {
console.warn('部门列表为空');
return [];
}
// tenant_id
let filteredList = deptList;
if (tenantId !== undefined && tenantId !== null) {
filteredList = deptList.filter((dept) => {
const deptTenantId = dept.tenant_id || dept.tenantId;
return deptTenantId === tenantId || deptTenantId === 0;
});
}
const tree: any[] = [];
const map = new Map<number, any>();
//
filteredList.forEach((dept) => {
if (!dept.id) {
console.warn('部门数据缺少id字段:', dept);
return;
}
map.set(dept.id, {
...dept,
children: [],
});
});
//
// parent_id0null
filteredList.forEach((dept) => {
if (!dept.id) return;
const node = map.get(dept.id);
if (!node) return;
const parentId = dept.parent_id || 0;
// parent_id0null
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_idparent_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_idid
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_id0
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 {
// IDel-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(); //
}
// IDAPI
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);
}
}
}
// tickDOM
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>

View File

@ -1,13 +1,7 @@
<template>
<div>
</div>
<router-view />
</template>
<script setup>
<script setup></script>
</script>
<style lang="scss" scoped>
</style>
<style lang="scss" scoped></style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,450 @@
<template>
<div class="container-box">
<div class="header-bar">
<h2>职位管理</h2>
<div class="header-actions">
<el-button type="primary" @click="handleAddPosition">
<el-icon><Plus /></el-icon>
添加职位
</el-button>
<el-button @click="refresh">
<el-icon><Refresh /></el-icon>
刷新
</el-button>
</div>
</div>
<el-divider></el-divider>
<el-table :data="positions" style="width: 100%" v-loading="loading">
<el-table-column
prop="name"
label="职位名称"
width="200"
align="center"
/>
<el-table-column
prop="code"
label="职位编码"
width="150"
align="center"
/>
<el-table-column
prop="departmentName"
label="所属部门"
width="150"
align="center"
/>
<el-table-column
prop="level"
label="职位级别"
width="120"
align="center"
/>
<el-table-column
prop="description"
label="职位描述"
align="center"
min-width="200"
/>
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
{{ scope.row.status === 1 ? "启用" : "禁用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="sortOrder"
label="排序"
width="100"
align="center"
/>
<el-table-column
prop="createTime"
label="创建时间"
width="180"
align="center"
/>
<el-table-column label="操作" width="180" align="center" fixed="right">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)"
>编辑</el-button
>
<el-button
size="small"
type="danger"
@click="handleDelete(scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="pagination-bar">
<el-pagination
background
:current-page="page"
:page-size="pageSize"
:total="total"
@current-change="handlePageChange"
layout="total, prev, pager, next"
/>
</div>
<!-- Dialog for add/edit -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px">
<el-form :model="form" label-width="80px">
<el-form-item label="职位名称">
<el-input v-model="form.name" placeholder="请输入职位名称" />
</el-form-item>
<el-form-item label="职位编码">
<el-input v-model="form.code" placeholder="请输入职位编码" />
</el-form-item>
<el-form-item label="所属部门">
<el-select
v-model="form.department_id"
placeholder="请选择部门"
style="width: 100%"
:loading="loadingDepartments"
clearable
>
<el-option
v-for="dept in departmentList"
:key="dept.id"
:label="dept.name"
:value="dept.id"
/>
</el-select>
</el-form-item>
<el-form-item label="职位级别">
<el-input-number v-model="form.level" :min="0" />
</el-form-item>
<el-form-item label="职位描述">
<el-input
v-model="form.description"
type="textarea"
:rows="3"
placeholder="请输入职位描述"
/>
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="form.sort_order" :min="0" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="form.status" placeholder="请选择状态">
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Refresh } from "@element-plus/icons-vue";
import {
getTenantPositions,
addPosition,
editPosition,
deletePosition,
getPositionInfo,
} from "@/api/position";
import { getTenantDepartments } from "@/api/department";
import { useAuthStore } from "@/stores/auth";
interface Position {
id: number;
name: string;
code: string;
department_id: number;
level: number;
description: string;
status: number;
sort_order: number;
tenant_id: number;
}
const authStore = useAuthStore();
const page = ref(1);
const pageSize = ref(10);
const total = ref(0);
const positions = ref<any[]>([]);
const departmentList = ref<any[]>([]);
const loadingDepartments = ref(false);
const loading = ref(false);
// ID
const getCurrentTenantId = () => {
if (authStore.user && authStore.user.tenant_id) {
return authStore.user.tenant_id;
}
const userInfo = localStorage.getItem('userInfo');
if (userInfo) {
try {
const user = JSON.parse(userInfo);
return user.tenant_id || user.tenantId || 0;
} catch (e) {
console.error('Failed to parse user info:', e);
}
}
return 0;
};
//
const fetchDepartments = async () => {
loadingDepartments.value = true;
try {
const tenantId = getCurrentTenantId();
const res = await getTenantDepartments(tenantId);
if (res.code === 0 && res.data) {
departmentList.value = Array.isArray(res.data) ? res.data : [];
} else {
departmentList.value = [];
}
} catch (error: any) {
console.error('获取部门列表失败:', error);
departmentList.value = [];
} finally {
loadingDepartments.value = false;
}
};
const fetchPositions = async () => {
loading.value = true;
let tenantId = getCurrentTenantId ? getCurrentTenantId() : null;
try {
const res = await getTenantPositions(tenantId);
let posList: any[] = [];
if (Array.isArray(res)) {
posList = res;
} else if (res?.data && Array.isArray(res.data)) {
posList = res.data;
} else if (res?.data?.data && Array.isArray(res.data.data)) {
posList = res.data.data;
} else if (res?.data) {
posList = res.data;
}
positions.value = posList.map((item: any) => {
//
let departmentName = '';
const departmentId = item.department_id || null;
if (departmentId) {
const deptInfo = departmentList.value.find(d => d.id === departmentId);
departmentName = deptInfo ? deptInfo.name : '';
}
const createTime = item.create_time || item.createTime || null;
return {
id: item.id,
name: item.name || '',
code: item.code || '',
department_id: departmentId,
departmentName: departmentName,
level: item.level || 0,
description: item.description || '',
status: item.status || 1,
sort_order: item.sort_order || item.sortOrder || 0,
createTime: createTime
? new Date(createTime).toLocaleString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
})
: "",
};
});
total.value = positions.value.length;
} catch (e) {
positions.value = [];
total.value = 0;
ElMessage.error("获取职位列表失败");
} finally {
loading.value = false;
}
};
onMounted(async () => {
await fetchDepartments();
fetchPositions();
});
const handlePageChange = (p: number) => {
page.value = p;
};
const dialogVisible = ref(false);
const dialogTitle = ref("");
const isEdit = ref(false);
const form = ref<any>({
id: null,
name: "",
code: "",
department_id: null,
level: 0,
description: "",
sort_order: 0,
status: 1,
tenant_id: null,
});
const refresh = async () => {
loading.value = true;
try {
await fetchDepartments();
await fetchPositions();
ElMessage.success('刷新成功');
} catch (error) {
ElMessage.error('刷新失败');
} finally {
loading.value = false;
}
};
const handleAddPosition = () => {
dialogTitle.value = "添加职位";
isEdit.value = false;
let tenantId = null;
const cachedUser = localStorage.getItem("userInfo");
if (cachedUser) {
try {
const userInfo = JSON.parse(cachedUser);
tenantId = userInfo.tenant_id || null;
} catch (e) {
tenantId = null;
}
}
form.value = {
id: 0,
name: "",
code: "",
department_id: null,
level: 0,
description: "",
sort_order: 0,
status: 1,
tenant_id: tenantId,
};
dialogVisible.value = true;
};
const handleEdit = async (position: Position) => {
dialogTitle.value = "编辑职位";
isEdit.value = true;
try {
const res = await getPositionInfo(position.id);
const data = res.data || res;
const tenantId = getCurrentTenantId();
form.value = {
id: data.id,
name: data.name || '',
code: data.code || '',
department_id: data.department_id || null,
level: data.level || 0,
description: data.description || '',
sort_order: data.sort_order || data.sortOrder || 0,
status: data.status || 1,
tenant_id: data.tenant_id || tenantId,
};
} catch (e) {
ElMessage.error("加载职位信息失败");
return;
}
dialogVisible.value = true;
};
const submitForm = async () => {
try {
if (isEdit.value) {
if (!form.value.id || form.value.id === 0) {
ElMessage.error("职位ID不能为空请重新选择职位");
return;
}
const submitData: any = {
name: form.value.name,
code: form.value.code,
department_id: form.value.department_id || 0,
level: form.value.level,
description: form.value.description,
sort_order: form.value.sort_order,
status: form.value.status,
};
await editPosition(form.value.id, submitData);
ElMessage.success("更新成功");
dialogVisible.value = false;
fetchPositions();
} else {
const submitData: any = {
name: form.value.name,
code: form.value.code,
department_id: form.value.department_id || 0,
level: form.value.level,
description: form.value.description,
sort_order: form.value.sort_order,
status: form.value.status,
};
if (form.value.tenant_id) {
submitData.tenant_id = form.value.tenant_id;
}
await addPosition(submitData);
ElMessage.success("添加成功");
dialogVisible.value = false;
fetchPositions();
}
} catch (e: any) {
const errorMsg = e?.response?.data?.message || e?.message || "操作失败";
ElMessage.error(errorMsg);
}
};
const handleDelete = async (position: Position) => {
ElMessageBox.confirm("确认删除该职位?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
await deletePosition(position.id);
ElMessage.success("删除成功");
fetchPositions();
} catch (e) {
ElMessage.error("删除失败");
}
});
};
</script>
<style lang="less" scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-size: 1.4rem;
font-weight: 700;
}
}
</style>

View File

@ -35,6 +35,16 @@
align="center"
min-width="200"
/>
<el-table-column prop="department" label="部门" width="150" align="center">
<template #default="scope">
<span>{{ scope.row.departmentName || '未分配' }}</span>
</template>
</el-table-column>
<el-table-column prop="position" label="职位" width="150" align="center">
<template #default="scope">
<span>{{ scope.row.positionName || '未分配' }}</span>
</template>
</el-table-column>
<el-table-column prop="role" label="角色" width="150" align="center">
<template #default="scope">
<el-tag :type="getRoleTagType(scope.row.roleName)">
@ -140,6 +150,39 @@
<el-form-item label="邮箱" v-if="dialogTitle !== '修改密码'">
<el-input v-model="form.email" />
</el-form-item>
<el-form-item label="部门" v-if="dialogTitle !== '修改密码'">
<el-select
v-model="form.department_id"
placeholder="请选择部门"
style="width: 100%"
:loading="loadingDepartments"
clearable
@change="handleDepartmentChange"
>
<el-option
v-for="dept in departmentList"
:key="dept.id"
:label="dept.name"
:value="dept.id"
/>
</el-select>
</el-form-item>
<el-form-item label="职位" v-if="dialogTitle !== '修改密码'">
<el-select
v-model="form.position_id"
placeholder="请选择职位"
style="width: 100%"
:loading="loadingPositions"
clearable
>
<el-option
v-for="pos in positionList"
:key="pos.id"
:label="pos.name"
:value="pos.id"
/>
</el-select>
</el-form-item>
<el-form-item label="角色" v-if="dialogTitle !== '修改密码'">
<el-select
v-model="form.role"
@ -211,6 +254,10 @@ const total = ref(0);
const users = ref<any[]>([]);
const roleList = ref<any[]>([]);
const loadingRoles = ref(false);
const departmentList = ref<any[]>([]);
const loadingDepartments = ref(false);
const positionList = ref<any[]>([]);
const loadingPositions = ref(false);
const loading = ref(false);
// ID
@ -249,6 +296,61 @@ const fetchRoles = async () => {
}
};
//
const fetchDepartments = async () => {
loadingDepartments.value = true;
try {
const tenantId = getCurrentTenantId();
const res = await getTenantDepartments(tenantId);
if (res.code === 0 && res.data) {
departmentList.value = Array.isArray(res.data) ? res.data : [];
} else {
departmentList.value = [];
}
} catch (error: any) {
console.error('获取部门列表失败:', error);
departmentList.value = [];
} finally {
loadingDepartments.value = false;
}
};
//
const fetchPositions = async (departmentId?: number) => {
loadingPositions.value = true;
try {
const tenantId = getCurrentTenantId();
let res;
if (departmentId && departmentId > 0) {
//
res = await getPositionsByDepartment(departmentId);
} else {
//
res = await getTenantPositions(tenantId);
}
if (res.code === 0 && res.data) {
positionList.value = Array.isArray(res.data) ? res.data : [];
} else {
positionList.value = [];
}
} catch (error: any) {
console.error('获取职位列表失败:', error);
positionList.value = [];
} finally {
loadingPositions.value = false;
}
};
//
const handleDepartmentChange = (departmentId: number | null) => {
form.value.position_id = null; //
if (departmentId && departmentId > 0) {
fetchPositions(departmentId);
} else {
fetchPositions(); //
}
};
//
const getRoleTagType = (roleName: string) => {
if (!roleName) return 'info';
@ -311,6 +413,22 @@ const fetchUsers = async () => {
roleName = roleInfo ? roleInfo.roleName : '';
}
//
let departmentName = '';
const departmentId = item.department_id || null;
if (departmentId) {
const deptInfo = departmentList.value.find(d => d.id === departmentId);
departmentName = deptInfo ? deptInfo.name : '';
}
//
let positionName = '';
const positionId = item.position_id || null;
if (positionId) {
const posInfo = positionList.value.find(p => p.id === positionId);
positionName = posInfo ? posInfo.name : '';
}
// last_login_time
const lastLoginTime = item.last_login_time || item.lastLoginTime || null;
// IP last_login_ip
@ -323,6 +441,10 @@ const fetchUsers = async () => {
email: item.email,
role: roleValue,
roleName: roleName,
department_id: departmentId,
departmentName: departmentName,
position_id: positionId,
positionName: positionName,
status: item.status,
lastLoginTime: lastLoginTime
? new Date(lastLoginTime).toLocaleString("zh-CN", {
@ -348,8 +470,12 @@ const fetchUsers = async () => {
};
onMounted(async () => {
//
await fetchRoles();
//
await Promise.all([
fetchRoles(),
fetchDepartments(),
fetchPositions(),
]);
fetchUsers();
});
@ -389,7 +515,11 @@ const currentForm = computed(() => {
const refresh = async () => {
loading.value = true;
try {
await fetchRoles();
await Promise.all([
fetchRoles(),
fetchDepartments(),
fetchPositions(),
]);
await fetchUsers();
ElMessage.success('刷新成功');
} catch (error) {
@ -422,9 +552,13 @@ const handleAddUser = () => {
password: "",
email: "",
role: null, // role ID
department_id: null,
position_id: null,
status: "active",
tenant_id: tenantId,
};
//
fetchPositions();
dialogVisible.value = true;
};
@ -456,9 +590,18 @@ const handleEdit = async (user: User) => {
password: "",
email: data.email,
role: roleValue, // role ID
department_id: data.department_id || null,
position_id: data.position_id || null,
status: statusStr,
tenant_id: data.tenant_id || tenantId,
};
//
if (form.value.department_id) {
fetchPositions(form.value.department_id);
} else {
fetchPositions(); //
}
} catch (e) {
ElMessage.error("加载用户失败");
return;
@ -542,6 +685,14 @@ const submitForm = async () => {
submitData.role = form.value.role; // role
}
// ID
if (form.value.department_id) {
submitData.department_id = form.value.department_id;
}
if (form.value.position_id) {
submitData.position_id = form.value.position_id;
}
if (form.value.tenant_id) {
submitData.tenant_id = form.value.tenant_id;
}
@ -573,6 +724,14 @@ const submitForm = async () => {
submitData.role = form.value.role; // role
}
// ID
if (form.value.department_id) {
submitData.department_id = form.value.department_id;
}
if (form.value.position_id) {
submitData.position_id = form.value.position_id;
}
if (form.value.tenant_id) {
submitData.tenant_id = form.value.tenant_id;
}

View File

@ -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,
},
}
}

View 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()
}

View 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()
}

View 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()
}

View File

@ -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{}{

View 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;

View 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;

View File

@ -0,0 +1,15 @@
-- 添加组织架构管理菜单
-- 注意:请根据实际情况修改 parent_id92 是 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);

View 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 '父部门ID0表示顶级部门',
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;

View 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 '父部门ID0表示顶级部门',
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;

View 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;

View 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;

View 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
View 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
}

View File

@ -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
}

View File

@ -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
View 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
}

View File

@ -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 {

View File

@ -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")