Vue项目-手机app瑞幸咖啡详解(全网最细) 从脚手架搭建到前后端数据交互(二)

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为32,获取数据,瀑布流展示。

源代码:

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,点击去结算,创建订单(接口),同时跳转到确认订单页面。

至此,实现的动态效果(接口)基本上介绍完了,其他的静态页面以及实现不完美的接口就不展示了,有兴趣可以下载源码(下载地址我会附在博客末尾),大家可以在评论区讨论交流,我会一一回复。(我可能是最良心的博主了!

三、项目中遇到的一些问题

本次项目持续近两个周,遇到的问题大大小小不计其数层出不穷,这也是每个程序员必经的过程。都说程序员容易秃顶,那也是有迹可循的。好了,话不多说,我将以我个人视角总结下本次项目中遇到的主要困难吧:

  1. 脚手架的使用

    最开始项目的时候,手足无措,因为第一次使用脚手架,和以往的开发流程不同,会有些生疏,不知道从何下手,特别是做的还是SPA(单页面应用),不知道怎么写第二个页面,以及组件之间是如何动态切换的。

  2. 组件的复用问题

    组件存在的意义不就是为了复用,然而在开发中却不那么顺利,因为有的组件结构大致相同,只是内容不同。如何只改变内容,而结构不变困扰了我很久,直到学习了vueX.。

  3. 请求错误问题

    由于是前后端联合,发请求势必少不了404和500,而这个问题也是让我一度肝肠寸断,前端通过后端提供的接口去发请求。
    一般来说404都是前端地址错误,而500是后端服务器错误,但事实不止于此。404也可能是后端提供的接口问题,而500也可能是前端网络问题。我:???
    一般遇到这种问题,我都会先在postman测试,再查看network,以此判断是谁的问题,也不能一味的甩锅。

  4. 跨域问题

    由于前后端分离,两边都有一个服务器,那跨域在所难免。跨域的问题可以是后端解决,当然也可以前端解决。前端解决:反向代理。
    由于刚开始后端服务器关闭频繁,接口地址随之变化,导致每次都需要修改地址并重启服务,这就导致莫名其妙的404或者500问题。

  5. 页面渲染导致样式混乱

    由于每个人在开发时的习惯不同,是按照不同的移动端模拟器开发的,这就导致同一个页面在不同电脑下运行样式出现混乱的问题,特别是刚开始合并项目的时候,文件名重名,路径不统一导致样式不显示或显示错误等,需要一点一点调试。

  6. token问题

    这个问题可真是杀伤力极大,因为token是后端返回给前端并且前端负责保存在本地的,由于沟通不及时,我们原本打算保存在localStorage里,但是后端在做token验证时是从cookie里获取。
    我们不得不重新修改代码,将token保存在cookie里,cookie数据操作需要自己封装函数,于是,封装呗。并且我们在做请求拦截器以及路由守卫时都需要修改为cookie获取。
    在小组内部也是因为token的获取方式导致项目调试久久不能完成。

  7. Gitee的使用

    git,多人协作必用的工具,我们是第一次使用,操作起来也是非常生疏,特别是通过git合并项目时,用了最笨的方法,一次次的git clone,然后手动复制粘贴,再一次次的git push。并且在复制粘贴的过程中,路径问题,静态资源文件夹重名问题,真的头大。
    后来才发现可以使用git branch,才彻底改变了我对git的看法。

  8. 开发依赖库下载及版本问题

    项目开发中,需要用到很多第三方库,如vant,axios等等,版本过高或过低都会导致形形色色的问题,特别是项目合并时,由于这些第三方库的不统一,需要统一下载并保持版本一致。

  9. 组件重名问题

    只能说,我们给组件起名字时太过语义化 ,如shop1,shop2,导致很多重名的组件,没办法,只能一个个对比着改,然后一一测试效果。

  10. 第三方组件的使用

    本次项目中,用到最多的第三方库就是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(无毒无害,放心下载),有什么问题评论区见哈!

  • 9
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值