企业日常运营中,活动组织、考勤管理和请假审批是三项高频且容易产生协作成本的业务流程。传统处理方式往往面临以下痛点:纸质审批流转慢、考勤数据统计滞后、跨部门活动协调困难。我们团队基于实际企业需求,开发了这套多角色协同的管理系统,核心目标是通过数字化手段实现三个关键提升:
系统采用前后端分离架构,前端使用Vue.js 2.x(兼容Element UI),后端基于Spring Boot+MyBatis Plus构建RESTful API。这种技术选型主要基于:
实际开发中发现,采用Vuex管理全局状态可显著减少组件间重复请求。例如员工提交请假后,无需手动刷新即可更新审批状态列表。
采用RBAC(基于角色的访问控制)模型,通过五张表实现:
sys_user(用户基础表)sys_role(角色定义表)sys_menu(菜单权限表)user_role(用户-角色关联)role_menu(角色-菜单关联)关键代码示例(后端):
java复制// 基于注解的权限控制
@PreAuthorize("@ss.hasRole('admin')")
@PostMapping("/assignRoles")
public R assignRoles(@RequestBody UserRoleDTO dto) {
userService.updateUserRoles(dto);
return R.ok();
}
前端权限控制要点:
v-if="$hasPerm('sys:user:add')"控制按钮显隐asyncRoutes支持弹性考勤设置:
javascript复制// 前端规则配置表单数据结构
attendanceRules: {
workTime: '09:00', // 标准上班时间
flexibleRange: 30, // 弹性区间(分钟)
lateThreshold: 10, // 迟到阈值
autoSignOut: '18:30' // 自动签退时间
}
采用WebSocket实现实时名额更新:
vue复制// 活动卡片组件
<template>
<el-card :loading="loading">
<div slot="header">
<span>{{ activity.title }}</span>
<el-tag v-if="remaining <= 5" type="danger">
仅剩{{ remaining }}个名额
</el-tag>
</div>
<el-button
@click="handleJoin"
:disabled="joined || remaining === 0">
{{ joined ? '已报名' : '立即参与' }}
</el-button>
</el-card>
</template>
<script>
export default {
data() {
return {
remaining: 0,
joined: false
}
},
mounted() {
this.$socket.on('activity_update', data => {
if(data.id === this.activity.id) {
this.remaining = data.remaining
}
})
}
}
</script>
通过工作流引擎实现多级审批:
mermaid复制graph TD
A[员工提交] --> B{请假天数≤3?}
B -->|是| C[直接主管审批]
B -->|否| D[部门总监审批]
C --> E[HR备案]
D --> E
实际开发中改用状态机实现:
javascript复制// 请假状态流转配置
const states = {
draft: {
submit: 'pending'
},
pending: {
approve: 'approved',
reject: 'rejected'
},
approved: {
complete: 'done'
}
}
前端采用虚拟滚动提升长列表性能:
vue复制<template>
<el-table
:data="applications"
row-key="id"
height="500"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="userName" label="申请人"></el-table-column>
<!-- 其他列 -->
</el-table>
<el-button-group>
<el-button @click="batchApprove">批量通过</el-button>
<el-button @click="batchReject">批量驳回</el-button>
</el-button-group>
</template>
后端对应接口需处理并发控制:
java复制@Transactional
public void batchApprove(List<Long> ids, String remark) {
List<LeaveApplication> list = listByIds(ids);
for (LeaveApplication app : list) {
if (app.getStatus() != 0) {
throw new RuntimeException("申请状态已变更");
}
app.setStatus(1);
app.setApproverRemark(remark);
}
updateBatchById(list);
}
采用DTO模式进行数据传输,典型接口示例:
java复制// 分页查询考勤记录
@GetMapping("/attendance/list")
public R list(AttendanceQueryDTO dto) {
Page<AttendanceVO> page = new Page<>(dto.getPageNum(), dto.getPageSize());
IPage<AttendanceVO> iPage = attendanceService.queryPage(page, dto);
return R.ok(iPage);
}
前端API封装策略:
javascript复制// api/attendance.js
export function getAttendanceList(params) {
return request({
url: '/attendance/list',
method: 'get',
params,
// 自动处理分页参数转换
transformRequest: [
function(data) {
return qs.stringify(data, { arrayFormat: 'repeat' })
}
]
})
}
使用MySQL窗口函数提高统计效率:
sql复制SELECT
user_id,
COUNT(*) AS total_days,
SUM(CASE WHEN status = 'normal' THEN 1 ELSE 0 END) AS normal_days,
SUM(CASE WHEN status = 'late' THEN 1 ELSE 0 END) AS late_days
FROM attendance
WHERE date BETWEEN '2023-07-01' AND '2023-07-31'
GROUP BY user_id WITH ROLLUP;
前端数据可视化采用ECharts:
javascript复制// 考勤统计图表配置
option = {
tooltip: { trigger: 'axis' },
legend: { data: ['正常', '迟到', '缺勤'] },
xAxis: { type: 'category', data: dates },
yAxis: { type: 'value' },
series: [
{ name: '正常', type: 'bar', stack: 'total', data: normalData },
{ name: '迟到', type: 'bar', stack: 'total', data: lateData },
{ name: '缺勤', type: 'bar', stack: 'total', data: absentData }
]
}
采用Redis分布式锁防止重复打卡:
java复制public boolean signIn(Long userId) {
String lockKey = "sign:lock:" + userId;
String requestId = UUID.randomUUID().toString();
try {
// 获取锁,设置10秒过期
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, 10, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("操作太频繁");
}
// 业务处理
return attendanceService.signIn(userId);
} finally {
// 释放锁
if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}
使用POI的SXSSFWorkbook处理大数据量Excel:
java复制// 分页查询+流式写入
public void exportAttendance(HttpServletResponse response) {
SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 内存保留100行
Sheet sheet = workbook.createSheet("考勤记录");
int pageSize = 1000;
for (int page = 1; ; page++) {
List<Attendance> list = mapper.selectPage(
new Page<>(page, pageSize),
null
).getRecords();
if (list.isEmpty()) break;
// 写入数据行...
}
response.setContentType("application/vnd.ms-excel");
workbook.write(response.getOutputStream());
workbook.dispose();
}
基于CSS媒体查询的断点设置:
css复制/* 小屏幕(手机) */
@media (max-width: 768px) {
.form-item {
width: 100%;
margin-bottom: 15px;
}
.el-dialog {
width: 90% !important;
}
}
通过jssdk实现原生体验:
javascript复制// 初始化企业微信SDK
wx.config({
debug: false,
appId: '', // 企业微信CorpID
timestamp: '',
nonceStr: '',
signature: '',
jsApiList: ['chooseImage', 'previewImage']
});
// 调用拍照接口
wx.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['camera'],
success: function(res) {
uploadFile(res.localIds[0]);
}
});
推荐使用Docker Compose编排服务:
yaml复制version: '3'
services:
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql/data:/var/lib/mysql
redis:
image: redis:alpine
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
使用Spring Boot Actuator暴露指标:
properties复制# application.properties
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
配合Prometheus采集数据:
yaml复制# prometheus.yml
scrape_configs:
- job_name: 'spring'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['backend:8080']
在三个月的实际运行中,系统日均处理考勤记录1200+条,审批流程平均耗时从原来的48小时缩短至4小时。特别在疫情期间,居家办公申请模块帮助企业快速适应了远程办公模式转型。