购物系统
前言
你是否:
- 毕业设计不知道做什么?学了Vue+Springboot不知道拿什么练手?
- 看完 vue,还有 vueRouter 、vuex 、vue-cli、es6 (emm。。。学不动了?) 那就: 眼睛看过来,手摸手教你撸代码
1.项目简介
vue-shopping-webapp商家端是一个后台管理 spa 页面,用户端是一个web网站,它基于 vue 和 element-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
安装
# 克隆项目
git clone https://gitee.com/zhou_guo_xi/vue-shopping-webapp.git
# 安装依赖
npm install
# 启动服务
npm run dev
系统数据流图
系统ER图
2.页面结构
home
从上图我们可以看见一些图标,以及一个轮播图,购物超市标题,购物超市下端一些功能栏。结构分三层,分别是header区域,main区域以及tabbar区域。其中main区域是一个router-view
用于嵌套不同的页面。图标来源于:MUI
。
list
用户能够看到的商品列表页面。有商品各种信息,包括商品编号、商品价格、商品标签、商品库存、商品图片、商品描述、商品数量、图文介绍、商品名称属性。
manage
管理页面是一个spa页面。商家可以查看在售商品,上下架商品,以及对订单进行一系列管理。
3.加入购物车
可以看到,商品的信息是记录到了购物车中,以及自动算出来了商品总价。动画效果用的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. 上架商品
其中,图片上传部分代码:
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. 购物车商品购买
这里面的逻辑比较多,但并不难。有订单信息追踪以及修改(如订单支付状态,收货地址)。
购物车合并购买
依靠的是数据库主外键联系,购物车购买以后在购物车订单表中生成一条订单数据,订单数据包括订单id,是一个外键,与之联系的是购物车商品表,根据刚刚的订单id生成对应购物车购买的商品信息:买了几个商品就有几条数据,几条数据共享一个订单id,不用的是各自的信息,如商品名称,商品单价,选中数量等等。这样根据订单id就能查询到订单所有信息,供商家管理订单。
3. 商家管理
商家可以看到刚刚客户购买的订单,然后进行接下来的操作。比如发货等。
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后有个项目练练手,好记性不如烂笔头 这句话,实践才是真理啊,多动手,多探索。希望大家喜欢。