# Warehouse-System-Web
**Repository Path**: xiaolu-over/warehouse-system-web
## Basic Information
- **Project Name**: Warehouse-System-Web
- **Description**: SpringBoot+Vue前后端分离的仓库管理系统前端
- **Primary Language**: NodeJS
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2025-11-07
- **Last Updated**: 2025-11-07
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 1. 项目概述
项目是基于SpringBoot+Vue前后端分离的仓库管理系统
后端:SpringBoot + MybatisPlus
前端:Node.js + Vue + element-ui
数据库:mysql
# 2. 创建后端项目
## 2.1 创建模块
1. 创建项目模块

2. 导入项目依赖
pom.xml
```xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.7.5
com.wms
Warehouse-System
0.0.1-SNAPSHOT
Warehouse-System
Warehouse management system
11
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
8.0.30
runtime
org.projectlombok
lombok
true
com.baomidou
mybatis-plus-boot-starter
3.4.1
com.baomidou
mybatis-plus-generator
3.4.1
org.freemarker
freemarker
2.3.30
com.spring4all
spring-boot-starter-swagger
1.5.1.RELEASE
org.springframework.boot
spring-boot-maven-plugin
```
## 2.2 加入MybatisPlus支持
1. 导入依赖
```xml
com.baomidou
mybatis-plus-boot-starter
3.5.2
```
2. 创建并连接数据库

3. 配置端口和数据源
application.yml
```yml
server:
port: 8002
spring:
datasource:
url: jdbc:mysql://localhost:3306/wms?useUnicode=true&characterEncoding=utf-8&serveTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
```
4. 编写实体类
```java
@Data
public class User {
private int id;
private String no;
private String name;
private String password;
private int sex;
private int roleId;
private String phone;
private String isvalid;
}
```
5. 编写Mapper接口
```java
@Mapper
public interface UserMapper extends BaseMapper {
public List selectAll();
}
```
6. 编写Service接口
```java
public interface UserService extends IService {
public List selectAll();
}
```
7. 编写Service实现类
```java
@Service
public class UserServiceImpl extends ServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
public List selectAll(){
return userMapper.selectAll();
}
}
```
8. 编写配置文件
UserMapper.xml
```xml
```
9. 编写测试代码
```java
@RestController
public class testController {
@Autowired
private UserService userService;
@GetMapping
public List test(){
return userService.selectAll();
}
}
```
10. 测试结果

# 3. 代码生成器
> 简化开发:删除之前编写的实体类、接口、实现类、配置文件以及测试类,利用MyBatisPlus代码生成器自动生成代码
1. 导入依赖
> 注意:MybatisPlus版本用3.4.1,3.5版本的MybatisPlus会报错!
```xml
com.baomidou
mybatis-plus-generator
3.4.1
org.freemarker
freemarker
2.3.30
```
2. 编写代码生成器
> 参考MybatisPlus官网:https://baomidou.com/pages/d357af/
```java
package com.wms.common;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class CodeGenerator {
/**
*
* 读取控制台内容
*
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("linsuwen");
gc.setOpen(false);
gc.setSwagger2(true); //实体属性 Swagger2 注解
gc.setBaseResultMap(true); // XML ResultMap
gc.setBaseColumnList(true); // XML columList
//去掉service接口首字母的I, 如DO为User则叫UserService
gc.setServiceName("%sService");
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/wms?useUnicode=true&characterEncoding=utf-8&serveTimezone=UTC");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
//pc.setModuleName(scanner("模块名"));
//模块配置
pc.setParent("com.wms")
.setEntity("entity")
.setMapper("mapper")
.setService("service")
.setServiceImpl("service.Impl")
.setController("controller");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
// 公共父类
//strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
// 写于父类中的公共字段
//strategy.setSuperEntityColumns("id");
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
//strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
```
3. 运行代码生成器自动生成代码
生成结果:

# 4. 用户的增删改查
## 4.1 新增用户
```java
/*
* 新增用户
* @author linsuwen
* @date 2023/1/2 19:11
*/
@PostMapping("/save")
public boolean save(@RequestBody User user){
return userService.save(user);
}
```
## 4.2 删除用户
```java
/*
* 删除用户
* @author linsuwen
* @date 2023/1/2 19:15
*/
@GetMapping("/delete")
public boolean delete(Integer id){
return userService.removeById(id);
}
```
## 4.3 更新用户
```java
/*
* 更新用户
* @author linsuwen
* @date 2023/1/2 19:11
*/
@PostMapping("/update")
public boolean update(@RequestBody User user){
return userService.updateById(user);
}
```
```java
/*
* 新增或修改:存在用户则修改,否则新增用户
* @author linsuwen
* @date 2023/1/2 19:12
*/
@PostMapping("/saveOrUpdate")
public boolean saveOrUpdate(@RequestBody User user){
return userService.saveOrUpdate(user);
}
```
## 4.4 查询用户
```java
/*
* 查询全部用户
* @author linsuwen
* @date 2023/1/2 19:26
*/
@GetMapping("/list")
public List list(){
return userService.list();
}
```
```java
/*
* 模糊查询
* @author linsuwen
* @date 2023/1/2 19:36
*/
@PostMapping("/listP")
public List query(@RequestBody User user){
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName,user.getName());
return userService.list(wrapper);
}
```
# 5. 分页的处理
1. 参数的封装
> 注:可以不封装,在controller层用HashMap接收参数
```java
package com.wms.common;
/*
* 分页参数的封装类
* @author linsuwen
* @date 2023/1/2 19:53
*/
@Data
public class QueryPageParam {
//设置默认值
private static int PAGE_SIZE=20;
private static int PAGE_NUM=1;
private int pageSize=PAGE_SIZE;
private int pageNum=PAGE_NUM;
private HashMap param = new HashMap();
}
```
2. 添加分页拦截器
```java
/*
* MybatisPlus分页拦截器
* @author linsuwen
* @date 2023/1/2 20:06
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
```
3. 编写分页的Mapper方法
```java
/*
* 分页查询
* @author linsuwen
* @date 2023/1/2 19:48
*/
@PostMapping("/lsitPage")
public Result page(@RequestBody QueryPageParam query){
HashMap param = query.getParam();
String name = (String)param.get("name");
Page page = new Page();
page.setCurrent(query.getPageNum());
page.setSize(query.getPageSize());
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName,name);
IPage result = userService.page(page,wrapper);
return Result.success(result.getRecords(),result.getTotal());
}
```
4. 自定义SQL使用Wrapper
# 6. 返回前端数据的封装
> 让前端收到统一的数据,方便处理
```java
package com.wms.common;
import lombok.Data;
/*
* 返回前端统一数据的封装类
* @author linsuwen
* @date 2023/1/2 20:36
*/
@Data
public class Result {
private int code; //编码 200/400
private String msg; //成功/失败
private Long total; //总记录数
private Object data; //数据
public static Result fail(){
return result(400,"失败",0L,null);
}
public static Result success(){
return result(200,"成功",0L,null);
}
public static Result success(Object data){
return result(200,"成功",0L,data);
}
public static Result success(Object data,Long total){
return result(200,"成功",total,data);
}
private static Result result(int code,String msg,Long total,Object data){
Result res = new Result();
res.setData(data);
res.setMsg(msg);
res.setCode(code);
res.setTotal(total);
return res;
}
}
```
# 7. 创建前端项目
1. 创建一个名为 Warehouse-System-Web 的工程
```shell
npm init #项目初始化命令
#如果想直接生成 package.json 文件,那么可以使用命令
npm init -y
```
2. 安装依赖
```shell
#进入工程目录
cd Warehouse-System-Web
#安装vue-router
npm install vue-router --save-dev
#安装element-ui
npm i element-ui -S
#安装依赖
npm install
#安装SASS加载器
cnpm install sass-loader node-sass --save-dev
```
3. 启动项目
```shell
#启动测试
npm run serve
```

# 8. 编写前端页面
## 8.1 搭建页面布局
> 参考Element-ui Container 布局容器:https://element.eleme.cn/#/zh-CN/component/container
Index.vue:整体页面布局 Vue 组件
## 8.2 页面布局拆分
从 Index.vue 中拆分出 Aside.vue 和 Header.vue 组件后,然后在 Index.vue 再导入拆分出去的组件
```js
import Aside from "./Aside";
import Header from "./Header";
```
## 8.3 编写头部页面
> 编写步骤:
1. dropdown下拉
2. 菜单伸缩图标
3. 欢迎字样
4. 去除背景,加入下拉框
> 代码实现:
```vue
、
欢迎来到仓库管理系统
{{user.name}}
个人中心
退出登录
```
## 8.4 编写导航菜单页面
```vue
首页
{{item.menuname}}
```
## 8.5 菜单导航页面伸缩
菜单导航页面伸缩思路:
1. header点击图标提交
2. 父组件改变
3. aside子组件(collapse)
# 9. 安装axios与跨域处理
> **Axios:**Axios是一个基于promise 的 HTTP 库,可以用在浏览器和 node.js中。
>
> **Ajax:**Ajax即**A**synchronous **J**avascript **A**nd **X**ML(异步JavaScript和[ XML])在 2005年被Jesse James Garrett提出的新术语,用来描述一种使用现有技术集合的'新'方法,包括:HTML 或 XHTML,CSS,JavaScript,DOM,XML,XSLT以及最重要的 XMLHttpRequest。使用Ajax技术网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面,这使得程序能够更快地回应用户的操作。
>
> **Axios与Ajax的区别:**
>
> 1. axios是一个基于Promise的HTTP库,而ajax是对原生XHR的封装。
> 2. ajax技术实现了局部数据的刷新,而axios实现了对ajax的封装。
> **什么是跨域访问:**说到跨域访问,必须先解释一个名词:同源策略。所谓同源策略就是在浏览器端出于安全考量,向服务端发起请求必须满足:协议相同、Host(ip)相同、端口相同的条件,否则访问将被禁止,该访问也就被称为跨域访问。
>
> 虽然跨域访问被禁止之后,可以在一定程度上提高了应用的安全性,但也为开发带来了一定的麻烦。比如:我们开发一个前后端分离的易用,页面及js部署在一个主机的nginx服务中,后端接口部署在一个tomcat应用容器中,当前端向后端发起请求的时候一定是不符合同源策略的,也就无法访问。
>
> **SpringBoot下解决跨域问题的四种方式:**
>
> 1. 使用CorsFilter进行全局跨域配置
> 2. 重写WebMvcConfigurer的addCorsMappings方法(全局跨域配置)
> 3. 使用CrossOrigin注解(局部跨域配置)
> 4. 使用HttpServletResponse设置响应头(局部跨域配置)
>
> 参考文章:https://cloud.tencent.com/developer/news/472954
**安装axios与跨域处理:**
1. 安装axios
```shell
npm install axios --save
```
2. 在main.js全局引⼊axios
```shell
import axios from "axios";
Vue.prototype.$axios =axios;
```
3. 解决跨域问题
重写WebMvcConfigurer的addCorsMappings方法(全局跨域配置):
```java
package com.wms.common;
/*
* 解决跨域问题:重写WebMvcConfigurer的addCorsMappings方法(全局跨域配置)
* @author linsuwen
* @date 2023/1/3 1:30
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
//是否发送Cookie
.allowCredentials(true)
//放行哪些原始域
.allowedOriginPatterns("*")
.allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})
.allowedHeaders("*")
.exposedHeaders("*");
}
}
```
4. 测试请求
get请求的使⽤:
```js
this.$axios.get('http://localhost:8002/user/list').then(res=>{
console.log(res)
})
```
post请求的使用:
```js
this.$axios.post('http://localhost:8002/user/query',{}).then(res=>{
console.log(res)
})
```
5. 将地址设置为全局
main.js
```js
Vue.prototype.$httpUrl='http://localhost:8002' //将地址设置为全局
```
# 10. 登录退出功能
## 10.1 登录功能
1. 编写登录页面
Login.vue

2. 后台查询登录代码
```java
/*
* 用户登录
* @author linsuwen
* @date 2023/1/3 14:08
*/
@PostMapping("/login")
public Result login(@RequestBody User user){
//匹配账号和密码
List list = userService.lambdaQuery()
.eq(User::getNo,user.getNo())
.eq(User::getPassword,user.getPassword())
.list();
return list.size()>0?Result.success(list.get(0)):Result.fail();
}
```
3. 登录页面的路由跳转
安装路由插件
```shell
npm i vue-router@3.5.4
```
创建路由文件(router目录下的index.js文件),访问路由跳转到登录页面
```js
import VueRouter from 'vue-router';
const routes = [
{
path:'/',
name:'login',
component:()=>import('../components/Login')
}
]
const router = new VueRouter({
mode:'history',
routes
})
export default router;
```
在main.js中注册路由
```js
import VueRouter from 'vue-router';
import router from './router';
Vue.use(VueRouter);
new Vue({
router,
render: h => h(App),
}).$mount('#app')
```
4. 主页的路由(接收路由)
App.vue
```vue
```
5. 登录成功后页面跳转到首页
...
http://localhost:8081/ 跳转到 http://localhost:8081/index
## 10.2 退出登录功能
1. 展示名字(Header.vue)
```vue
{{user.name}}
个人中心
退出登录
data(){
return {
user : JSON.parse(sessionStorage.getItem('CurUser'))
}
}
```
2. 退出登录事件
```vue
退出登录
```
3. 退出跳转、清空相关数据以及退出确认
```js
logout(){
console.log('logout')
this.$confirm('您确定要退出登录吗?', '提示', {
confirmButtonText: '确定', //确认按钮的文字显示
type: 'warning',
center: true, //文字居中显示
})
.then(() => {
this.$message({
type:'success',
message:'退出登录成功!'
})
this.$router.push("/")
sessionStorage.clear()
})
.catch(() => {
this.$message({
type:'info',
message:'已取消退出登录!'
})
})
}
```
## 10.3 个人中心
1. 编写页面
2. 路由跳转(Header.vue)
```vue
个人中心
methods:{
toUser(){
console.log('to_user')
this.$router.push("/Home")
}
}
```
3. 路由错误解决(router/index.js)
```js
const VueRouterPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (to) {
return VueRouterPush.call(this, to).catch(err => err)
}
```
# 11. 菜单展示
## 11.1 菜单跳转
1. 菜单增加router、高亮
```vue
```
2. 配置子菜单
```js
data(){
return {
menu:[
{
menuClick:'Admin',
menuName:'管路员管理',
menuIcon:'el-icon-s-custom'
},{
menuClick:'User',
menuName:'用户管理',
menuIcon:'el-icon-user-solid'
}
]
}
}
}
```
3. 模拟动态menu
```vue
{{item.menuName}}
```
## 11.2 动态路由
> **vuex状态管理:**
>
> vuex是专为vue.js应用程序开发的状态管理模式。它采用集中存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
>
> Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
>
> 状态管理有5个核心,分别是state、getter、mutation、action以及module。
> **动态路由的实现:**
1. 设计menu表和数据
```mysql
CREATE TABLE `menu` (
`id` int NOT NULL,
`menuCode` varchar(8) DEFAULT NULL COMMENT '菜单编码',
`menuName` varchar(16) DEFAULT NULL COMMENT '菜单名字',
`menuLevel` varchar(2) DEFAULT NULL COMMENT '菜单级别',
`menuParentCode` varchar(8) DEFAULT NULL COMMENT '菜单的父code',
`menuClick` varchar(16) DEFAULT NULL COMMENT '点击触发的函数',
`menuRight` varchar(8) DEFAULT NULL COMMENT '权限 0超级管理员,1表示管理员,2表示普通用户,可以用逗号组合使用',
`menuComponent` varchar(200) DEFAULT NULL,
`menuIcon` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
```
```mysql
INSERT INTO `menu` VALUES (1, '001', '管理员管理', '1', NULL, 'Admin', '0', 'admin/AdminManage.vue', 'el-icon-s-custom');
INSERT INTO `menu` VALUES (2, '002', '用户管理', '1', NULL, 'User', '0,1', 'user/UserManage.vue', 'el-icon-user-solid');
INSERT INTO `menu` VALUES (3, '003', '仓库管理', '1', NULL, 'Storage', '0,1', 'storage/StorageManage', 'el-icon-office-building');
INSERT INTO `menu` VALUES (4, '004', '物品分类管理', '1', NULL, 'Goodstype', '0,1', 'goodstype/GoodstypeManage', 'el-icon-menu');
INSERT INTO `menu` VALUES (5, '005', '物品管理 ', '1', NULL, 'Goods', '0,1,2', 'goods/GoodsManage', 'el-icon-s-management');
INSERT INTO `menu` VALUES (6, '006', '记录管理', '1', NULL, 'Record', '0,1,2', 'record/RecordManage', 'el-icon-s-order');
```
2. 生成menu对应的后端代码
```java
/*
* 根据用户身份获取菜单列表
* @author linsuwen
* @date 2023/1/3 20:48
*/
@GetMapping("/list")
public Result list(@RequestParam String roleId){
List list = menuService.lambdaQuery()
.like(Menu::getMenuright,roleId)
.list();
return Result.success(list);
}
```
3. 返回前端数据
思路:登录的时候一并查询 menu,这样只需要异步查询一次即可将菜单页面展示出来
修改后端UserController中的登录代码,将用户角色数据和用户角色对应的菜单数据返回给前端
```java
/*
* 用户登录
* @author linsuwen
* @date 2023/1/3 14:08
*/
@PostMapping("/login")
public Result login(@RequestBody User user){
//匹配账号和密码
List list = userService.lambdaQuery()
.eq(User::getNo,user.getNo())
.eq(User::getPassword,user.getPassword())
.list();
if(list.size()>0){
User user1 = list.get(0);
List