创建项目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, // 定时器id
mobile: '', // 手机号
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// 重置定时器id
this.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.data
if (res.status !== 200) {
// 给提示
Toast(res.message)
// 抛出一个错误的promise
return 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的第一个参数,都是state
setUserInfo (state, obj) {
state.userInfo = obj
setInfo(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 false
if (!authUrls.includes(to.path)) {
// 非权限页面,直接放行
next()
return
}
// 是权限页面,需要判断token
const token = store.getters.token
if (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-search
readonly
shape="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-item
v-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