大家好,我是Java1234_小锋老师,分享一套锋哥原创的SpringBoot4+Vue3企业人事(人力资源)HR管理系统。

项目介绍
随着企业组织规模扩大和用工形式多样化,传统依赖电子表格与纸质单据的人事管理方式已难以满足考勤统计、请假审批、薪资核算和员工信息集中维护等业务要求。针对中小企业人力资源信息化建设的实际需求,本文设计并实现了一套基于 B/S 架构的企业人事(人力资源)HR 管理系统。
系统采用前后端分离开发模式。后端以 Java 17 为编程语言,基于 Spring Boot 4 构建 RESTful 服务,使用 MyBatis-Plus 完成对象关系映射与分页查询,数据持久化于 MySQL 8 数据库 db_hr_system;前端采用 Vue 3、Vite、Element Plus 与 ECharts,实现管理端与员工端一体化界面。身份认证采用 JWT 令牌机制,接口返回统一封装为 R 对象,日期在界面中按 yyyy-MM-dd、日期时间按 yyyy-MM-dd HH:mm:ss 格式展示。
系统面向管理员与员工两类角色。管理员可完成首页数据统计、部门与职位维护、员工档案管理、考勤查询、请假审批、薪资核算发放、公告发布、账号管理及个人中心操作;员工可进行上下班打卡、提交请假、查看本人考勤与薪资、阅读公告,并维护头像、个人资料和登录密码。测试结果表明,系统功能完整、流程清晰,能够支撑中小企业日常人事管理工作,对同类毕业设计与信息化实践具有参考价值。
源码下载
链接: https://pan.baidu.com/s/1fXAS1OBFTCfjHe2F5tlJIA?pwd=1234
提取码: 1234
系统展示






核心代码
package com.java1234.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.java1234.common.R;
import com.java1234.entity.Department;
import com.java1234.entity.Employee;
import com.java1234.service.DepartmentService;
import com.java1234.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
/**
* 部门控制器
*/
@RestController
@RequestMapping("/department")
public class DepartmentController {
@Autowired
private DepartmentService departmentService;
@Autowired
private EmployeeService employeeService;
/**
* 分页查询部门
*
* @param pageNum 页码
* @param pageSize 每页条数
* @param deptName 部门名称
* @param deptNo 部门编号
* @return 分页数据
*/
@GetMapping("/page")
public R page(@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
String deptName, String deptNo) {
Page<Department> page = departmentService.page(new Page<>(pageNum, pageSize),
new LambdaQueryWrapper<Department>()
.like(StringUtils.hasText(deptName), Department::getDeptName, deptName)
.like(StringUtils.hasText(deptNo), Department::getDeptNo, deptNo)
.orderByAsc(Department::getSortNum)
.orderByDesc(Department::getId));
Map<String, Object> data = new HashMap<>();
data.put("total", page.getTotal());
data.put("records", page.getRecords());
return R.ok(data);
}
/**
* 查询全部部门(下拉框使用)
*
* @return 部门列表
*/
@GetMapping("/list")
public R list() {
return R.ok(departmentService.list(new LambdaQueryWrapper<Department>()
.orderByAsc(Department::getSortNum)));
}
/**
* 新增部门
*
* @param department 部门信息
* @return 操作结果
*/
@PostMapping
public R add(@RequestBody Department department) {
long count = departmentService.count(new LambdaQueryWrapper<Department>()
.eq(Department::getDeptNo, department.getDeptNo()));
if (count > 0) {
return R.error("部门编号已存在");
}
department.setCreateTime(LocalDateTime.now());
departmentService.save(department);
return R.ok();
}
/**
* 修改部门
*
* @param department 部门信息
* @return 操作结果
*/
@PutMapping
public R update(@RequestBody Department department) {
departmentService.updateById(department);
return R.ok();
}
/**
* 删除部门
*
* @param id 主键ID
* @return 操作结果
*/
@DeleteMapping("/{id}")
public R delete(@PathVariable Integer id) {
long empCount = employeeService.count(new LambdaQueryWrapper<Employee>().eq(Employee::getDeptId, id));
if (empCount > 0) {
return R.error("该部门下存在员工,无法删除");
}
departmentService.removeById(id);
return R.ok();
}
}
<template>
<div class="page-card">
<div class="search-bar">
<el-select v-model="query.employeeId" placeholder="员工" clearable filterable style="width: 160px">
<el-option v-for="e in emps" :key="e.id" :label="e.empName" :value="e.id" />
</el-select>
<el-select v-model="query.status" placeholder="状态" clearable style="width: 120px">
<el-option v-for="s in ['正常','迟到','早退','缺勤','请假']" :key="s" :label="s" :value="s" />
</el-select>
<el-date-picker v-model="query.beginDate" value-format="YYYY-MM-DD" placeholder="开始日期" />
<el-date-picker v-model="query.endDate" value-format="YYYY-MM-DD" placeholder="结束日期" />
<el-button type="primary" @click="load">查询</el-button>
<el-button type="success" @click="open()">新增</el-button>
</div>
<el-table :data="table" stripe border style="width: 100%">
<el-table-column prop="empNo" label="工号" min-width="110" />
<el-table-column prop="empName" label="姓名" min-width="90" />
<el-table-column prop="attendanceDate" label="考勤日期" min-width="120" />
<el-table-column prop="checkInTime" label="上班打卡" min-width="170" />
<el-table-column prop="checkOutTime" label="下班打卡" min-width="170" />
<el-table-column prop="status" label="状态" min-width="90" />
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip />
<el-table-column label="操作" min-width="140" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="open(row)">编辑</el-button>
<el-button link type="danger" @click="remove(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pager"><el-pagination background layout="total, prev, pager, next" :total="total" v-model:current-page="query.pageNum" @current-change="load" /></div>
<el-dialog v-model="visible" :title="form.id ? '编辑考勤' : '新增考勤'" width="520px">
<el-form :model="form" label-width="90px">
<el-form-item label="员工">
<el-select v-model="form.employeeId" style="width:100%" filterable>
<el-option v-for="e in emps" :key="e.id" :label="e.empName" :value="e.id" />
</el-select>
</el-form-item>
<el-form-item label="考勤日期"><el-date-picker v-model="form.attendanceDate" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item>
<el-form-item label="上班打卡"><el-date-picker v-model="form.checkInTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%" /></el-form-item>
<el-form-item label="下班打卡"><el-date-picker v-model="form.checkOutTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%" /></el-form-item>
<el-form-item label="状态">
<el-select v-model="form.status" style="width:100%">
<el-option v-for="s in ['正常','迟到','早退','缺勤','请假']" :key="s" :label="s" :value="s" />
</el-select>
</el-form-item>
<el-form-item label="备注"><el-input v-model="form.remark" /></el-form-item>
</el-form>
<template #footer>
<el-button @click="visible=false">取消</el-button>
<el-button type="primary" @click="save">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { attendancePage, attendanceAdd, attendanceUpdate, attendanceDelete, empList } from '../../api'
const query = reactive({ pageNum: 1, pageSize: 10, employeeId: null, status: '', beginDate: '', endDate: '' })
const table = ref([]); const total = ref(0); const visible = ref(false); const emps = ref([]); const form = reactive({})
const load = async () => { const res = await attendancePage(query); table.value = res.data.records; total.value = res.data.total }
const open = (row) => { Object.keys(form).forEach(k => delete form[k]); Object.assign(form, row ? { ...row } : { status: '正常' }); visible.value = true }
const save = async () => { form.id ? await attendanceUpdate(form) : await attendanceAdd(form); ElMessage.success('保存成功'); visible.value=false; load() }
const remove = (row) => { ElMessageBox.confirm('确认删除?').then(async () => { await attendanceDelete(row.id); ElMessage.success('删除成功'); load() }) }
onMounted(async () => { emps.value = (await empList()).data || []; load() })
</script>



43万+

被折叠的 条评论
为什么被折叠?



