Vue项目-手机app瑞幸咖啡详解(全网最细) 从脚手架搭建到前后端数据交互(二)
后续来啦!2.10、潮品页2.10.1、获取所有商品的分类2.10.2、展示某类型的所有商品(瀑布流)2.11、我的页面2.11.1、展示某一类型的商品2.11.2、获取个人信息2.12、待付款页2.13、购物车页2.13.1、显示购物车三、项目中遇到的一些问题四、项目中一些技术的实现4.1、拦截器4.2、vueX4.3、路由4.4、路由守卫4.5、反向代理4.6、mock数据五、项目开发体会2.
后续来啦!
2.10、潮品页
潮品页面也就是从首页跳转到的二级页面,通过点击首页商品分类的更多实现跳转,静态页面效果如下:
该页面实现了两个动态效果:获取所有商品的分类,展示某类型的所有商品 。
2.10.1、获取所有商品的分类
实现思路:
//思路较简单,没有参数
1,连接口,发请求,获取数据;
2,渲染页面。
部分源码:
import axios from '../utils/request.js'
export default {
name: "Centen4",
data() {
return {
numZu: [],
};
},
created() {
axios({
url: "/api/goods/findFashionType",
// method:'get', 默认是get请求
}).then((res) => {
// console.log(res.data.data);
this.numZu = res.data.data;
// console.log(this.numZu);
});
},
};
实现效果参考上面这张图(新品首发)
2.10.2、展示某类型的所有商品(瀑布流)
就是获取某个类型的所有商品(该类型已经固定,已经存储在数据库),以瀑布流的形式展示。
实现思路:
1,该类型已经固定为3,故发起请求,并传参typeId为3;
2,获取数据,瀑布流展示。
源代码:
import axios from "axios";
export default {
name: "Centen6",
data() {
return {
shuZu: [],
};
},
created() {
axios({
url: "api/goods/findGoodsByType/",
params: {
typeId: 3,//此处传参
},
}).then((res) => {
console.log(res.data.data);
this.shuZu = res.data.data;
});
},
};
效果图:
(数据库中只存储了少量图片,因为切图实在太麻烦了!瀑布流前面已经解释过,可以看我的上一篇博客。)
2.11、我的页面
相信这个大家都清楚,基本上所有APP都会有我的页面,类似于首页,用来展示APP的一些功能以及个人信息等。
静态页面在上一篇博客已经展示过,有兴趣的小伙伴可以先看完第一篇博客。
在本次项目中,我的页面实现的功能较少,因为功能太多啦,而且时间和水平上的原因,只实现少部分功能:展示某一类型的商品,获取个人信息;
2.11.1、展示某一类型的商品
这部分功能和首页的很像,只是数据不同,布局和接口完全一样,所以我会说的快一些。
首先,我的页面有以下四种分类,并且页面加载完毕默认显示第一种分类。
这里同样可以使用动态组件
,vueX
等,在首页我选择了使用vueX
。
实现思路:(与首页一致)
1,默认获取下午茶相关的商品数据,渲染;
2,点击其他分类获取对应的商品信息,并渲染。
来看看相关代码:
import axios from "../utils/request"
export default {
name: "Classify",
data() {
return {
arr:["9","10","11","12"],
a: 0,
teas:[]
};
},
created () {
this.gettea(0)
},
methods: {
reveal(index) {
this.a = index;
this.gettea(index);
},
gettea(idx){
axios({
url:'/api/goods/findGoodsByType',
params:{
typeId:this.arr[idx]
}
})
.then(res=>{
this.teas=res.data.data;
// console.log(res.data.data);
})
},
//动态路由传参的方式
goDetail(id){
this.$router.push("/info/"+id);
},
tochao(){
this.$router.push('/Newsp')
}
}
};
这里使用了动态组件的思路来做的,效果也是同样完美,每个人都有自己的思路,挺不错的!
效果图:
很显然,在这里存储了更多的图片,效果也是很不错的,就是切图太麻烦。。。
2.11.2、获取个人信息
这里只是实现了简单的效果,通过事先在数据库中存储一些个人信息,再通过登录时的手机号查询出来,并保存在本地。
实现思路:
1,连接口,发请求,获取个人信息;
2,将数据都存储在cookie中,需要获取的时候再从本地获取。
部分源码:
//此处封装了模块,并引入
created() {
axios({
url: "http://1.117.52.63:8081/user/findByTel",
}).then((res) => {
// console.log("this.ming", res.data.data);
console.log( creatCookie("id",res.data.data.id));
creatCookie("id",res.data.data.id,7)
creatCookie("sex",res.data.data.sex,7)
creatCookie("tel",res.data.data.tel,7)
creatCookie("username",res.data.data.username,7)
});
},
封装存储cookie模块:
function creatCookie(key,value,date){
var t = new Date();
t.setDate(t.getDate()+date)
document.cookie = key+'='+value+';expires='+t;
}
export default creatCookie;
至于获取个人信息是在个人资料页从cookie里读取并渲染:
封装读取cookie模块:
function getCookie(value){
var str = document.cookie;
var arr = str.split('; ');
var newarr = [];
for (const key in arr) {
newarr = arr[key].split('=');
if (newarr[0] == value) {
return newarr[1];
}
}
return '';
}
export default getCookie;
cookie函数的封装也可以封装在一个模块并导出。
2.12、待付款页
待付款页是从确认订单页点击取消订单跳转到的页面,该页面实现了一个动态效果:查询订单信息。
待付款页面是有一个5分钟倒计时的,倒计时结束,则自动取消订单,若倒计时结束前用户点击去支付,则重新跳转到确认订单页面。
静态页面先不看。
实现思路:
1,发请求,传参,获取数据;
2,渲染。(思路就是这么简单)
源码:
import axios from "../utils/request";
export default {
data() {
return {
serv:[],
arrlen:[],
arrf1: [
"14BC03EAA1BC4066AF476A6976B0BF32",
"72C35A1B4BF144F38580E7F38619EAEB",
"8666F9A626B44824B175C9F63B4BCABE",
"D7EDF5B1D23A4A6CB14C1FC2A10FBCF1",
],
f11: 0,
};
},
axios
.get(
"/api/order/selectOrderByOid/" +
this.arrf1[this.f11]
)
.then((ok) => {
console.log(ok.data.data);
this.serv=ok.data.data
this.arrlen =ok.data.data.orderDetailResps
console.log(this.arrlen);
});
},
};
效果图:
2.13、购物车页
购物车,这可能是每个电商APP不可缺少的环节了吧,商品加入购物车才能购买,并且在购物车可以调节购买数量,查看商品的基本信息等等。
购物车作为支付前的最后一个环节,自然是重中之重,遗憾的是,本次项目中在购物车环节实现的效果不全,当前也和我们自身的水平有关。以下我将对购物车已实现的部分功能加以概述。
首先,我们要知道商品从选择到支付完成的一整套流程是:
选中商品==》选择商品数量口味等==》加入购物车==》显示购物车==》去结算==》支付成功
加入购物车我们已经实现了,下面显示购物车:
2.13.1、显示购物车
其实就是一个查询操作,将加入购物车的商品查出来并渲染。
实现思路:
1,发请求,同时传参uid(用户id);
2,获取数据,渲染。
源码:
import axios from "../utils/request.js";
import Vue from "vue";
import { Toast } from "vant";
import { Popup } from "vant";
Vue.use(Popup);
Vue.use(Toast);
export default {
name: "Centent1",
data() {
return {
show: false,
goodsList: [],
};
},
created() {
axios({
url: "/api/cart/cartIterm",
params: {
uid: 8,
},
}).then((res) => {
this.goodsList = res.data;
});
},
methods: {
removeGoods(idx) {
if (confirm("亲,您真的要删除吗?")) {
this.goodsList.splice(idx, 1);
}
},
reducer(idx) {
if (this.goodsList[idx].number <= 0) {
this.show = true;
return;
}
this.goodsList[idx].number--;
},
add(idx) {
this.goodsList[idx].number++;
},
quxiao() {
this.show = false;
this.goodsList[0].number = 1;
},
que() {
},
totalMoney() {
let sum = 0;
this.goodsList.forEach((goods) => {
if (goods.isChecked) {
sum += parseInt(goods.number) * parseInt(goods.price);
}
});
return sum;
},
},
};
效果图:
同时:当商品数量减到0,即删除该商品,会弹出提示框:
由于购物车实现效果较少,我在这里总结下购物车应有的效果:
购物车总结:
1,显示购物车(接口实现);
2,点击加减号对应商品数量加减对应数量(数据库中,接口实现),
同时总价也相应的变化;
3,当数量减到0,视为删除该商品(接口实现),会有提示框;
4,只有选中商品前面的复选框,该商品小计才会计算到总价中;
5,点击去结算,创建订单(接口),同时跳转到确认订单页面。
至此,实现的动态效果(接口)基本上介绍完了,其他的静态页面以及实现不完美的接口就不展示了,有兴趣可以下载源码(下载地址我会附在博客末尾),大家可以在评论区讨论交流,我会一一回复。(我可能是最良心的博主了!)
三、项目中遇到的一些问题
本次项目持续近两个周,遇到的问题大大小小不计其数层出不穷,这也是每个程序员必经的过程。都说程序员容易秃顶,那也是有迹可循的。好了,话不多说,我将以我个人视角总结下本次项目中遇到的主要困难吧:
-
脚手架的使用
最开始项目的时候,手足无措,因为第一次使用脚手架,和以往的开发流程不同,会有些生疏,不知道从何下手,特别是做的还是SPA(单页面应用),不知道怎么写第二个页面,以及组件之间是如何动态切换的。
-
组件的复用问题
组件存在的意义不就是为了复用,然而在开发中却不那么顺利,因为有的组件结构大致相同,只是内容不同。如何只改变内容,而结构不变困扰了我很久,直到学习了vueX.。
-
请求错误问题
由于是前后端联合,发请求势必少不了404和500,而这个问题也是让我一度肝肠寸断,前端通过后端提供的接口去发请求。
一般来说404都是前端地址错误,而500是后端服务器错误,但事实不止于此。404也可能是后端提供的接口问题,而500也可能是前端网络问题。我:???
一般遇到这种问题,我都会先在postman测试,再查看network,以此判断是谁的问题,也不能一味的甩锅。 -
跨域问题
由于前后端分离,两边都有一个服务器,那跨域在所难免。跨域的问题可以是后端解决,当然也可以前端解决。前端解决:反向代理。
由于刚开始后端服务器关闭频繁,接口地址随之变化,导致每次都需要修改地址并重启服务,这就导致莫名其妙的404或者500问题。 -
页面渲染导致样式混乱
由于每个人在开发时的习惯不同,是按照不同的移动端模拟器开发的,这就导致同一个页面在不同电脑下运行样式出现混乱的问题,特别是刚开始合并项目的时候,文件名重名,路径不统一导致样式不显示或显示错误等,需要一点一点调试。
-
token问题
这个问题可真是杀伤力极大,因为token是后端返回给前端并且前端负责保存在本地的,由于沟通不及时,我们原本打算保存在localStorage里,但是后端在做token验证时是从cookie里获取。
我们不得不重新修改代码,将token保存在cookie里,cookie数据操作需要自己封装函数,于是,封装呗。并且我们在做请求拦截器以及路由守卫时都需要修改为cookie获取。
在小组内部也是因为token的获取方式导致项目调试久久不能完成。 -
Gitee的使用
git,多人协作必用的工具,我们是第一次使用,操作起来也是非常生疏,特别是通过git合并项目时,用了最笨的方法,一次次的
git clone
,然后手动复制粘贴,再一次次的git push
。并且在复制粘贴的过程中,路径问题,静态资源文件夹重名问题,真的头大。
后来才发现可以使用git branch
,才彻底改变了我对git的看法。 -
开发依赖库下载及版本问题
项目开发中,需要用到很多第三方库,如vant,axios等等,版本过高或过低都会导致形形色色的问题,特别是项目合并时,由于这些第三方库的不统一,需要统一下载并保持版本一致。
-
组件重名问题
只能说,我们给组件起名字时太过语义化 ,如shop1,shop2,导致很多重名的组件,没办法,只能一个个对比着改,然后一一测试效果。
-
第三方组件的使用
本次项目中,用到最多的第三方库就是vant,它提供了很多组件,可以直接拿来用。但是刚开始却不是那么顺利。有些组件可以直接全局引入,有的还需要按需引入。这就需要不断尝试,还有这些组件自定义样式的问题。
好了,以上就简单总结一下项目中遇到的问题,吃一堑长一智,及时的总结经验可以让你在未来避免这些错误。如果你也遇到过类似的问题,欢迎在评论区分享出你的解决方案。
四、项目中一些技术的实现
4.1、拦截器
在请求发出后,以及响应回来前要干的一些事情可以写在拦截器里,拦截器并不是真的拦截。
在项目根目录下新建utils/request.js文件
import axios from "axios";
import store from '../store'
// axios.defaults.baseURL="/api";
// 请求拦截器
axios.interceptors.request.use(config => {
store.commit('changeload', true)
if (getCookie('token')) {
config.headers.common["token"] = getCookie('token');
}
return config;
});
// 响应拦截器
axios.interceptors.response.use(res => {
store.commit('changeload', false);
return res;
});
//此处封装了获取cookie的函数
function getCookie(key) {
var info = document.cookie;
var arr = info.split("=");
if (arr[0] == key) {
return arr[1];
}
return "";
}
//别忘了导出
export default axios;
4.2、vueX
用来保存全局数据,供整个项目使用。(前面提到过,首页使用了vueX技术实现商品类型切换)
在项目根目录新建store/index.js文件
import vueX from 'vuex'
import Vue from 'vue'
import axios from '../utils/request.js'
Vue.use(vueX)
export default new vueX.Store({
state: {
shopinfo: [],
tel: '',
code: '',
isshow: false,
orderInfo: ["全部", "立等可取", "预约订单"],
outerInfo: ["门店自提", "送货上门", "瑞即购"],
isLoading: false,
},
mutations: {
//state:store中的state
change(state, payload) {
state.shopinfo = payload.data
// console.log(state.shopinfo);
},
changeload(state, payload) {
// console.log(payload);
state.isshow = payload
},
changeIsLoading(state, payload) {
state.isLoading = payload.isLoading;
}
},
actions: {
//context:store对象
change1(context) {
axios({
url: "/api/goods/findGoodsByType",
params: {
typeId: 4,
}
})
.then(res => {
console.log(res.data.data);
context.commit('change', { //将请求结果传给mutations
data: res.data.data
})
})
},
change2(context, payload) {
// console.log(payload);
axios({
url: "/api/goods/findGoodsByType",
params: {
typeId: payload,
}
})
.then(res => {
context.commit('change', { //将请求结果传给mutations
data: res.data.data
})
})
},
}
})
4.3、路由
实现组件间的跳转。(相当于pc端里a标签的功能,路由的使用需要配置,可以参考我的博客路由是什么)
在根目录下新建router/index.js文件
import Vue from "vue";
import VueRouter from "vue-router";
import Menu from "../pages/Menu.vue";
import Order from "../pages/Order.vue";
import Outering from "../pages/Outering.vue";
import Address from "../pages/Address.vue";
import Detail from "../pages/Detail.vue";
import Acontent from "../components/Acontent.vue";
import NotFound from "../pages/NotFound.vue";
import Item from "../components/Item.vue";
import index from '../pages/Index'
import info from '../pages/Info'
import addcomm from '../pages/addcomm'
import pay from '../pages/pay.vue'
import Newsp from "../pages/Newsp";
import Shop from "../pages/Shop";
import Login from "../pages/Login";
import Payment from "../pages/Payment";
import My from "../pages/My";
import PersonalData from "../pages/PersonalData";
import Profile from "../pages/Profile.vue"
import NewlyAdd from "../pages/NewlyAdd.vue"
import CardRedirect from "../pages/CardRedirect.vue"
import getmoney from '../pages/GetMoney.vue'
Vue.use(VueRouter);
// 路由配置
let router = new VueRouter({
mode: 'history',
routes: [{
path: '/',
redirect: '/index', //重定向
},
{
path: '/index',
name: index,
component: resolve => require(['../pages/Index'], resolve)
},
{
path: "/Menu",
name: Menu,
component: resolve => require(['../pages/Menu'], resolve)
},
{
path: "/Order",
name: Order,
component: resolve => require(['../pages/Order'], resolve),
meta: {
requiresAuth: true
}
},
{
path: "/Shop",
component: Shop,
meta: {
requiresAuth: true
}
},
{
path: '/info/:id',
component: info,
meta: {
requiresAuth: true
}
},
{
path: '/addcomm/:id',
component: addcomm,
meta: {
requiresAuth: true
}
},
{
path: '/pay',
component: pay,
meta: {
requiresAuth: true
}
},
{
path: "/Outering",
component: Outering
},
{
path: "/Address",
component: Address
},
{
path: "/Detail/:id",
component: Detail,
meta: {
requiresAuth: true
}
},
{
path: "/Acontent",
component: Acontent
},
{
path: "/Item/:id",
name: Item,
component: resolve => require(['../components/Item'], resolve)
},
{
path: "/Newsp",
component: Newsp,
meta: {
requiresAuth: true
}
},
{
path: "/Login",
component: Login
},
{
path: "/Payment/:money",
component: Payment,
meta: {
requiresAuth: true
}
},
{
path: "/My",
component: My,
meta: {
requiresAuth: true
}
},
{
path: '/GetMoney',
component: getmoney,
meta: {
requiresAuth: true
}
},
{
path: "/PersonalData",
component: PersonalData,
meta: {
requiresAuth: true
}
},
{
path: "/CardRedirect",
component: CardRedirect,
meta: {
requiresAuth: true
}
},
{
path: "/Profile",
component: Profile
},
{
path: "/NewlyAdd",
component: NewlyAdd
},
{
path: "*",
component: NotFound
},
]
})
// 导出路由实例
export default router;
4.4、路由守卫
可以用来验证用户是否登录以及是否可以访问页面。
同4.3,在一个文件里
//路由全局守卫(前置)
router.beforeEach((to, from, next) => {
//to 到哪去(目标路由)
//from 从哪来(当前路由)
//next 去干吗
if (to.meta.requiresAuth) {
if (getCookie("token")) { //是否登录
console.log('已经登陆');
next();
} else {
console.log('没有登陆');
next({ path: "/login", params: { toPath: to.path } });
}
}
next()
})
4.5、反向代理
前端解决跨域问题的方法。
在项目根目录新建vue.config.js文件
module.exports = {
devServer: {
//设置代理
proxy: { //代理是从指定的target后面开始匹配的,不是任意位置;配置pathRewrite可以做替换
// 商品列表反向代理
'/api': { //axios访问 /api == target + /api
target: '此处为服务端真实ip地址',
changeOrigin: true, //创建虚拟服务器
pathRewrite: {
'^/api': '' //重写接口,去掉/api, 在代理过程中是否替换掉/api/路径
}
},
}
}
}
4.6、mock数据
模拟后端数据。(没有后端接口时测试用)
在项目根目录新建db.json文件
{
"books": [
{ "id": "878911", "name": "三国", "author": "罗贯中" },
{ "id": "878912", "name": "水浒", "author": "吴承恩" }
],
"readers": [
{ "id": "007", "name": "张三疯", "age": 35 },
{ "id": "008", "name": "张四疯", "age": 32 }
]
}
注意:文件内容如上图所示,在本次项目中mock数据用的较少。具体的使用参考我之前的博客。
五、项目开发体会
项目做完了,但是总体来说效果不是特别好,该有的功能没有实现,比如购物车功能,支付功能。经过这次的项目开发经历,确实学到了很多,也有很多不足的地方。
总之,收获到的远远大于所经历的痛苦,因为,痛并快乐着。
写这篇博客也是抽出时间来写的,历时将近两天,能想到的都写了,思路不是很清晰,也不知道还需要写什么,以后有机会再补上吧!
最后的最后,附上项目完整的源代码下载地址:https://gitee.com/sandas/ruixing(无毒无害,放心下载),有什么问题评论区见哈!
更多推荐
所有评论(0)