Vue移动端项目–尚硅谷外卖

本项目是前后端分离的移动端外卖项目,做的功能包含:用户的注册登录、主页商家列表的展示、商家详情页、添加购物车、搜索商家等。使用axios发送请求,获取数据,用ajax来实现前后端交互。其中还使用了mockjs来产生和API请求接口格式相同的数据,以满足后端接口暂未完成的情况。在部分功能上使用BScroll实现滚动。可以使用懒加载的方式来引入路由,来实现项目优化。
项目地址

项目目录结构介绍

├─api  ------------------请求文件夹,放一些请求接口的代码。或者封装的请求接口函数
├─common ----------------普通文件夹,存放一些资源文件
│  ├─stylus -------------stylus文件夹,存放stylus样式文件
│  └─utils  -------------存放资源类js文件
├─components  -----------组件文件夹,存放vue的公共组件(注册于全局)
│  ├─AlertTip  ----------提示组件,封装了一个提示,可以直接调用,用于提示错误等
│  ├─CartControl  -------项目中加入购物车时,点击+/-来改变食物数量
│  ├─Food   -------------商家中食品的详情组件
│  ├─FooterGuide --------底部导航组件,点击不同的按钮,跳至不同的路由页面。根据页面会选择是否显示底部的导航(如登录注册页面不需要显示)
│  ├─HeaderTop  ---------头部组件,有个title参数,在不同的页面显示不同的标题
│  ├─RatingSelect -------评价筛选组件,在商家评价页面调用
│  ├─ShopCart -----------购物车组件
│  ├─ShopHeader ---------商家头部组件,每个商家页面都会展示不同的头部,可以重复调用。
│  ├─ShopList -----------商家列表组件
│  ├─Split --------------分割组件,用于不同块之间的分割。
│  └─Stars --------------星星组件,在评分时用到。根据不同的分数显示不同的星星
├─filters ---------------自定义过滤器的文件夹
├─mock ------------------有时后端无法及时提供接口,可以先按照后端接口的数据格式,使用mock产生随机数据。使用时与调用接口相似
├─pages -----------------存放主体页面。一般可以是充当路由的组件。
│  ├─Login --------------登录注册页面。登录时有两种登录方式。使用表单验证。
│  ├─Msite --------------主题页面。进入app时显示的页面。包含头部、导航、商家列表以及底部导航。
│  ├─Order --------------订单页面。显示订单列表,如果没有登录,需要先登录。(未实现)
│  ├─Profile  -----------个人页面。未登录时,不显示信息。可以跳转至登录注册页面。如果已登录,会在最下方添加退出登录按钮。
│  ├─Search -------------搜索商家页面。根据关键字搜索商家列表,如果搜索时没有相应的商家,则显示“抱歉。。。。”
│  └─Shop ---------------商家页面。包含三个子路由。
│      ├─ShopGoods ------商家食品组件。展示商家的所有食品。分为两块:左侧分类和右侧食物列表。使用了BScroll(使用这个时,需要注意调用的时间)
│      ├─ShopInfo -------商家信息组件。展示商家的详细信息。
│      └─ShopRatings ----客户对商家的评价。
├─router ----------------路由文件夹。用来存放路由配置js文件。
└─store -----------------Vuex。

项目源码地址: https://github.com/weiyuewang14/gshop

移动端适配

首先在index.html文件中添加一个meta标签

<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">

其他的适配方式还有

  • 动态REM
  • vw

头部和底部导航

首先头部定义一个组件HeaderTop,有个属性名title。当其他组件调用时,传入一个属性值即可。

<HeaderTop :title="value"></HeaderTop>

底部导航,在App组件中使用。底部导航只有在一些情况下才需要展示,因此在路由配置meta中添加showFooter属性(true为显示,false为不显示)来标识是否显示底部导航。

<FooterGuide v-if="$route.meta.showFooter"></FooterGuide>

在底部导航中,当显示哪个路由组件时才会有相应的样式,通过动态绑定class的值实现。

在这里插入图片描述

登录注册页面

包含两种登录方式:密码登录和短信登录。两种登录方式会根据选择来显示,用到了一个值loginWay(1:表示密码登录;2:表示短信登录)和class属性值on。登录时需要提交表单,此处用到了表单验证,使用正则表达式对输入的数据做的相应的校验。

密码登录

密码登录时,会发送请求获取验证码。发送验证码时,需要注意每次请求的路径需要不同,所以在请求链接后加入一个当前时间的param参数。

src = 'http://xxx/xxxx/xxx?time' + Date.now()

密码输入框有一个按钮来选择是否显示密码。按钮的动态变化是根据class属性值on来决定。定义一个showPwd标识,来控制显示。

密码登录

短信登录

短信登录时,首先需要输入格式正确的手机号,否则发送验证码按钮无效。点击发送验证码之后,会有一个已发送时间的倒计时,在倒计时归0之前不能再次点击发送验证码。当登录失败时,还需要重置倒计时。
短信登录

Profile页面

我的页面分两种状态:已登录和未登录。

未登录

用户未登录时,不显示登录信息,而且可以点击登录到登录注册页面。

已登录

用户已登录时,则会显示用户名。当还未绑定手机号的情况下,会显示暂无绑定手机号。
Profile页面
在Profile页面,如果用户已经登录,在最下方会添加一个退出登录按钮,点击退出登录会发送请求。

Msite页面

打开外卖移动端时,会直接到达Msite页面,在路由配置中设置了重定向。
在页面右上角,会显示用户是否登录。当页面挂载的时候,就会请求接口,获得导航部分的分类数据和商家列表数据。
Msite页面

首页导航

使用轮播图实现。轮播图的创建时,可能会出现创建时数据还未加载,因此需要监听请求到的分类数据。此处使用Vue提供的nextTick方法。
Vue.$nextTick()的定义:在下次 DOM 更新循环结束之后执行延迟回调。
当数据更新后,先等待DOM更新。因此在此时创建轮播图对象。

// 监听食品分类数据的变化
      categories (value) {
        // 修改数据之后,会立即使用这个数据并等待DOM更新
        this.$nextTick(() => {
          //创建轮播图
          new Swiper('.swiper-container', {
            loop: true,
            //如果要是分页
            pagination: {
              el: '.swiper-pagination'
            }
          })
        })
      }
ShopList

将请求到的数据通过v-for展示在页面上,然后给每个列表项添加一个click事件,跳转路由至商家页面。

商家页面

商家页面包含商家页面的头部、三个子路由(点餐、评价、商家信息)。使用了BScroll实现滑动,使用BScroll时注意其会将默认click事件关闭,需要通过属性值来打开。
better-scroll是基于父元素固定高度,溢出才滚动的,所以父元素务必定高,否则无法滚动。

this.foodsScroll = new BScroll('.foods-wrapper', {
  probeType: 2, // 1:,2:,3:
  click: true, // 开启click事件。BScroll是默认关闭点击事件的。
})
点餐

分为菜单列和食品列表。当选择某个菜单时,食品列表滑动到相应的位置。使用BScroll来实现。
需要收集当前分类的index和每个食品分类的头部值,形成映射。

添加数量

点击添加或减少食品数量进入购物车时,需要派发action实现同步更新food中的count值。当第一次加入购物车时,food中不存在count属性,需要添加属性。

Vue.set(obj, 'propertyName', value)
购物车

购物车的显示基于购物车中的食品数量和用户的点击事件。
当没有食品加入购物车时,无法查看购物车。当购物车中有食品时,用户可以点击购物车查看购物车中的食品。

评价

难点:筛选评论时的条件。

/!*
	selectType: 2, //全部         // rating.rateType(0/1)
	onlyContent: true // 是否只看有内容的  //rating.text
result = !onlyContent || rating.text.length > 0
*!/
/!*
	selectType: 0/1/2   如果是0/1需要判断是否与rating.rateType相等, 如果是2就不需要
	onlyContent: true/false  如果为true需要判断rating.text必须有值, 如果是false就不需要
result= selectType === rateType && (!onlyContent || rating.text.length > 0)
*!/

评价时间使用自定义过滤器

import moment from 'moment'
import Vue from 'vue'

Vue.filter('dateString', function (value, format) {
  return moment(value).format(format || 'YYYY-MM-DD HH:mm:ss')
})
商家

商家信息展示时,有两处使用了BScroll,整体的一个和商家实景。分为垂直滑动(scrollY)和水平滑动(scrollX)。
在商家实景处,创建BScroll对象后,还是不能滑动,是因为ul没有被撑开,所以需要计算ul的宽度。

总结

搭建整个项目时,需要先考虑布局,如何划分模块?如何划分是几级路由?然后查看后端接口,先进行测试(postman)。还有解决跨域问题(使用代理)。

  • 使用axios发送请求
  • 使用ajax进行前后台交互
  • 组件之间的数据传递(子传父ref、父传子props、兄弟之间的传递等)vuex可以实现所有的数据传递
  • 当出现某个值不存在某个属性的错误时,可能是由于数据还未能加载的时候,取了其中的数据。
Logo

前往低代码交流专区

更多推荐