头条笔记
1.移动端rem解决多屏适配
介绍
原因:
这个项目是移动端,所以,它需要去适配不同的手机屏幕。我们希望实现适配的效果是:与屏幕大小相关。以按钮为例:在大屏手机,按钮的宽高都大些,在小屏手机上尺寸小些。
分析:
Vant 中的样式默认使用
px
作为单位,如果需要使用rem
单位,推荐使用以下两个工具一起工作,来达到目标。-官网地址
- 将px单位转换rem单位 利用 postcss 和 postcss-pxtorem 插件将px自动转化成rem
- rem是相对于根标签的字体大小改变 利用 amfe-flexible插件 设置根标签字体大小(生产依赖)
步骤
1.安装包
yarn add postcss postcss-pxtorem@5.1.1 -D
# 生产依赖
# 后处理器 开发阶段使用
# 作用:把px单位自动转成rem单位
yarn add amfe-flexible
# 开发依赖
# 修改rem基准值的js插件 需要在打包后需要使用
# 作用: 根据设置屏幕的宽度去调整rem的值(html标签上font-size的大小)
# 它的默认计算方式是屏幕宽度的1/10,默认值是37.5
2.设置postcss
根目录下创建postcss.config.js文件,内容如下:
module.exports = {
plugins: {
'postcss-pxtorem': {
// 能够把所有元素的px单位转成Rem
// rootValue: 转换px的基准值。
// 例如一个元素宽是75px,则换成rem之后就是2rem。
rootValue: 37.5,
propList: ['*']
}
}
}
重启服务器
npm run serve
yarn serve
3.引入flexible
入口文件main.js导入 amfe-flexible
import 'amfe-flexible'
2.封装axios请求函数 通过awaitTo改进
介绍
原因:
为了调用ajax方便,这里我们先对 axios进行一次封装,把它封装成一个独立的模块,在需要的时候直接加载使用。
思路:
- 安装axios包
- 对axios进行二次封装
步骤
1.安装包
yarn add axios
2.二次封装axios
src
目录下创建utils/request.js
1.引入axios
import axios from 'axios'
2.设置基地址
//两种方式
//第一种设置基地址方式
const ajax = axios.create({
// baseURL: 'http://api-toutiao-web.itheima.net'
baseURL: 'http://toutiao-app.itheima.net' // 请求的基础路径
})
//第二种设置基地址方式
axios2.defaults.baseURL = 'http://toutiao-app.itheima.net'
3.导出请求
export default (url, method = 'GET', data = {}, params = {}, headers) => ajax({
url,
method,
data,
params,
headers
})
创建一个箭头函数
形参 是需要请求的数据
函数体内调用axios
相当于ajax({url,method,data,params,headers})
箭头函数 一行代码不用{} 且return
// 以后如果换网络请求的库, 直接更换这个文件里的代码, 保证代码的复用性和独立性(高内聚低耦合)
3.await错误处理
原因:
await
是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
所以await只能拿到到Promise.then的结果,不能拿到Promise.catch的结果
拓展复习
:Promise里要放异步任务
解决方法awaitTo
Promise 介绍
- then( )或catch( )都会在原地返回一个全新的promise对象
- then和catch函数体里的return,都会把值返回给原地生成的promise的then函数
export default ({ url, method = 'GET', params, data, headers }) => {
const pA = axios({
url: url,
method: method,
params: params,
data: data,
headers: headers
})
const pB = pA.then((res) => {
// 错误对象, 成功对象
return [null, res]
}).catch((err) => {
return [err, null]
})
return pB
}
解释:
- 发起axios请求在原地留下一个promise对象pA
- pA.then且catch( )会在原地留下一个promise对象pB
- pA的then且catch的return会把值传给pB的then
3.vant组件
注意:
在使用vant组件时,不要忘了引入他的css样式
import 'vant/lib/index.css'
在使用定制主题2和3时把引入的
import 'vant/lib/index.less'
vant定制主题方式
方法一
审查元素的方式,查看对应样式的class类名,进行样式覆盖 不生效类名前加 /deep/
图-定制主题方式一
方法二:
按照官方文档通过定制主题的方式,直接覆盖vant组件库里less变量
// 这里要把 .css 后缀名改为 .less
import 'vant/lib/index.less'
不然样式失效,
方法三
通过定制主题的方式,自定义less主题文件,基于文件的方式覆盖默认的less变量
在styles文件下新建一个less文件用来覆盖变量
@blue: #007bff;
@white: white;
@font-14: 14px;
// NavBar
@nav-bar-background-color: @blue;
@nav-bar-title-text-color: @white;
@nav-bar-title-font-size: @font-14;
在vue.config.js下添加如下配置
const path = require('path')
// 自定义主题的文件路径
const coverPath = path.join(__dirname, './src/styles/cover.less')
module.exports = {
css: {
loaderOptions: {
less: {
modifyVars: {
// 通过 less 文件覆盖(文件路径为绝对路径)
hack: `true; @import "${coverPath}";`
}
}
}
}
}
4.ESlint关闭规则
//不让变量名内有_下划线
步骤
- ESlint官网查询
- 在ESlint.js文件下修改
5.Token
1.存储Token
vuex文件夹下
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
Token: null//定义数据
},
mutations: {
//修改数据的方法
//参数1 :state
//参数2 :接收传过来的数据
tonkenFN (state, Token) {
state.Token = Token
}
},
actions: {
},
modules: {
}
})
接口调用处
<script>
import { mapMutations } from 'vuex'
//map映射Mutations下的方法
// from 来自 vuex
export default {
data () {
return {
token: 'asdfadsfasdfwer'
}
},
methods: {
//展开注意必须写[] 和 ''包裹
...mapMutations(['tonkenFN']),
buFn () {
this.tonkenFN(this.token)
}
}
}
</script>
2.持久化存储Token
原因:
网页刷新,所有变量回归初始化 ,vuex的值就消失 用户就得重新登录
解决:
把token数据保存在本地
步骤
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
//2.token的取值先看本地存储 没有 为空
Token: localStorage.getItem('token') || null
},
mutations: {
tonkenFN (state, Token) {
state.Token = Token
//1.把token数据传入vuex中也将其存入localStorage
//注意: localStorage.setItem后跟的是()不是=
//参数1 :键名
//参数2 :值
localStorage.setItem('token', Token)
}
},
actions: {
},
modules: {
}
})
6.路由守卫
需求 :
- 已经登陆了,不要再跳转到登录页(如果想重新登录,点击退出)
- 涉及用户操作的页面,需要登录(编辑,点赞,评论)
7.切换路由页面
代码实现
<van-tabbar v-model="active" router>
<van-tabbar-item icon="home-o" to="/home">首页</van-tabbar-item>
<van-tabbar-item icon="setting-o" to="/user">我的</van-tabbar-item>
</van-tabbar>
步骤 :
- van-tabbar 添加入
router
属性
V-自带的属性
- van-tabbar-item 添加入
to="/home"
原生实现
<router-link to="/home">首页</router-link>
//router-link 相当于一个a标签
8.首页-频道列表
展示图
接口图
代码
/api/index.js文件下
import store from '@/store'
//引入vuex 以前都是map映射 那是vue页面
//js页面 直接引入 store文件夹下的index文件
export const UserListAPI = () => {
return request({
url: '/v1_0/search',
headers:{
Authorization = 'Bearer ' + store.state.token
//注意:'Bearer ' 加空格 加空格 加空格
}
})
}
9.首页 - 文章列表
需求1 封装组件
展示图
将文章列表封装成一个单独的组件
步骤
- 在Home/components/ArticleList.vue //文章列表组件–只负责循环展示数据
- 首页—引入文章列表组件,替换Tab中间作为插槽使用
需求2.先铺静态页面(不考虑三张图片的问题)
展示图
步骤
- 简单的文章列表铺设( 不考虑图片)
- 封装网络请求
当前系统时间: new Date( ).getTime( ) 毫秒值
page 传几 返回第几页数据
-
(封装的展示组件/vant组件-都是只负责展示你传入的数据, 而交互的动作交回页面来完成-规范)
-
引入请求->创建数组->存储数据
-
父传子->将数组数据传给->文章列表组件
需求3.实现图片(1~3)图
- 先实现一张图片的展示( 观察数据)
- 由后端数据可知:cover里的type是图片的数量,images[ ]存储图片地址
- 渲染图片数据(实现一张图片展示)—v-if判断cover是否等于1 等于1展示
- 考虑三张图片的位置,渲染页面(多张图片v-for渲染)
10.首页-下拉刷新
步骤:
-
找到组件van-pull-refresh, main.js引入注册
-
包住ArticleList组件(内容列表)
-
获取新的数据, getArticleList方法-发请求拿新数据
-
isLoading改成false(关闭顶部加载中状态-保证下次还能继续下拉刷新)
11首页-上拉加载
BUG 1 下拉刷新
上拉加载更多.page++ 加载到page =3(或n )
这个时候,用户进行 下拉刷新 由于当前page=3
渲染的数据会是 第3页的
解决
在下拉时—把page改为 1
BUG 2 网页打开发起两次请求
问题:
- 第一次 : 在created里调用发起的 请求第一页
- 第二次 : list组件挂载到页面(但是第一页的数据没有回来,list没有高度,list认为触底了,所以马上执行onload)
- 请求了第2页
解决:
list组件添加一个v-if判断, 如果渲染list的数组的长度大于0就让list组件渲染
12.首页-频道点击切换
思路:
思路来源于用户操作
要实现点击频道切换页面?
点击频道—删除原数组,请求新数组, 渲染页面
根据什么请求不同的数据?
获取频道列表的接口 传id 的 传的id不同返回的数据不同
如何获取点击 频道的id呢?
看看vant组件文档
步骤
- 点击频道,拿到id
在tabs组件
click 事件返回 点击的频道的 name 和 title
所以 name 返回的是索引 title 返回的是标题
但是 我想要的是 id ( 请求频道标题的数据中有id) 怎么办
给 name属性设置动态属性 :name=“obj.id”
把name属性默认的属性换成id
现在点击频道 返回的就是click的回调就是id 和标题
-
data 中创建channelId用于存储id 默认值 0
-
在发起获取文章列表的数据中 当做参数传入(即可通过id获取不同的数据)
-
click的方法里 将获取的 id 赋给 channelId
-
把page设置为1 切换频道页码回到1,数据重新获取
-
将渲染文章数组 清空
-
重新调用 获取文章列表的方法
-
13.格式化时间
dayjs 中文官网:https://dayjs.fenxianglu.cn/
- 下载包
yarn add dayjs
- 使用页面 引入
dayjs
和relativeTime
和本地化语言包
// 导入 dayjs 的核心模块
import dayjs from 'dayjs'
// 导入计算相对时间的插件
import relativeTime from 'dayjs/plugin/relativeTime'
// 导入本地化的语言包
import 'dayjs/locale/zh'
- 配置插件和语言包
// 配置插件
dayjs.extend(relativeTime)
// 配置语言包
dayjs.locale('zh')
-
定义一个格式化时间的方法
/所有步骤 import dayjs from 'dayjs' import relativeTime from 'dayjs/plugin/relativeTime' import zh from 'dayjs/locale/zh-cn' export default { formatTime(时间参数){ dayjs.extend( relativeTime) dayjs.locale('zh') var a=dayjs() var b=dayjs(时间参数 ) returan a.to(b) } }
-
预处理数组
res.data.data.results.forEach( obj=>obj.pubdate=this.formatTime(obj.pubdate)
)
//循环数组 将处理完的数据在赋值给原数组
14.图片懒加载
图片懒加载:当图片滚动到视口区域,在加载
基于vant 组件实现
import { Lazyload } from 'vant'
Vue.use(Lazyload)
// 注册时可以配置额外的选项
Vue.use(Lazyload, {
lazyComponent: true
})//这句话是对模块进行懒加载
<img v-for="img in imageList" v-lazy="img" />
//用v-lazy 代替src
//img 是 图片地址
export default {
data() {
return {
imageList: [
'https://img01.yzcdn.cn/vant/apple-1.jpg',
'https://img01.yzcdn.cn/vant/apple-2.jpg',
],
};
},
};
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JFEtP9Li-1625989206635)(C:\Users\醒醒\AppData\Roaming\Typora\typora-user-images\image-20210710202216673.png)]
15.反馈面板
一级面板
使用: 反馈组件 ActionSheet面板
注意: 使用 get-container 指定挂载的节点
否则就会出现这种情况
解决
//ActionSheet组件标签上
get-container="body"
二级面板
思路:
点击 反馈垃圾内容时 给控制ActionSheet组件 反馈面板的数组 替换
使用三个数组
数组1 : 用来存放 当前反馈级别的内容(默认是 一级反馈的内容)
数组2 : 也用来存放 一级反馈的内容
数组3 : 用来存放 二级反馈的内容
思路:
默认是一级反馈的内容
点击 反馈垃圾内容时 替换数组 将数组3赋值给数组1
点击 取消 将数组 2 赋值给数组 1
代码实现
通过名字判断你点击的是哪一个选项,
后续步骤
- 看接口文档,api下定义反馈的方法
- 文章列表组件里写反馈组件标签
- 网络请求要写在index.vue 里
- 子传父 用 文章列表组件 将 反馈对象和文章id 传出
- 调用后台接口
this.$emit( 'action',action,this.artId)
//子传父 三个参数
16.数字过大问题
原因:
js 中的 安全数字 是一个 16位的数字
大于这个数会出错
解决方案:
json-bigint(https://www.npmjs.com/package/json-bigint)
步骤
-
下载json-bigint包
yarn add json-bigint
-
在request…js中使用, 对后台的数据, 不要让axios自动转成JS对象, 需要用json-bigint转换
import bigInt from 'json-bigint' const ajax = axios.create({ baseURL: 'http://toutiao-app.itheima.net', // 请求的基础路径 transformResponse: [function (data) { // 对内容进行处理 // data:就是本次请求获取的数据 // 在这里可以对它进行进一步的处理 -- JSONbig // 后端返回数据可能不是 JSON 字符串,而JSONbig.parse()只能处理JSON字符串 // 所以,为了保证代码可以正常执行,这里引入try-catch来捕获异常 try { // 尝试着进行大数的处理 return bigInt.parse(data) } catch { // 大数处理失败时的后备方案 return JSON.parse(data) } }] })
解释
为什么要写在request.js
中?
axios再把数据(JSON字符串)请求回来以后,帮你把JSON字符串 自动转换 JS数据类型
如果 数字大于16位 则16位后面的精度会出错
所以 我们要在 request.js 中修改
怎么实现?
对axios进行重构,自己来编写转换JSON字符串的过程
/transformResponse 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
//data 就是本次请求回来的数据
// 对 data 进行任意转换处理
return data;
}]
参考下面axios官网截图
axios官网:(http://www.axios-js.com/zh-cn/docs/#axios-create-config)
- **axios.create([config])**干什么的
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
//设置基地址
timeout: 1000,
//`timeout 指定请求超时的毫秒数(0 表示无超时时间)
// 如果请求话费了超过timeout的时间,请求将被中断
headers: {'X-Custom-Header': 'foobar'}
// headers 是即将被发送的自定义请求头
});
-
transformRequest干什么的
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据 transformResponse: [function (data) { //data 就是本次请求回来的数据 // 对 data 进行任意转换处理 return data; }], 也就是在本次请求回来后 ,但是还没有传递给js之前 对请求回来的数据进行处理
-
try…catch
- 首先,执行try { …} 中的代码
- 如果这里没有错误,则忽略catch( error):执行到try的末尾并跳过catch继续执行
- 如果这里出现了错误,则try停止执行,控制流转向catch(err)的开头,(变量err可以使用任何名称),将包含一个error对象,该对象包含了所发生的事件的详细信息
完整代码 第二行有疑问
import axios2 from 'axios'
const bigInt = require('json-bigint')({ storeAsString: true })
const axios = axios2.create({
transformResponse: [function (data) {
if (data.length === 0) {
// 如果data响应数据没有, JSON.parse('')会报错-JSON格式错误
return ''
} else {
try {
// 尝试着进行大数的处理
// 把JSON字符串, 转成JS数据类型 (相比JSON.parse来说, 它会对大数进行处理)
// 把大数转成BigInt类型对象, 而并不是普通的Number
// 后台给我的: 1324194433473708032
// JSON.parse转换会变成 1324194433473708000
// bigInt会转成 BigInt对象(正常显示这个大数)
return bigInt.parse(data)
} catch {
// 大数处理失败时的后备方案
return JSON.parse(data)
}
}
}]
})
文章列表删除思路
思路1 :
- 删除成功
- 把对应数据删除(删除一条数据 页面剩余9条数据)
- 问题 可能会删除光了,造成 白屏 体验不好
- **解决 ** 和后台商量 ,删除一个文章,接口一定要返回一个新的(补在后面)
思路2 :
- 删除后重新调用接口,重新调接口-拿回10个新的数据
共同点 :
- 使page=1
- 清空渲染数组
- 重新请求列表数据
17.axios拦截器
请求拦截器
过程: 前端(客户端) => axios =>axios拦截器触发 => 后端服务器
使用场景 可以在发起请求之前,带一些值
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
响应拦截器
过程: 响应拦截器:前端(客户端) <=axios <=axios响应器触发 <= 后端服务器
使用场景 处理响应状态(401 /…)统一处理
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
//当状态码2xx/3xx开头时进入这里
// 对响应数据做点什么
return response;
}, function (error) {
// 响应状态码 4xx/5xx进这里
// 对响应错误做点什么
return Promise.reject(error);
});
响应拦截器 使用场景 拦截401错误请求
- 手动修改token值,模拟401错误
- console.dir(err) 观看数据
详细代码
axios.interceptors.response.use(function (response) { //
return response
}, function (error) {
if (error.response.status === 401) { // 身份过期
// token无用,
store.commit('setToken', '')
router.push({ path: '/login' })
}
return Promise.reject(error)
})
/处理401情况, 遇到401自动切换到登录页面
登录页面
登录页
步骤
V- :代表vant里面的组件
- 静态页面
- 表单及页面-表单校验(V-form )
- 点击
提交
发起请求 (表单的name是提交的参数) - 登录成功和失败提示 (V-notify )
- 给登录按钮设置 禁用 和 加载动画 (V-button)
- 把token值存到vuex中
- token持久化保存
- 登录成功路由跳转到首页
首页
步骤一 底部
- 底部—导入tabbar组件
(V-tabbar)
- 底部—创建首页的二级页面Home和User/index.vue
- 底部—router/index.js -引入二级页面组件 -配置二级路由
- 底部—首页设置挂载点
- 底部—tabbar上设置router和to属性,点击首页和我的切换路由
步骤二 顶部
- 顶部—index.vue使用NavBar
(V-NavBar)
- 顶部—NavBar插槽技术-自定义左侧图片
- 顶部—右侧icon图标导入,修改颜色
(V-Icon)
步骤三 中间内容
- 中间—导航栏切换 -Tabs标签页组件-注册复制
(V-Tabs)
- 中间—定义接口方法,请求频道数据
- 中间—在当前页面使用接口拿到数据铺设的页面
- 中间—封装 展示文章单元格列表组件
- 引入注册组件, 替换到Tab中间作为插槽使用
文章单元格列表组件 内
-
简单的文章列表铺设(不考虑图片)
-
title和label区域-标签+css
-
回到index.vue - 请求数据—(封装的展示组件/vant组件-都是只负责展示你传入的数据, 而交互的动作交回页面来完成-规范)
-
封装文章列表数据API接口并引入
-
定义方法,先请求默认的推荐数据
-
创建数组,接收请求回来的数据
-
传入数组到子组件
文章列表组件
—>铺设列表单元格 (父传子)
技巧及小知识点
1.快速创建多个文件夹
mkdir 后面接文件夹名 ,同时多个 空格间隔
mkdir api styles utils
2.绝对路径
1.不要手动写绝对路径
const path = require('path')
// 自定义主题的文件路径
const coverPath = path.join(__dirname, './src/styles/cover.less'
3.路由跳转
this.$router.push({ path:'/layout'})
4.传参数
传参数的时候最好使用对象
对象是无序的只要名字符合就可以
5.尽快熟悉项目结构
画一个树形图显示
写出每个文件夹都是干什么的
6.规范
封装的组件/vant组件-都是只负责展示你传入的 数据,而交互的动作交回给页面完成
7.获取当前系统时间
- new Date( )
- new Date( ).getTime( ) 毫秒值
8.postcss处理css
注意 : postcss只会处理css代码,标签里的行内样式单位需要你自己写rem
9.图片403解决
有的网站会对图片做防盗链
防盗链:后台判断你请求的来源,是不是自己服务器发起的请求,如果不是,不返回数据
解决
public文件夹/index.html/html标签下的head
<meat name="referrer" content="no-referrer" >
10.在组件内绑定事件注意
在组件内绑定的任何事件都是自定义事件,(除了那些组件自己原有的事件)
11.事件修饰符 .native
@click.native="btn"
就是实现原生的click
12.数据预处理
数据与处理:在请求回来的数据使用之前,先把数据里要处理的数据拿出来完善
13.代码优化小技巧
-
函数内多层if-else 使用return 优化
代码如下:
actionFn (actionObj, artId) { if (actionObj.name === '拉黑作者') { return // 只要你敢进来, 我就让下面代码不执行 } if (actionObj.name === '不感兴趣') { const [err] = await articleDislikeAPI({ target: artId }) return } },
-
一模一样的代码重复使用封装函数