news 2026/7/1 10:34:57

分享一套锋哥原创的SpringBoot4+Vue3差旅(出差)报销管理系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
分享一套锋哥原创的SpringBoot4+Vue3差旅(出差)报销管理系统

大家好,我是Java1234_小锋老师,分享一套锋哥原创的SpringBoot4+Vue3差旅(出差)报销管理系统。

项目介绍

随着企业经营规模的不断扩大和异地业务的日益频繁,员工出差已成为企业日常经营活动中的常态。传统的差旅报销主要依赖纸质单据和人工审核,存在单据易丢失、填报不规范、审批流程冗长、统计汇总困难、财务对账效率低等突出问题,难以满足现代企业精细化、信息化管理的需求。因此,设计并实现一套高效、规范、易用的差旅(出差)报销管理系统具有重要的现实意义。

本系统采用前后端分离的架构思想进行设计与开发。后端基于 SpringBoot 框架搭建,利用其自动配置和起步依赖特性快速构建 RESTful 接口;持久层采用 MyBatis-Plus 框架,通过其内置的通用 Mapper、条件构造器和分页插件大幅简化数据库访问代码;数据库选用 MySQL 进行数据的持久化存储。前端采用 Vue3 框架并结合 Element Plus 组件库进行页面构建,通过 Axios 与后端进行数据交互,借助 Vue Router 和 Pinia 完成路由管理与状态管理,实现了组件化、响应式的用户界面。

系统按照角色划分为普通员工、审批人和系统管理员三类用户,主要实现了用户登录、个人中心、出差申请管理、报销单填报、报销审批、费用类型与部门等基础数据管理以及系统公告等功能模块。论文按照软件工程的思想,依次完成了系统的需求分析、概念结构设计(E-R 图)、功能结构设计、业务流程设计(时序图)、数据库设计以及各功能模块的编码实现,并对系统进行了功能测试。

测试结果表明,本系统功能完整、运行稳定、操作便捷,能够有效规范企业差旅报销流程,提高报销审批效率,降低人工成本,具有较好的实用价值和推广意义。

源码下载

链接: https://pan.baidu.com/s/1wOVtO2kPVJBtoJHYBTkc4Q?pwd=1234
提取码: 1234

系统展示

核心代码

package com.java1234.controller; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.java1234.common.BusinessException; import com.java1234.common.Result; import com.java1234.config.ClaimsHolder; import com.java1234.entity.ExpenseType; import com.java1234.service.ExpenseTypeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 费用类型管理控制器 */ @RestController @RequestMapping("/api/expense-type") public class ExpenseTypeController { @Autowired private ExpenseTypeService expenseTypeService; /** * 分页查询费用类型列表 */ @GetMapping("/page") public Result<IPage<ExpenseType>> page( @RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "10") int pageSize, @RequestParam(required = false) String typeName) { checkAdmin(); LambdaQueryWrapper<ExpenseType> wrapper = new LambdaQueryWrapper<>(); if (StrUtil.isNotBlank(typeName)) { wrapper.like(ExpenseType::getTypeName, typeName); } wrapper.orderByDesc(ExpenseType::getCreateTime); return Result.success(expenseTypeService.page(new Page<>(pageNum, pageSize), wrapper)); } /** * 获取所有启用的费用类型 */ @GetMapping("/list") public Result<List<ExpenseType>> list() { return Result.success(expenseTypeService.list( new LambdaQueryWrapper<ExpenseType>().eq(ExpenseType::getStatus, 1))); } /** * 新增费用类型 */ @PostMapping public Result<Void> add(@RequestBody ExpenseType expenseType) { checkAdmin(); expenseTypeService.save(expenseType); return Result.success(); } /** * 修改费用类型 */ @PutMapping public Result<Void> update(@RequestBody ExpenseType expenseType) { checkAdmin(); expenseTypeService.updateById(expenseType); return Result.success(); } /** * 删除费用类型 */ @DeleteMapping("/{id}") public Result<Void> delete(@PathVariable Long id) { checkAdmin(); expenseTypeService.removeById(id); return Result.success(); } /** * 校验管理员权限 */ private void checkAdmin() { if (!ClaimsHolder.isAdmin()) { throw new BusinessException(403, "无权操作"); } } }
<template> <el-container class="layout-container"> <el-aside :width="isCollapse ? '64px' : '220px'" class="sidebar"> <div class="logo"> <el-icon :size="24"><Suitcase /></el-icon> <span v-show="!isCollapse">差旅报销系统</span> </div> <el-menu :default-active="route.path" :collapse="isCollapse" router background-color="transparent" text-color="#bfcbd9" active-text-color="#409eff" > <el-menu-item index="/home"> <el-icon><HomeFilled /></el-icon> <span>首页</span> </el-menu-item> <el-menu-item index="/reimbursement"> <el-icon><Document /></el-icon> <span>报销单管理</span> </el-menu-item> <el-menu-item v-if="userStore.isAdmin" index="/audit"> <el-icon><Checked /></el-icon> <span>审批管理</span> </el-menu-item> <el-sub-menu v-if="userStore.isAdmin" index="system"> <template #title> <el-icon><Setting /></el-icon> <span>系统管理</span> </template> <el-menu-item index="/user">用户管理</el-menu-item> <el-menu-item index="/dept">部门管理</el-menu-item> <el-menu-item index="/expense-type">费用类型</el-menu-item> </el-sub-menu> <el-menu-item index="/profile"> <el-icon><User /></el-icon> <span>个人中心</span> </el-menu-item> </el-menu> </el-aside> <el-container> <el-header class="header"> <div class="header-left"> <el-icon class="collapse-btn" @click="isCollapse = !isCollapse"> <Fold v-if="!isCollapse" /><Expand v-else /> </el-icon> <el-breadcrumb separator="/"> <el-breadcrumb-item>{{ route.meta.title || '首页' }}</el-breadcrumb-item> </el-breadcrumb> </div> <div class="header-right"> <el-dropdown @command="handleCommand"> <span class="user-info"> <el-avatar :size="32" style="background:#409eff">{{ userStore.user?.realName?.charAt(0) }}</el-avatar> <span class="username">{{ userStore.user?.realName }}</span> <el-icon><ArrowDown /></el-icon> </span> <template #dropdown> <el-dropdown-menu> <el-dropdown-item command="profile">个人中心</el-dropdown-item> <el-dropdown-item command="logout" divided>退出登录</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </el-header> <el-main class="main-content"> <router-view /> </el-main> </el-container> </el-container> </template> <script setup> import { ref } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useUserStore } from '@/stores/user' const route = useRoute() const router = useRouter() const userStore = useUserStore() const isCollapse = ref(false) function handleCommand(cmd) { if (cmd === 'logout') { userStore.logout() router.push('/login') } else if (cmd === 'profile') { router.push('/profile') } } </script> <style scoped> .layout-container { height: 100vh; } .sidebar { background: linear-gradient(180deg, #1a1f3a 0%, #2d3561 100%); transition: width 0.3s; overflow: hidden; } .logo { height: 60px; display: flex; align-items: center; justify-content: center; gap: 8px; color: #fff; font-size: 16px; font-weight: bold; border-bottom: 1px solid rgba(255,255,255,0.1); } .sidebar :deep(.el-menu) { border-right: none; } .sidebar :deep(.el-menu-item.is-active) { background: rgba(64, 158, 255, 0.15) !important; border-right: 3px solid #409eff; } .header { display: flex; align-items: center; justify-content: space-between; background: #fff; box-shadow: 0 1px 4px rgba(0,0,0,0.08); padding: 0 20px; } .header-left { display: flex; align-items: center; gap: 16px; } .collapse-btn { font-size: 20px; cursor: pointer; color: #606266; } .user-info { display: flex; align-items: center; gap: 8px; cursor: pointer; } .username { color: #303133; font-size: 14px; } .main-content { background: #f0f2f5; padding: 0; overflow-y: auto; } </style>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/1 10:29:19

3个实战配置深度解析:Kafka-UI企业级权限管控最佳实践

3个实战配置深度解析&#xff1a;Kafka-UI企业级权限管控最佳实践 【免费下载链接】kafka-ui Open-Source Web UI for Apache Kafka Management 项目地址: https://gitcode.com/GitHub_Trending/ka/kafka-ui Apache Kafka-UI作为开源Kafka管理Web界面&#xff0c;在企业…

作者头像 李华
网站建设 2026/7/1 10:27:27

ntfy-android附件下载失败排查指南:配置映射错误的技术解析

ntfy-android附件下载失败排查指南&#xff1a;配置映射错误的技术解析 【免费下载链接】ntfy-android Android app for ntfy.sh 项目地址: https://gitcode.com/gh_mirrors/nt/ntfy-android "为什么我的附件总是下载失败&#xff1f;"——这是许多ntfy-andro…

作者头像 李华
网站建设 2026/7/1 10:27:22

HS2-HF Patch完整汉化教程:3步快速实现HoneySelect2完美体验

HS2-HF Patch完整汉化教程&#xff1a;3步快速实现HoneySelect2完美体验 【免费下载链接】HS2-HF_Patch Automatically translate, uncensor and update HoneySelect2! 项目地址: https://gitcode.com/gh_mirrors/hs/HS2-HF_Patch HS2-HF Patch是HoneySelect2玩家的终极…

作者头像 李华
网站建设 2026/7/1 10:19:35

5个核心功能揭秘:d2s-editor如何让暗黑2存档编辑变得如此简单

5个核心功能揭秘&#xff1a;d2s-editor如何让暗黑2存档编辑变得如此简单 【免费下载链接】d2s-editor 项目地址: https://gitcode.com/gh_mirrors/d2/d2s-editor 还在为暗黑破坏神2存档修改的复杂性而头疼吗&#xff1f;想象一下&#xff0c;你可以像编辑电子表格一样…

作者头像 李华