一、功能模块
二、涉及技术
三、项目中的vw适配
项目根目录中, 新建postcss的配置文件`postcss.config.js`
四、路由配置
路由设计:
-
登录页
-
首页架子
-
首页 - 二级
-
分类页 - 二级
-
购物车 - 二级
-
我的 - 二级
-
-
搜索页
-
搜索列表页
-
商品详情页
-
结算支付页
-
我的订单页
4.1 配置一级路由
router/index.js
配置一级路由,新建对应的页面文件
import Vue from 'vue'
import VueRouter from 'vue-router'
import Layout from '@/views/layout'
import Search from '@/views/search'
import SearchList from '@/views/search/list'
import ProDetail from '@/views/prodetail'
import Login from '@/views/login'
import Pay from '@/views/pay'
import MyOrder from '@/views/myorder'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/login',
component: Login
},
{
path: '/',
component: Layout
},
{
path: '/search',
component: Search
},
{
path: '/searchlist',
component: SearchList
},
{
path: '/prodetail/:id',
component: ProDetail
},
{
path: '/pay',
component: Pay
},
{
path: '/myorder',
component: MyOrder
}
]
})
export default router
4.2 路由配置-tabbar标签页
vant网站地址:Vant 2 - 轻量、可靠的移动端组件库 (gitee.io)
4.2.1 vant-ui.js
引入组件
4.2.2 导航栏配置
-
layout.vue 复制官方代码
-
修改显示文本及显示的图标
-
配置高亮颜色
4.2.3 router/index.js
配置二级路由
4.2.3.1 新建vue页面
4.2.3.2 router/index.js
配置二级路由
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '@/views/login'
import Layout from '@/views/layout'
import Search from '@/views/search'
import SearchList from '@/views/search/list'
import ProDetail from '@/views/prodetail'
import Pay from '@/views/pay'
import MyOrder from '@/views/myorder'
import Home from '@/views/layout/home'
import Category from '@/views/layout/category'
import Cart from '@/views/layout/cart'
import User from '@/views/layout/user'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/login', component: Login },
{
path: '/',
component: Layout,
children: [
{
path: '/home', component: Home
},
{
path: 'category',
component: Category
},
{
path: 'cart',
component: Cart
},
{
path: 'user',
component: User
}
]
},
{ path: '/search', component: Search },
{
path: '/prodetail/:id',
component: ProDetail
},
{
path: '/pay',
component: Pay
},
{
path: '/myorder',
component: MyOrder
},
{
path: '/searchlist',
component: SearchList
}
]
})
export default router
4.2.3.3 layout.vue
配置路由出口, 配置 tabbar,点击导航栏的图标可以跳转页面,并且配置了二级路由出口
<template>
<div>
<!--二级路由出口,二级组件展示的位置-->
<van-tabs route active-color="#ee0a24" inactive-color="#000" >
<van-tabbar-item to="/home" icon="wap-home-o">首页</van-tabbar-item>
<van-tabbar-item to="/category" icon="apps-o">分类页</van-tabbar-item>
<van-tabbar-item to="/cart" icon="shopping-cart-o">购物车</van-tabbar-item>
<van-tabbar-item to="/user" icon="user-o">我的</van-tabbar-item>
</van-tabs>
</div>
</template>
<script>
export default {
name: 'layoutIndex'
}
</script>
<style>
</style>
五、登录页面布局
5.1组件导入,使用的是
import { NavBar } from 'vant'
Vue.use(NavBar)
5.2 login页面样式
<template>
<div class="login">
<van-nav-bar title="会员登录" left-arrow @click-left="$router.go(-1)" />
<div class="container">
<div class="title">
<h3>手机号登录</h3>
<p>未注册的手机号登录后将自动注册</p>
</div>
<div class="form">
<div class="form-item">
<input class="inp" maxlength="11" placeholder="请输入手机号码" type="text">
</div>
<div class="form-item">
<input class="inp" maxlength="5" placeholder="请输入图形验证码" type="text">
<img src="@/assets/code.png" alt="">
</div>
<div class="form-item">
<input class="inp" placeholder="请输入短信验证码" type="text">
<button>获取验证码</button>
</div>
</div>
<div class="login-btn">登录</div>
</div>
</div>
</template>
<script>
export default {
name: 'LoginPage'
}
</script>
<style lang="less" scoped>
.container {
padding: 49px 29px;
.title {
margin-bottom: 20px;
h3 {
font-size: 26px;
font-weight: normal;
}
p {
line-height: 40px;
font-size: 14px;
color: #b8b8b8;
}
}
.form-item {
border-bottom: 1px solid #f3f1f2;
padding: 8px;
margin-bottom: 14px;
display: flex;
align-items: center;
.inp {
display: block;
border: none;
outline: none;
height: 32px;
font-size: 14px;
flex: 1;
}
img {
width: 94px;
height: 31px;
}
button {
height: 31px;
border: none;
font-size: 13px;
color: #cea26a;
background-color: transparent;
padding-right: 9px;
}
}
.login-btn {
width: 100%;
height: 42px;
margin-top: 39px;
background: linear-gradient(90deg,#ecb53c,#ff9211);
color: #fff;
border-radius: 39px;
box-shadow: 0 10px 20px 0 rgba(0,0,0,.1);
letter-spacing: 2px;
display: flex;
justify-content: center;
align-items: center;
}
}
</style>
六、request模块 - axios封装
说明:我们会使用 axios 来请求后端接口, 一般都会对 axios 进行一些配置 (比如: 配置基础地址,请求响应拦截器等等)
一般项目开发中, 都会对 axios 进行基本的二次封装, 单独封装到一个模块中, 便于使用
6.1 根据起步 | Axios中文文档 | Axios中文网 (axios-http.cn)创建实例
// 创建axios实例,将来对创建出来的实例,进行自定义配置
// 好处:不会污染原始的axios实例
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: { 'X-Custom-Header': 'foobar' }
})
6.2 自定义配置
// 自定义配置 请求/响应 拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么(默认axios会多包装一层data)
return response.data
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error)
})
6.3 导出配置好的axios实例:instance
// 导出配置好的实例:instance
export default instance
七、图形验证码功能
7.1 准备数据,获取图形验证码后存储图片路径,存储图片唯一标识
async created () {
this.getPicCode()
},
data () {
return {
picUrl: '',
picKey: ''
}
},
methods: {
// 获取图形验证码
async getPicCode () {
const { data: { base64, key } } = await request.get('/captcha/image')
this.picUrl = base64
this.picKey = key
}
}
7.2 动态渲染图形验证码,并且点击时要重新刷新验证码
<img v-if="picUrl" :src="picUrl" @click="getPicCode">
八、封装api接口 - 图片验证码接口
目的:
-
请求与页面逻辑分离
-
相同的请求可以直接复用请求
-
进行了统一管理
8.1 新建 api/login.js
提供获取图形验证码 Api 函数(如果不加return,就接受不到返回回来的promise的结果)
// 此处用于存放所有登录相关的接口请求
// 1.获取图像验证码
import request from '@/utils/request'
export const getPicCode = () => {
// 如果不加return,就接受不到返回回来的promise的结果
return request.get('/captcha/image')
}
九、toast 轻提示(两种使用方式)
记不得看官方文档Vant 2 - 轻量、可靠的移动端组件库 (gitee.io)
十、短信验证倒计时功能
10.1倒计时效果
10.1.1 准备 data 数据
定时器的ID,便于清空定时器,同时也用于判断定时器存不存在
data () {
return {
totalSecond: 60, // 总秒数
second: 60, // 当前秒数,开定时器
timer: null // 定时器 id
}
},
10.1.2 给按钮注册点击事件
只有它们两个相等时,才显示获取验证码,否则都是几秒后发送
<button @click="getCode">
{{ second === totalSecond ? '获取验证码' : second + `秒后重新发送`}}
</button>
10.1.3 开启倒计时
判断:只有没有定时器,并且值相等才会开启
async getCode () {
if (!this.timer && this.second === this.totalSecond) {
// 开启倒计时
this.timer = setInterval(() => {
this.second--
if (this.second < 1) {
clearInterval(this.timer)
this.timer = null
this.second = this.totalSecond
}
}, 1000)
// 发送请求,获取验证码
this.$toast('发送成功,请注意查收')
}
}
10.1.4 离开页面销毁定时器
destroyed () {
clearInterval(this.timer)
}
10.2 验证码请求校验处理
10.2.1 准备变量
data () {
return {
mobile: '', // 手机号
picCode: '' // 图形验证码
}
},
10.2.2 双向绑定,以快速获取表单数据
<input v-model="mobile" class="inp" maxlength="11" placeholder="请输入手机号码" type="text">
<input v-model="picCode" class="inp" maxlength="5" placeholder="请输入图形验证码" type="text">
10.2.3 methods中封装校验方法,返回结果为true,则通过校验,可以继续
validFn () {
if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
this.$toast('请输入正确的手机号')
return false
}
if (!/^\w{4}$/.test(this.picCode)) {
this.$toast('请输入正确的图形验证码')
return false
}
return true
},
10.2.4 请求倒计时前进行校验
// 获取短信验证码
async getCode () {
if (!this.validFn()) {
return
}
...
}
10.3 封装接口,请求获取验证码
10.3.1 封装接口
// 获取短信验证码
export const getMsgCode = (captchaCode, captchaKey, mobile) => {
return request.post('/captcha/sendSmsCaptcha', {
form: {
captchaCode,
captchaKey,
mobile
}
})
}
10.3.2 调用接口,添加提示
async getCode () {
if (!this.validFn()) {
return
}
if (!this.timer && this.second === this.totalSecond) {
// 发送请求,获取验证码
await getMsgCode(this.picCode, this.picKey, this.mobile)
this.$toast('发送成功,请注意查收')
// 开启倒计时
...
}
}
十一、封装api接口 - 登录功能
11.1 提供登录 Api 函数
// 验证码登录
export const codeLogin = (mobile, smsCode) => {
return request.post('/passport/login', {
form: {
isParty: false,
mobile,
partyData: {},
smsCode
}
})
}
11.2 login/index.vue
登录功能
<input class="inp" v-model="msgCode" maxlength="6" placeholder="请输入短信验证码" type="text">
<div class="login-btn" @click="login">登录</div>
data () {
return {
msgCode: '',
}
},
methods: {
async login () {
if (!this.validFn()) {
return
}
if (!/^\d{6}$/.test(this.msgCode)) {
this.$toast('请输入正确的手机验证码')
return
}
await codeLogin(this.mobile, this.msgCode)
this.$router.push('/')
this.$toast('登录成功')
}
}
十二、响应拦截器统一处理错误提示
响应拦截器是拿到数据的 第一个 “数据流转站”,可以在里面统一处理错误,只要不是 200 默认给提示,抛出错误。
utils/request.js里toast不能直接使用,需要导入
import { Toast } from 'vant'
...
// 添加响应拦截器
request.interceptors.response.use(function (response) {
const res = response.data
if (res.status !== 200) {
Toast(res.message)
return Promise.reject(res.message)
}
// 对响应数据做点什么
return res
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error)
})