尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于SSM+Vue的企业办公系统开发实践

基于SSM+Vue的企业办公系统开发实践 1. 项目概述这是一个基于SSM框架和Vue.js的企业内部管理系统主要包含三大功能模块活动报名、居家办公申请和考勤请假管理。系统采用前后端分离架构前端使用Vue.js实现后端采用SpringSpringMVCMyBatisSSM框架设计了三种不同权限的用户角色。我在实际开发这类系统时发现很多企业都在寻找能够整合日常办公流程的一体化解决方案。这个系统正好满足了企业将分散的行政事务集中管理的需求特别是后疫情时代居家办公场景的常态化使得这类系统的实用性大大提升。2. 系统架构设计2.1 技术栈选型前端技术栈Vue 2.x考虑到企业级稳定性和生态成熟度Element UI提供丰富的组件库Axios处理HTTP请求Vue Router实现前端路由Vuex状态管理选择Vue而不是React或Angular的主要考虑是学习曲线平缓适合企业内部开发团队快速上手丰富的UI组件库选择与后端SSM框架对接的成熟方案多后端技术栈Spring 5.xIoC和AOP核心SpringMVCWeb层MyBatis 3.x持久层MySQL 8.0关系型数据库Redis缓存和会话管理SSM框架的优势在于轻量级且性能良好国内Java开发者熟悉度高与Vue前端对接的成熟方案多2.2 系统模块划分├── 活动报名模块 │ ├── 活动发布 │ ├── 报名管理 │ ├── 签到管理 │ └── 数据统计 ├── 居家办公模块 │ ├── 申请提交 │ ├── 审批流程 │ ├── 办公日志 │ └── 考勤关联 └── 考勤请假模块 ├── 请假申请 ├── 审批流程 ├── 考勤统计 └── 异常处理3. 核心功能实现3.1 活动报名模块实现数据库设计关键表CREATE TABLE activity ( id int(11) NOT NULL AUTO_INCREMENT, title varchar(100) NOT NULL, content text, start_time datetime NOT NULL, end_time datetime NOT NULL, location varchar(200) NOT NULL, max_people int(11) DEFAULT NULL, status tinyint(4) DEFAULT 0 COMMENT 0-未开始 1-进行中 2-已结束, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ); CREATE TABLE activity_apply ( id int(11) NOT NULL AUTO_INCREMENT, activity_id int(11) NOT NULL, user_id int(11) NOT NULL, apply_time datetime DEFAULT CURRENT_TIMESTAMP, status tinyint(4) DEFAULT 0 COMMENT 0-待审核 1-已通过 2-已拒绝, checkin_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY idx_activity_user (activity_id,user_id) );Vue前端关键代码活动列表组件template div classactivity-list el-table :dataactivities stylewidth: 100% el-table-column proptitle label活动名称/el-table-column el-table-column propstartTime label开始时间 width180 template slot-scopescope {{ formatDate(scope.row.startTime) }} /template /el-table-column el-table-column proplocation label地点/el-table-column el-table-column label操作 width180 template slot-scopescope el-button v-ifscope.row.status 0 typeprimary sizemini clickhandleApply(scope.row.id) 立即报名 /el-button el-tag v-else-ifscope.row.status 1 typesuccess已报名/el-tag /template /el-table-column /el-table /div /template script import { getActivityList, applyActivity } from /api/activity import { formatDate } from /utils/date export default { data() { return { activities: [] } }, created() { this.fetchData() }, methods: { async fetchData() { const res await getActivityList() this.activities res.data }, async handleApply(activityId) { try { await applyActivity({ activityId }) this.$message.success(报名成功) this.fetchData() } catch (error) { this.$message.error(error.message) } }, formatDate } } /script3.2 居家办公模块实现核心业务逻辑员工提交居家办公申请需填写办公日期范围居家办公原因紧急联系人方式直接主管审批HR备案系统自动关联考勤记录状态机设计public enum RemoteWorkStatus { PENDING(0, 待审批), APPROVED(1, 已批准), REJECTED(2, 已拒绝), CANCELLED(3, 已取消); // 省略构造函数和getter方法 }审批流程实现RestController RequestMapping(/remote-work) public class RemoteWorkController { Autowired private RemoteWorkService remoteWorkService; PostMapping(/apply) public Result apply(RequestBody RemoteWorkApplyDTO dto) { String currentUserId SecurityUtils.getCurrentUserId(); return remoteWorkService.apply(dto, currentUserId); } PostMapping(/approve) PreAuthorize(hasRole(MANAGER)) public Result approve(RequestParam Long id, RequestParam Boolean approved, RequestParam(required false) String comment) { String approverId SecurityUtils.getCurrentUserId(); return remoteWorkService.processApproval(id, approved, comment, approverId); } }3.3 考勤请假模块实现考勤规则配置attendance: rules: work-time: 09:00:00 off-work-time: 18:00:00 late-threshold: 30 # 迟到阈值(分钟) early-leave-threshold: 60 # 早退阈值(分钟) overtime-start: 19:00:00 # 加班开始时间请假类型枚举public enum LeaveType { ANNUAL(1, 年假), SICK(2, 病假), MATERNITY(3, 产假), PATERNITY(4, 陪产假), PERSONAL(5, 事假); // 省略实现 }考勤统计SQL示例SELECT u.real_name, COUNT(CASE WHEN a.status 1 THEN 1 END) AS normal_days, COUNT(CASE WHEN a.status 2 THEN 1 END) AS late_times, COUNT(CASE WHEN a.status 3 THEN 1 END) AS early_leave_times, COUNT(CASE WHEN a.status 4 THEN 1 END) AS absent_times, SUM(CASE WHEN a.overtime_hours IS NOT NULL THEN a.overtime_hours ELSE 0 END) AS total_overtime FROM attendance a JOIN user u ON a.user_id u.id WHERE a.date BETWEEN :startDate AND :endDate GROUP BY a.user_id;4. 权限设计与实现4.1 三种角色定义普通员工活动查看/报名居家办公申请/查看考勤打卡/请假申请/查看记录部门经理继承普通员工所有权限活动创建/管理本部门活动居家办公审批本部门申请考勤审批请假/查看部门统计HR管理员继承部门经理所有权限活动管理全公司活动居家办公查看全公司记录考勤管理全公司考勤规则/导出报表4.2 权限控制实现Spring Security配置Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(/api/activity/**).hasAnyRole(EMPLOYEE, MANAGER, HR) .antMatchers(/api/remote-work/apply).hasRole(EMPLOYEE) .antMatchers(/api/remote-work/approve).hasRole(MANAGER) .antMatchers(/api/attendance/report).hasRole(HR) .anyRequest().authenticated() .and() .addFilter(new JwtAuthenticationFilter(authenticationManager())) .addFilter(new JwtAuthorizationFilter(authenticationManager())) .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } }Vue前端路由守卫router.beforeEach((to, from, next) { const hasToken store.getters.token const hasRoles store.getters.roles store.getters.roles.length 0 if (hasToken) { if (to.path /login) { next(/) } else { if (hasRoles) { if (hasPermission(to.meta.roles, store.getters.roles)) { next() } else { next(/401) } } else { store.dispatch(user/getInfo).then(() { const roles store.getters.roles generateRoutes(roles).then(accessRoutes { router.addRoutes(accessRoutes) next({ ...to, replace: true }) }) }) } } } else { if (whiteList.includes(to.path)) { next() } else { next(/login) } } })5. 前后端交互设计5.1 API规范采用RESTful风格设计部分示例模块方法路径描述活动管理GET/api/activities获取活动列表POST/api/activities创建新活动居家办公GET/api/remote-works获取申请列表POST/api/remote-works/approve审批居家办公申请考勤管理GET/api/attendance/records获取考勤记录POST/api/attendance/clock-in打卡5.2 数据格式示例请求示例创建活动POST /api/activities { title: 季度技术分享会, content: 分享最新技术趋势..., startTime: 2023-06-15 14:00:00, endTime: 2023-06-15 17:00:00, location: 3楼会议室, maxPeople: 50 }响应示例{ code: 200, message: success, data: { id: 123, createTime: 2023-06-01 10:30:45 } }5.3 文件上传处理前端实现VueElement UItemplate el-upload action/api/upload :on-successhandleSuccess :before-uploadbeforeUpload el-button typeprimary上传附件/el-button /el-upload /template script export default { methods: { beforeUpload(file) { const isLt10M file.size / 1024 / 1024 10 if (!isLt10M) { this.$message.error(文件大小不能超过10MB) return false } return true }, handleSuccess(res) { if (res.code 200) { this.$emit(uploaded, res.data.url) } } } } /script后端实现SpringMVCPostMapping(/upload) public Result upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(文件不能为空); } try { String fileName FileUtils.upload(file); return Result.ok().data(url, /uploads/ fileName); } catch (IOException e) { log.error(文件上传失败, e); return Result.error(上传失败); } }6. 系统部署方案6.1 开发环境配置前端开发环境# 安装依赖 npm install # 启动开发服务器 npm run serve # 构建生产环境 npm run build后端开发环境JDK 1.8Maven 3.6MySQL 8.0Redis 6.06.2 生产环境部署前端部署Nginx配置示例server { listen 80; server_name yourdomain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://backend:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }后端部署Docker示例FROM openjdk:8-jdk-alpine VOLUME /tmp COPY target/ssm-system.jar app.jar ENTRYPOINT [java,-Djava.security.egdfile:/dev/./urandom,-jar,/app.jar]6.3 性能优化建议前端优化使用路由懒加载组件按需引入启用Gzip压缩配置合理的缓存策略后端优化启用MyBatis二级缓存高频查询使用Redis缓存数据库连接池配置优化异步处理耗时操作数据库优化为常用查询字段添加索引定期进行表优化合理设计表结构避免冗余7. 常见问题与解决方案7.1 跨域问题处理SpringBoot解决方案Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }Vue开发环境代理配置vue.config.jsmodule.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } } }7.2 权限控制常见问题问题1页面刷新后权限丢失解决方案在Vuex中持久化存储用户角色信息结合localStorage实现。// store/modules/user.js const state { roles: JSON.parse(localStorage.getItem(roles)) || [] } const mutations { SET_ROLES: (state, roles) { state.roles roles localStorage.setItem(roles, JSON.stringify(roles)) } }问题2按钮级权限控制解决方案自定义指令实现。// 注册全局指令 Vue.directive(permission, { inserted(el, binding, vnode) { const { value } binding const roles store.getters.roles if (value value instanceof Array value.length 0) { const hasPermission roles.some(role { return value.includes(role) }) if (!hasPermission) { el.parentNode el.parentNode.removeChild(el) } } else { throw new Error(需要指定权限数组如v-permission[admin]) } } })7.3 数据一致性保障场景审批操作与状态更新解决方案使用数据库事务和乐观锁。Transactional public Result processApproval(Long id, Boolean approved, String comment, String approverId) { // 1. 查询申请记录 RemoteWorkApply apply applyMapper.selectById(id); if (apply null) { return Result.error(申请记录不存在); } // 2. 检查当前状态 if (apply.getStatus() ! RemoteWorkStatus.PENDING.getValue()) { return Result.error(该申请已处理); } // 3. 更新状态 apply.setStatus(approved ? RemoteWorkStatus.APPROVED.getValue() : RemoteWorkStatus.REJECTED.getValue()); apply.setApproverId(approverId); apply.setApproveTime(new Date()); apply.setComment(comment); applyMapper.updateById(apply); // 4. 如果批准关联考勤记录 if (approved) { Attendance attendance new Attendance(); attendance.setUserId(apply.getUserId()); attendance.setDate(apply.getWorkDate()); attendance.setStatus(AttendanceStatus.REMOTE.getValue()); attendanceMapper.insert(attendance); } return Result.ok(); }8. 扩展功能建议8.1 移动端适配响应式布局优化使用rem适配不同屏幕针对移动端优化表单交互关键操作添加手势支持PWA支持添加manifest.json注册Service Worker实现离线缓存8.2 第三方集成微信通知集成审批结果通知活动提醒考勤异常提醒日历同步将批准的活动和请假同步到Outlook/Google日历支持iCal格式导出8.3 数据分析增强考勤热力图展示部门/个人考勤趋势异常考勤自动标记活动参与度分析部门参与度排名活动类型偏好分析请假类型统计各部门请假类型分布季节性变化趋势9. 项目总结与反思在实际开发这个系统的过程中有几个关键点值得特别注意状态管理复杂度随着业务规则增加各种审批状态和关联操作会变得复杂。建议使用状态模式封装状态转换逻辑绘制清晰的状态转换图编写详细的单元测试覆盖各种状态转换场景权限设计灵活性初期设计的三种角色可能无法满足后续需求变化。建议采用RBAC模型设计权限系统将权限与具体功能点解耦提供界面化权限配置功能数据一致性挑战特别是考勤数据与请假、居家办公记录的关联。建议建立统一的时间段冲突检测机制关键操作添加操作日志定期进行数据一致性检查性能优化点考勤统计报表预生成大数据量查询添加分页和缓存前端长列表使用虚拟滚动这个系统从技术实现角度看不算复杂但真正考验开发者的地方在于对办公场景的业务理解深度。比如如何处理调休与请假的抵扣关系、如何设计合理的审批流程、如何应对各种异常情况等。建议在开发类似系统前先充分调研目标企业的实际管理流程避免闭门造车。
返回列表