一:创建项目
进去选择第三个,人工选择:
二:加入git版本管理
查看git日志:
github首次创建vuecli ,自己提交了第一次代码
第二步,将本地仓库添加远程仓库地址
add后的origin后面地址 的名字,远程仓库的名字
以后想添加可以用
指定提交暂存区的资源 形成历史记录
git add xxx 或者用 git commit -m “xxx”
git push
三.调整初始目录结构
删除router
修改完之后:
git status 查看之前的操作进行的影响和变化
git add .
将变化的目录提交到暂存区
再次查看git status ,绿色表示添加到暂存区的资源
形成历史记录
再push
打开commit记录可以看到
四:导入图标
一般设计师设计为svg图片,再转化为icon文件
这样图标就算放大也不会模糊,也不需要额外的请求,加载一次就够了
怎么转化为icon文件
用阿里iconfont转换,创建项目
将样式文件尽量放前,否则可能会出错
五:引入Vant组件库
Vant 3 - Mobile UI Components built on Vue
六:移动端页面适配
在不同设备不同
mainjs引入:import 'amfe-flexible'
将px转为rem npm i postcss-pxtorem -D
根目录中添加:
有种情况不会转 写行内样式 style=“padding:20px”
配置内容: 插件 autoprefixer:兼容不容浏览器
// lib-flexible 的 REM 适配方案:把一行分为 10 份,每份就是十分之一
// 所以 rootValue 应该设置为你的设计稿宽度的十分之一
// 我们的设计稿是 750,所以应该设置为 750 / 10 = 75
// 但是 Vant 建议设置为 37.5,为什么?因为 Vant 是基于 375 写的
// 所以必须设置为 37.5,唯一的缺点就是使用我们设计稿的尺寸都必须 / 2
// 有没有更好的办法?
// 如果是 Vant 的样式,就按照 37.5 来转换
// 如果是 我们自己 的样式,就按照 75 来转换
// 通过查阅文档,我们发现 rootValue 支持两种类型:
// 数字:固定的数值
// 函数:可以动态处理返回
// postcss-pxtorem 处理每个 CSS 文件的时候都会来调用这个函数
// 它会把被处理的 CSS 文件相关的信息通过参数传递给该函数
rootValue ({ file }) {
return file.indexOf('vant') !== -1 ? 37.5 : 75
},
八:封装请求模块
登陆页面:
三:个人中心:
校验
规定只能输入数字,最大长度位11
vant中表单的submit只有在表单验证成功之后才能提交
验证码倒计时模块
登陆页面轻提示:
底部标签栏如何处理比较好:
创建为公共组件,可以但是每一个页面都要重新下载这个组件
更好的办法是设置子路由,这样就不会重新加载了?
具体操作如下:
具体路由表如下
import()采用动态加载,当使用的时候才去加载
轻提示:
这里的结构挺有意思
notlogin-btn是一个整体,他的大小由图片和文字确定
他的父标签为了能让他居中对齐,可以采用flex对齐
他的子标签也要用flex对齐,这点很重要
最好用他这个可以实现懒加载等
为什么要在vuex里存储数据,因为数据是响应式的
一定要注意这路是 this.$dialog
vant官方并不是这样写的
携带token数据:
用户携带token数据登录
第一种发送请求的时候携带:
授权,如果只有这个请求携带
Tab标签页组件“:
active表示上来表示的标签 从0开始 0 1 2 3
右边框
下边框:
默认有了
宽高:
怎么设置
用f12查看类名并修改:
修改底部条样式:
使用&:before伪元素
拿到数据:
加载缓存,下拉刷新
不能单纯的规定一个list数组 点开a 等于a 点开b等于b,这样其实就会覆盖,再返回a时候就需要重新加载,怎么解决
利用组件加载
父组件加载子组件
子组件中加载的数据就会进去
channel接收,并规定不能为空,否则报错
vant标签组件本身就具有懒加载的功能,点一下哪个哪个才加载数据,体现在开发者工具里面就算
点哪个标签才有哪个articlelist组件,里面才有prop接收的数据
一个新奇的接口,返回数据,通过时间戳返回数据的第几页
列表组件:
加载列表数据:
methods: {
// 初始化或滚动到底部的时候会触发调用 onLoad
async onLoad () {
try {
// 1. 请求获取数据
const { data } = await getArticles({
channel_id: this.channel.id, // 频道ID
// 你可以把 timestamp 理解为页码
// 如果请求第1页数据:当前最新时间戳 Date.now
// 如果请求之后的数据,使用本次接口返回的数据中的 pre_timestamp
timestamp: this.timestamp || Date.now(), // 时间戳,请求新的推荐数据传当前的时间戳,请求历史推荐传指定的时间戳
with_top: 1 // 是否包含置顶,进入页面第一次请求时要包含置顶文章,1-包含置顶,0-不包含
})
// 模拟随机失败的情况
// if (Math.random() > 0.5) {
// JSON.parse('dsnajndjsa')
// }
// 2. 把请求结果数据放到 list 数组中
const { results } = data.data
// 数组的展开操作符,它会把数组元素一个一个拿出来
this.list.push(...results)
// 3. 本次数据加载结束之后要把加载状态设置为结束
this.loading = false
// 4. 判断数据是否全部加载完成
if (results.length) {
// 更新获取下一页数据的时间戳
this.timestamp = data.data.pre_timestamp
} else {
// 没有数据了,将 finished 设置为 true,不再 load 加载更多了
this.finished = true
}
} catch (err) {
// 展示错误提示状态
this.error = true
// 请求失败了,loading 也需要关闭
this.loading = false
}
},
页面的加载数据怎么办,要给页面设置一个底边距
- load 事件:
+ List 初始化后会触发一次 load 事件,用于加载第一屏的数据。
+ 如果一次请求加载的数据条数较少,导致列表内容无法铺满当前屏幕,List 会继续触发 load 事件,直到内容铺满屏幕或数据全部加载完成。
- loading 属性:控制加载中的 loading 状态
+ 非加载中,loading 为 false,此时会根据列表滚动位置判断是否触发 load 事件(列表内容不足一屏幕时,会直接触发)
+ 加载中,loading 为 true,表示正在发送异步请求,此时不会触发 load 事件
- finished 属性:控制加载结束的状态
+ 在每次请求完毕后,需要手动将 loading 设置为 false,表示本次加载结束
+ 所有数据加载结束,finished 为 true,此时不会触发 load 事件
-->
请求失败的处理:
catch(err){
this.error = true
this.loading= true
}
data中error=false
下拉刷新:
vant组件 Pullrefresh
结构:将搜索和汉堡包导航栏固定
加入fixed之后,由于脱离了文档流,下面的汉堡包被挡住,那么就要偏移同时还需要fixed怎么解决:
top之后,看到虽然下来了但没有显示,被list遮住了,需要用index,最后用了定位里面的子元素没有撑开父盒子,需要加left=0,right=0
此时list还有两个被汉堡包遮住,
记住滚动的位置,下次打开还是在这个位置:
如何让标签内容文章列表产生自己的滚动容器:
固定高度:height:xxx
溢出滚动 overflow-y:auto
css3新增了一种视口单位 vw和vh,何谓视口,根据浏览器大小,不受父元素影响
1vw=可视窗口宽度的百分之一,比如窗口宽度750,1vw为7.5px
1vh = 6.67px
最终这样
文章列表项有的是三张图,有的两张图 ,有的只有列表
怎么办
在component下创建一个组件,(别的组件也要用)
van-cell
解决图片的403
服务器查看服务端是否发送refer
处理事件 js
day.js 和moment.js dayjs更小
npm i dayjs
在utils中创建新的day.js
加载dayjs的初始化设置
直接导入dayjs初始配置,不向外暴露。
从后端拿到一个数据得到几年前
用dayjs
频道编辑:
弹出层组件:
在home组件点击面包组件,从下向上弹出
关于vue中class和style中的样式详细用法
在Vue中使用样式:class样式和style内联样式_一休、 り的博客-CSDN博客_vue 内联style
在home中激活的 在组件中同样激活
条件绑定处理样式
解决推荐频道的问题 :
已知请求可以获取到所有的频道,但是没有推荐的频道
怎么办,通过所有的频道 - 我的频道
两种方法:
更简单的方法:
如何添加频道:
由于对频道推荐采用的是computed:
返回的数组是排除我的频道的东西
给下面绑定点击事件,传递channel参数,然后向我的频道push,由于computed属性自带监视属性
下面的频道就不需要删除了
当用插槽之后,vant会自动套一个父元素,这个父元素加了一个绝对定位,这样应该去掉这个绝对定位,从而相对于父亲的父亲做相对定位
position:unset
v-show :isedit
如何让推荐位不出现删除的按钮:
v-show="isEdit && !fixChannel.includes(channel.id)"
fixChannel:[0]
如何实现点击某个标签主页面也跳到某个标签
给我的频道注册点击事件,拿到index值(同active值)然后通过子组件给父组件传递参数,
频道编辑:持久化存储
要求在未登录的状态下的操作,在退出登录之后还保留,
在登陆状态下的操作进行的操作,登陆之后还保留
删除添加用户处理持久化
请求拿到我的频道,完整
文章搜素功能:(联想建议)(历史记录)(搜索结果)
搜索页面下面没有那四个标签,所以应该是一级路由
showaction 显示取消事件
不同情况下的 展示结果:
实现输入框的内容对应搜索列表的数据
传给子组件一个searchText
watch:{
searchtext{
handler(value){
csonsole.log(value)
},
immediate:true
}
}
这里有一个immediate 为什么
这里搜索建议的组件是在搜索框输入内容时才渲染,不写的话,第一次并不触发watch,第二次输入才触发
真正需要的是在侦听开始之后被立即调用,也就是值一变就修改
请求携带query ,请求axios参数写params
功能优化:优化防抖
每输入一个就会发送一个请求,频繁发生的行为
npm i lodash
// 按需加载有好处:只会把使用到的成员打包到结果中
import { debounce } from 'lodash'
如何使搜索高亮:
v-html=highlight(text)使文字变量,怎么办,一般可以用span标签再加上class
先定义一个字符串:
const highlightStr = `${this.searchText}`
如果正则匹配是一个变量即是动态的话必须new
const reg = new RegExp(this.searchText, 'gi')
return text.replace(reg, highlightStr)
当不生效的时候要注意是不是没有加/deep/。
传递搜索内容,当点击下面的项的时候就会自动进入下面的每一项
给每一个子组件都绑定一个click事件
@click="$emit('search',text)"
父组件传递 @search=“onsearch ” //自动触发search
onsearch(text){
将搜索栏的值变为text
}
出现的效果就是点一下自动搜索
因为父组件搜索框已经拿到的结果,然后为了拿到搜索数据,通过父子组件传值,子组件prop接收发请求
搜索结果组件
再次回忆一下list列表
在第一次和拉到最下边会触发,拉到最下边的时候,page会加加,所以请求的数据会不一样
如果没有了记着finish=true
添加搜索历史记录:
这个很好,可以考面试题了
将这个数据传递给history
接收:
操作搜索历史里面的数据:
由于这里删除了历史纪律是需要影响父亲传过来的历史i记录的,而如果在子组件使用父组件传过来的值进行直接修改是不行的,不能影响父组件,但是push可以
就要用父子组件通信
这里是浏览记录,当修改的状态下直接删除,但是如果不是则是直接搜索,那就要传值item
今日头条里面的搜索历史记录也不是采用请求api的方式,所以放在本地存储就可以了
文章详情:
路由转换
to:"'/title'+"
使用组件props来解耦路由传参:
通过props传递参数
接收的时候需要接收两种:
直接在url上打是字符串,点击跳转是数字
为什么请求文章接口会收到404:
后端返回数据中的大数字问题,之所以请求文章返回404是因为我们请求发送的文章的ID不正确,js能够准确-2^53到2^53之间的值无法精确表示某个值
也就是后端发过来的json数据太大,导致接收的数字出现问题
超出安全整数范围
怎么解决:npm i json=bigint
处理使得更见状:
处理加载过程:
created () {
this.loadArticle()
},
mounted () {},
methods: {
async loadArticle () {
// 展示 loading 加载中
this.loading = true
try {
const { data } = await getArticleById(this.articleId)
// if (Math.random() > 0.5) {
// JSON.parse('dsankljdnskaljndlkjsa')
// }
// 数据驱动视图这件事儿不是立即的
this.article = data.data
// 初始化图片点击预览
// console.log(this.$refs['article-content'])
setTimeout(() => {
this.previewImage()
}, 0)
// 请求成功,关闭 loading
// this.loading = false
} catch (err) {
if (err.response && err.response.status === 404) {
this.errStatus = 404
}
// this.loading = false
// console.log('获取数据失败', err)
}
// 无论成功还是失败,都需要关闭 loading
this.loading = false
},
文章正文的样式
github-markdown-css 找raw
这里markdown-css文件里采用的是px,之前会默认进行rem转换,但是这里尽量不进行rem转化,不然显得字体很小
或者手动的把里面的像素单位都改为2倍
如何等待页面加载完成之后再执行,比如v-if还没有渲染完毕
methods: {
async loadArticle () {
// 展示 loading 加载中
this.loading = true
try {
const { data } = await getArticleById(this.articleId)
// if (Math.random() > 0.5) {
// JSON.parse('dsankljdnskaljndlkjsa')
// }
// 数据驱动视图这件事儿不是立即的
this.article = data.data
// 初始化图片点击预览
// console.log(this.$refs['article-content'])
setTimeout(() => {
this.previewImage()
}, 0)
// 请求成功,关闭 loading
// this.loading = false
} catch (err) {
if (err.response && err.response.status === 404) {
this.errStatus = 404
}
// this.loading = false
// console.log('获取数据失败', err)
}
// 无论成功还是失败,都需要关闭 loading
this.loading = false
},
previewImage () {
// 得到所有的 img 节点
const articleContent = this.$refs['article-content']
const imgs = articleContent.querySelectorAll('img')
// 获取所有 img 地址
const images = []
imgs.forEach((img, index) => {
images.push(img.src)
// 给每个 img 注册点击事件,在处理函数中调用预览
img.onclick = () => {
ImagePreview({
// 预览的图片地址数组
images,
// 起始位置,从 0 开始
startPosition: index
})
}
})
}
}
}
关注不关注,用v-if v-else
添加请求,请求体参数用data,params参数向后加
关注取消关注方法
如果点击的特别快,网又慢,建议添加loading
默认data为false,触发事件后true ,完成之后false
这样同时能防止用户一直点发送请求
封装关注的组件:
这里其实传过去了is-follow 并且还通过is-follow的值
传的时候其实也把父类名,传过去了,子组件就不需要写样式了例如这样
传递这个值 这个值改变导致传给子组件的值改变
如果要修改v-model属性
在子组件
model: {
prop: 'isFollowed', // 默认是 value
event: 'update-is_followed' // 默认是 input
},
在组件上运用v-model
文章收藏:
但是加了子组件之后:
相当于又套了一个byn-item,为了能让样式生效,要在父组件样式前面添加/deep/
这里还有一个问题,下边评论,收藏的结构因为里面会用到请求来的article.collected
但是本来article是空对象,所以有时候渲染会直接渲染报错,因为请求数据还没拿到,这样最好是放在
这里面,因为v-else-if出现代表请求数据以及拿到,那么里面的collected也有了,这样就可以防止报错了
收藏交互:注意更新视图,为什么要这样,因为请求只发了一次,后面要修改页面上的东西
就用子父组件传就行了,但是为了保持下次刷新能用会发送请求
上面会相反,因为此时父组件还没有收到要修改的值,也就没有传给子组件,这样就会正好相反所以应该这样:
评论列表:这个列表经常用
async onLoad () {
try {
// 获取文章的评论和获取评论的回复是同一个接口
// 唯一的区别是接口参数不一样
// type
// a 文章的评论
// c 评论的回复
// source
// 文章的评论,则传递文章的 ID
// 评论的回复,则传递评论的 ID
// 1. 请求获取数据
const { data } = await getComments({
type: this.type, // 评论类型,a-对文章(article)的评论,c-对评论(comment)的回复
source: this.source.toString(), // 源id,文章id或评论id
offset: this.offset, // 获取评论数据的偏移量,值为评论id,表示从此id的数据向后取,不传表示从第一页开始读取数据
limit: this.limit // 获取的评论数据个数,不传表示采用后端服务设定的默认每页数据量
})
// 2. 将数据添加到列表中
const { results } = data.data
this.list.push(...results)
// 把文章评论的总数量传递到外部
this.$emit('onload-success', data.data)
// 3. 将 loading 设置为 false
this.loading = false
// 4. 判断是否还有数据 这里是判断当前数据比如第一次请求得到的是10,那就是true,至于下次可能返回空
if (results.length) {
// 有就更新获取下一页的数据页码
this.offset = data.data.last_id
} else {
// 没有就将 finished 设置结束
this.finished = true
}
} catch (err) {
this.error = true
this.loading = false
}
}
父组件
这里有问题
list 一般只在滚到的时候才会发送onload请求,所以此时拿不到评论总数
怎么办 可以在一开始就调用this.onload这样就可以拿到
点赞图标:
:class="{like:istrue}"
&.liked = .like-btn.liked
点赞区域:
评论弹出层:
当文档说不需要传就写此项为null
发布组件:有三个组件联动 发布组件 根组件 list组件
如何在点完发布组件之后,能出现在list组件上:这里这样
list组件中本来是在data设置空但是为了能拿到发布的数据:
设置props接收,如果单独就是为了这两个数据交互可以用$bus,但是根组件这里也需要逻辑操作,
父组件接受:
处理:
评论回复组件:
props接收:这里有个validator 验证传过来的[a,c]是否包含value,以防错误传个d或者别的
source是,一个大数据对象,虽然会默认转化为字符串,但是我们发送的时候还是自己tostring
这样query参数拼接的url地址就不会出错,如果不写拼接的时候会多加一个引号,url编码的时候会多一个空格所以这里尽量自己tostring
在使用超出js安全范围的数据的时候尽量都要tostring一下
当在回复评论列表的时候,会出现两次数据为什么
因为list列表在出现可视区域的时候就会自动调用,onload方法
为了在首页还没看到list数据能拿到多少评论就加载了
所以率先调用了这个,
回复评论的视图:
下面评论固定,中间部分滚动
依赖注入:
回复评论里post评论怎么做?
这里还需要拿到art_id的值,怎么拿最容易想到的就是从relpy组件里拿,reply也没有再从index里拿,这里有一种方法叫依赖注入,首先在根父组件中声明,(爷爷组件)
编辑个人资料
vant组件输入框:
这里有一个问题:设置昵称点取消,上一次输入的内容还会保存到下一次,怎么做?
原因是点击取消之后之前的那个组件还在,这里设置v-if
duration:0表示一直转,直到出现$toast表示发送成功或失败才消失
methods: {
async onConfirm () {
this.$toast.loading({
message: '保存中...',
forbidClick: true, // 禁止背景点击
duration: 0 // 持续展示
})
try {
const localName = this.localName
if (!localName.length) {
this.$toast('昵称不能为空')
return
}
await updateUserProfile({
name: localName
})
// 更新视图
this.$emit('input', localName)
// 关闭弹层
this.$emit('close')
// 提示成功
this.$toast.success('更新成功')
} catch (err) {
this.$toast.fail('更新失败')
}
}
}
}
picker选择器:
选择性别男女
这里也是需要和数组里的大小相呼应
传入日期
修改头像数据:
type=input hidden
当点击头像的时候,$refs.file.click()
给file绑定了onchange事件,当两次选择相同的图片就不会弹窗解决方案如下:
每次到最后要把value设置为0
这里还有传图片要用blob数据,window.URL.createObjectURL(file)
将取消,完成放在底部,样式这样写
position bottom left right display:flex
图片裁切:
cropper.js
使用这个库的要求 ,
1.外面套一个父元素
2.设置img样式:
3.写在mounted里面
要拿到img数据
拿到src的值
viewmode:0默认是0
1:只能在画布区域拖动
dragmode:move 可以拖动画布
aspecratio:比例
cropBOxmovable:后面的画布可以移动 截图区域不能移动 false
cropBOXresizele:截图区域不能缩放 false
background:false 关掉背景
move:画布可以拖动 默认为true
点完成有两种方式修改图片:
第一种与客户端交互
console.log(this.cropper.getData) 可以拿到所有裁切数
据
基于纯客户端获取裁切数据传递blob数据
传递formData:记着如果是multipart/form-data
(常见于文件上传)