创建项目p106
router,store和app.vue不用删
清一下router里的路由配置
vant组件库p107
目标:认识第三方vue组件库vant-ui(cv战士)
封装好了的组件整合在一起就是组件库
http://vant-contrib.gitee.io/vant/v2/#/zh-CN/
vue2用vant2,vue3用vant3和4
vant全部导入太慢了,按需导入推荐
安装npm i vant@latest-v2 -S
阿里云安装镜像 npm config set registry https://registry.npmmirror.com/
引入
vant网址http://vant-contrib.gitee.io/vant/v2/#/zh-CN/quickstart
cv到main.js
方式一. 自动按需引入组件 (推荐)
babel-plugin-import 是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式。
###### 安装插件
npm i babel-plugin-import -D
// 在.babelrc 中添加配置
// 注意:webpack 1 无需设置 libraryDirectory
{"plugins": [["import", {"libraryName": "vant","libraryDirectory": "es","style": true}]]
}// 对于使用 babel7 的用户,可以在 babel.config.js 中配置
module.exports = {plugins: [['import', {libraryName: 'vant',libraryDirectory: 'es',style: true}, 'vant']]
};
// 接着你可以在代码中直接引入 Vant 组件
// 插件会自动将代码转化为方式二中的按需引入形式
import { Button } from 'vant';
Tips: 如果你在使用 TypeScript,可以使用 ts-import-plugin 实现按需引入。
方式二. 手动按需引入组件(要css,不推荐)
在不使用插件的情况下,可以手动引入需要的组件。
import Button from 'vant/lib/button';
import 'vant/lib/button/style';
方式三. 导入所有组件
Vant 支持一次性导入所有组件,引入所有组件会增加代码包体积,因此不推荐这种做法。
import Vue from 'vue';
import Vant from 'vant';
import 'vant/lib/index.css';
//插件安装初始化,内部将所有的vant组件进行导入注册
Vue.use(Vant);
Tips: 配置按需引入后,将不允许直接导入所有组件。
按需导入
安装插件每次都要重新弄npm i babel-plugin-import -D
module.exports = {plugins: [['import', {libraryName: 'vant',libraryDirectory: 'es',style: true}, 'vant']]
};
放main.js里太多了,导入组件单独分一个vant-ui.js放在utils文件夹里
如果渲染不出来,重新打开编程软件和终端
postcss插件-实现vw适配p108
postcss写多少px直接转换为多少vw
npm下载插件:npm install postcss-px-to-viewport@1.1.1 -D --force//npm install postcss-px-to-viewport --legacy-peer-deps/
要在根目录上(和src同级的)创建postcss.config.js
然后官网cv:
// postcss.config.js
module.exports = {plugins: {'postcss-px-to-viewport': {viewportWidth: 375,},},
};
路由设计配置p109
一级路由配置
单个页面独立展示的是一级路由
每个一级路由要建成一个文件夹
文件夹里只有一个时直接简写文件夹
path中表示首页的是/
二级路由-底部导航tabbar
vant组件库有
vant2里有基础组件-icon图标
tabber标签栏-自定义颜色
点击切换用vant2的路由模式
默认首页在routes里加redirect
登录页静态布局+图形验证码功能p110
头部navbar
通用样式覆盖
request模块-axios封装-13min
使用axios请求后端接口,单独封装到一个request模块
request模块放在utils/request.js文件里
接口文档地址: https://apifox.com/apidoc/shared-12ab6b18-adc2-444c-ad11-0e60f5693f66/doc-2221080
基地址:http://cba.itlike.com/public/index.php?s=/api/
request文件里面放axios官方文档-axios实例(https://www.axios-http.cn/docs/instance)
- 创建axios实例
创建axios实例不会污染原始的axios实例,如果触及就会改底层参数 - 自定义配置-请求/响应 拦截器
添加请求拦截器
给原本axios实例加配置,没加到创建的实例上,要想不污染原始,就改成instance(创建的axios实例上)
axios的接口文档要从智慧商城找(http://cba.itlike.com/public/index.php?s=/api/)
const instance = axios.create({baseURL: 'https://some-domain.com/api/',//接口文档地址timeout: 1000,//超时时间headers: {'X-Custom-Header': 'foobar'}//这个没用
});
添加响应拦截器
导出配置好的实例(instance)
获取图片验证码数据结构,要复制地址
- base64:图片
- key:唯一标识
- md5:验证
图形验证码
获取短信验证码-39min
前两个缺一不可
4. captchacode 图形验证码值
5. captchakey验证码的唯一标识
6. mobile手机号
api接口模块-封装图片验证码接口p111
api模块(存放封装好的请求函数)
以前的模式
- 页面请求代码太多,可阅读性不高
- 相同请求没有复用
- 请求没有统一管理
封装api模块的好处 - 请求与页面逻辑分离
- 相同的请求可以直接复用
- 请求进行了统一管理
toast轻提示p112
点击出现轻提示
进入页面出现轻提示
this.$toast()只能用在实例组件里
短信验证倒计时p113
totalSecond和second要一致
封装短信验证请求接口智慧商城里
import { getMsgCode } from '@/api/login’别忘了导入
<template><div class="login" @click="getPicCode"><button>wds</button><input type="number"><button @click="getCode">{{ second===totalSecond? '获取验证码':second+'秒后重新发送'}}</button></div>
</template><script>
import { getMsgCode } from '@/api/login'export default {name: 'login-page',data () {return {picKey: '',picUrl: '',totalSecond: 6, // 总秒数second: 6, // 当前秒数timer: null, // 定时器idmobile: '', // 手机号picCode: ''// 用户输入的图形验证码}},methods: {// 获取图片验证码async getPicCode () {// Toast('获取图形验证码成功')this.$toast('wdf')},// 校验手机号和图形验证码是否合法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},// 获取验证码async getCode () {if (!this.validFn()) {return}// 当前没有定时器开着,且totalsecond和second一致(秒数归位)才可以倒计时if (!this.timer && this.totalSecond === this.second) {// 发送请求await getMsgCode(this.captchaCode, this.captchaKey, this.mobile)// 开启倒计时setInterval(() => {this.second--if (this.second <= 0) {clearInterval(this.timer)this.timer = null// 重置定时器idthis.second = this.totalSecond// 归位}}, 1000)}}},destroyed () {clearInterval(this.timer)// 离开页面清除定时器}
}
</script><style></style>
响应拦截处理器-统一处理错误p115
status
// 添加响应拦截器
instance.interceptors.response.use(function (response) {// 2xx 范围内的状态码都会触发该函数。// 对响应数据做点什么(默认axios会多包装一层data,需要响应拦截器中处理一下)const res = response.dataif (res.status !== 200) {// 给提示Toast(res.message)// 抛出一个错误的promisereturn Promise.reject(res.message)}return response.data
}, function (error) {// 超出 2xx 范围的状态码都会触发该函数。// 对响应错误做点什么return Promise.reject(error)
})
登录权证信息存储token和userld(user模块)/将token权证信息存入vuexP116
user模块放在store下面
export default {namespaced: true,// 提供数据state () {return {// 个人权证相关userInfo: {token: '',userId: ''}}},// 提供修改数据的方法的mutations: {},// 提供异步操作actions: {},// 提供基于state派生出来的属性getters: {}
}
storage存储模块-vuex持久化处理P117
vuex刷新会丢失,storage模块放utils里
// 约定一个通用的键名
const INFO_KEY = 'hm-shopping-info'// 获取个人信息
export const getInfo = () => {const defultObj = { token: '', userId: '' }const result = localStorage.getItem(INFO_KEY)return result ? JSON.parse(result) : defultObj
}// 设置个人信息
export const setInfo = (obj) => {localStorage.setItem(INFO_KEY, JSON.stringify(obj))
}// 移除个人信息
export const removeInfo = () => {localStorage.removeItemItem(INFO_KEY)
}
import { getInfo, setInfo } from '@/utils/storage'export default {namespaced: true,// 提供数据state () {return {// 个人权证相关userInfo: getInfo}},// 提供修改数据的方法的mutations: {// 所有mutations的第一个参数,都是statesetUserInfo (state, obj) {state.userInfo = objsetInfo(obj)}},// 提供异步操作actions: {},// 提供基于state派生出来的属性getters: {}
}
添加请求loading效果P118
toast轻提示中的加载提示
页面访问拦截p119
// 所有的路由在真正访问到之前,都会经过全局前置守卫
// 只有全局前置守卫放行了,才能到达对应的页面// 全局前置导航守卫
// to:到哪里去,到哪去的完整路由信息对象(路径,参数)
// from:到哪里来,从哪来的完整路由信息对象(路径,参数)
// next:是否放行
// (1)next()直接放行,放行到to要去的路径
// (2)next(路径)进行拦截,拦截到next里面配置的路径
router.beforeEach((to, from, next) => {// ...// 返回 false 以取消导航return false
})
// 定义一个数组,专门用户存放所有需要权限访问的页面
const authUrls= ['']
router.beforeEach((to, from, next) => {// ...// 返回 false 以取消导航// return falseif (!authUrls.includes(to.path)) {// 非权限页面,直接放行next()return}// 是权限页面,需要判断tokenconst token = store.getters.tokenif (token) {next()} else {next('/login')}
})
getters: {token (state) {return state.user.userInfo.token}},
首页-静态页面结构准备&动态渲染P120
- 静态结构准备(直接cv)
<template><div class="home"><!-- 导航条 --><van-nav-bar title="智慧商城" fixed /><!-- 搜索框 --><van-searchreadonlyshape="round"background="#f1f1f2"placeholder="请在此输入搜索关键词"@click="$router.push('/search')"/><!-- 轮播图 --><van-swipe class="my-swipe" :autoplay="3000" indicator-color="white"><van-swipe-item><img src="@/assets/banner1.jpg" alt=""></van-swipe-item><van-swipe-item><img src="@/assets/banner2.jpg" alt=""></van-swipe-item><van-swipe-item><img src="@/assets/banner3.jpg" alt=""></van-swipe-item></van-swipe><!-- 导航 --><van-grid column-num="5" icon-size="40"><van-grid-itemv-for="item in 10" :key="item"icon="http://cba.itlike.com/public/uploads/10001/20230320/58a7c1f62df4cb1eb47fe83ff0e566e6.png"text="新品首发"@click="$router.push('/category')"/></van-grid><!-- 主会场 --><div class="main"><img src="@/assets/main.png" alt=""></div><!-- 猜你喜欢 --><div class="guess"><p class="guess-title">—— 猜你喜欢 ——</p><div class="goods-list"><GoodsItem v-for="item in 10" :key="item"></GoodsItem></div></div></div></template><script>
import GoodsItem from '@/components/GoodsItem.vue'
export default {name: 'HomePage',components: {GoodsItem}
}
</script><style lang="less" scoped>// 主题 padding.home {padding-top: 100px;padding-bottom: 50px;}// 导航条样式定制.van-nav-bar {z-index: 999;background-color: #c21401;::v-deep .van-nav-bar__title {color: #fff;}}// 搜索框样式定制.van-search {position: fixed;width: 100%;top: 46px;z-index: 999;}// 分类导航部分.my-swipe .van-swipe-item {height: 185px;color: #fff;font-size: 20px;text-align: center;background-color: #39a9ed;}.my-swipe .van-swipe-item img {width: 100%;height: 185px;}// 主会场.main img {display: block;width: 100%;}// 猜你喜欢.guess .guess-title {height: 40px;line-height: 40px;text-align: center;}// 商品样式.goods-list {background-color: #f6f6f6;}</style>
还要导入
2. 封装接口
导入api