Vue+Springboot电商(购物)系统

8,576 阅读5分钟

购物系统

项目地址:vue-shopping-webapp | springboot-shopping-webapp

前言

你是否:

  • 毕业设计不知道做什么?学了Vue+Springboot不知道拿什么练手?
  • 看完 vue,还有 vueRouter 、vuex 、vue-cli、es6 (emm。。。学不动了?) 那就: 眼睛看过来,手摸手教你撸代码

1.项目简介

vue-shopping-webapp商家端是一个后台管理 spa 页面,用户端是一个web网站,它基于 vueelement-ui 采用了最新的前端技术栈,实现了登录、购物以及管理等一众数购物系统功能。后端采用springboot做数据处理,数据库采用的mysql,实现了典型的业务案例,它可以让你拥有前后端交互的项目经验,相信这个项目一定对你有帮助。

目前版本基于 webpack 4.0+ 和 vue-cli 2.x 版本构建,vue采用的是vue2.0,版本相关知识可以自行进官网进行了解

功能


- 登录 
  - 用户、商家登陆
  
- 页面
  - 用户页面
  - 商家spa页面
  
- 购物
  - 商品分类
  - 商品浏览
  - 商品购买
  
- 购物车
  - 加入购物车
  - 购物车信息(商品总价、数量等)
  - 购物车汇总购买
  
- 地址
  - 修改、增加、删除收货地址
  
- 管理
  - 商品上架、下架
  - 用户订单管理
  - 发货、退货、收货管理

- 评论
  - 用户评论
  - 商家管理评论
 
- Vant
 - van-sku

目录结构

vue

├── public                     # 静态资源
│   ├── img                    # 图片资源
├── src                        # 源代码
│   ├── api                    # 地区码
│   ├── components             # 全局公用组件
│   ├── css                    # 全局css样式
│   ├── images                 # 组件内部图片
│   ├── lib                    # css&&fonts
│   ├── router                 # 路由
│   ├── store                  # 全局store管理
│   ├── App.vue                # 入口页面
│   ├── main.js                # 入口文件 加载组件 初始化等
├── .gitignore.js              # git忽略文件设置
├── .babelrc.config.js         # babel-loader 配置
├── package.json               # package.json
└── vue.config.js              # vue-cli 配置

springboot

图片1.png

安装

# 克隆项目
git clone https://gitee.com/zhou_guo_xi/vue-shopping-webapp.git

# 安装依赖
npm install

# 启动服务
npm run dev

系统数据流图

sjlt.png

系统ER图

er.png

2.页面结构

home home1.png

从上图我们可以看见一些图标,以及一个轮播图,购物超市标题,购物超市下端一些功能栏。结构分三层,分别是header区域,main区域以及tabbar区域。其中main区域是一个router-view用于嵌套不同的页面。图标来源于:MUI

list list.png 用户能够看到的商品列表页面。有商品各种信息,包括商品编号、商品价格、商品标签、商品库存、商品图片、商品描述、商品数量、图文介绍、商品名称属性。

manage 111.png 管理页面是一个spa页面。商家可以查看在售商品,上下架商品,以及对订单进行一系列管理。

3.加入购物车

动画.gif 可以看到,商品的信息是记录到了购物车中,以及自动算出来了商品总价。动画效果用的vue动画钩子。

 <transition
      @before-enter="beforeEnter"
      @enter="enter"
      @after-enter="afterEnter"
    >
      <div class="ball" v-show="ballflag" ref="ball"></div>
    </transition>
 beforeEnter(el) {
      el.style.transform = "translate(0,0)";
    },
    enter(el, done) {
      el.offsetWidth;
      //获取小球的位置 
      const ballposition = this.$refs.ball.getBoundingClientRect();
      //获取购物车的位置
      const badgeposition = document
        .getElementById("badge")
        .getBoundingClientRect();
      const xDist = badgeposition.left - ballposition.left;
      const yDist = badgeposition.top - ballposition.top;

      el.style.transform = `translate(${xDist}px, ${yDist}px)`; //字符串的拼接
      el.style.transition = "all 1.5s ease";
      done();
    },
    afterEnter(el) {
      this.ballflag = !this.ballflag;
    },

在点击加入购物车后,调用vuex中的mutations中的方法同时传入对应商品参数:this.$store.commit("addToCar", goodsinfo);,将加入购物车的数据存储在state中。mutations中的方法如下:

addToCar(state, goodsinfo) {
            var flag = false //如果没有找到对应的商品
            state.car.some(item => {
                //点击加入购物车时,把商品信息保存到car中    1.如果购物车中已经有这个对应的商品,那么只需要跟新数量 
                if (item.id == goodsinfo.id) {
                    item.count += parseInt(goodsinfo.count)
                    flag = true
                    return true
                }
            })
            if (flag == false) {  //没有找到对应的商品就直接加入到car中
                state.car.push(goodsinfo)
            }
            localStorage.setItem('car', JSON.stringify(state.car))  //本地存储加入购物车的数量
        }

如果需要对购物车数据再做处理,比如更新购物车商品数据,删除购物车商品数据,直接操作state中保存的car对象即可,而car对象是从本地取的值:var car = JSON.parse(localStorage.getItem('car') || '[]')

4.购买流程

购买流程有购物车合并购买和正常购买,两者类似,这里仅仅演示购物车购买。

1. 上架商品

33.gif 其中,图片上传部分代码:

SimpleDateFormat sdf = new SimpleDateFormat("/yyyy/MM/dd");
@PostMapping("/img")
public Map<String, Object> flailed(MultipartFile file, HttpServletRequest req){
    Map<String, Object> result = new HashMap<>();  //hashmap是map接口主要实现类,hashmap可以去重,效率高但无序
    String originName = file.getOriginalFilename();//file是前端来的数据
    if(!originName.endsWith(".jpg")){
        result.put("status", "error1");
        result.put("msg", "文件类型不对");

        return  result;
    }
    String format = sdf.format(new Date());
    String realPath  = "D:/大学4年/大三上学期/images";//
    File flooder = new File(realPath);
    if(!flooder.exists()){
        flooder.mkdirs();
    }
  String newName =   UUID.randomUUID().toString() + ".jpg";  //防止文件重名
        try {
            file.transferTo(new File(flooder, newName));
            String url = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort()
                    + "/img/"
                    + newName;
            result.put("status", "success");
            result.put("url", url);

        } catch (IOException e){
            result.put("status", "error2");
            result.put("msg", e.getMessage());
        }
        return  result;
}
2. 购物车商品购买

44.gif 这里面的逻辑比较多,但并不难。有订单信息追踪以及修改(如订单支付状态,收货地址)。购物车合并购买依靠的是数据库主外键联系,购物车购买以后在购物车订单表中生成一条订单数据,订单数据包括订单id,是一个外键,与之联系的是购物车商品表,根据刚刚的订单id生成对应购物车购买的商品信息:买了几个商品就有几条数据,几条数据共享一个订单id,不用的是各自的信息,如商品名称,商品单价,选中数量等等。这样根据订单id就能查询到订单所有信息,供商家管理订单。

3. 商家管理

555.png

666.png 商家可以看到刚刚客户购买的订单,然后进行接下来的操作。比如发货等。

5. 附加

1. 跨域解决

在后端建立CrosConfig类,把代码拷过去即可,即拷即用。

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry){
        registry.addMapping("/**").
                allowedOriginPatterns("*").
                allowedMethods("GET","HEAD", "POST","PUT","DELETE").
                allowCredentials(true).
                maxAge(3600).
                allowedHeaders("*");
    }
}
2. 前端token登陆

这里只讲点核心守卫部分,即获取到了token并存到本地以后,通过vue前置路由守卫控制页面跳转:

router.beforeEach((to, from, next) => {
  const hasToken = getToken();
  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' });
    } else {
      next()
    }
  } else {
    if (to.path === '/login') {
      next();
    } else {
      next('/login');
    }
  }
})
export function getToken() {
    return localStorage.getItem('Authorization')
  }
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'

// create an axios instance
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // request timeout
})
// console.log("process.env.VUE_APP_BASE_API", process.env.VUE_APP_BASE_API)
// request interceptor
service.interceptors.request.use(
  config => {
    // do something before request is sent

    if (store.getters.token) {
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response interceptor
service.interceptors.response.use(
  /**
   * If you want to get http information such as headers or status
   * Please return  response => response
  */

  /**
   * Determine the request status by custom code
   * Here is just an example
   * You can also judge the status by HTTP Status Code
   */
  response => {
    const res = response.data
    // if the custom code is not 20000, it is judged as an error.
    if (response.status !== 200) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })
      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // to re-login
        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
          confirmButtonText: 'Re-Login',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      return res
    }
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)
export default service

好了,大公告成

总结

这个项目是我大学开发的,没太写过技术贴,文笔和逻辑组织能力还是相当差的,大家见谅。项目比较简单,但也需要一定的vue+spingboot基础。目的就是给大家学了vue和springboot后有个项目练练手,好记性不如烂笔头 这句话,实践才是真理啊,多动手,多探索。希望大家喜欢。