# 主页模块
# 首页频道基本布局
目标:基于vant组件实现频道效果布局
- 基本结构
<van-tabs swipeable>
<van-tab :key="index" v-for="index in 8" :title="'标签 ' + index">
<div class="scroll-wrapper">
<van-cell-group>
<van-cell v-for="item in 20" :key="item">{{index + '-' + item}}</van-cell>
</van-cell-group>
</div>
</van-tab>
</van-tabs>
<span class="bar_btn" slot="nav-right">
<van-icon name="wap-nav"></van-icon>
</span>
- 样式处理
.van-tabs {
height: 100%;
display: flex;
flex-direction: column;
/deep/ .van-tabs__wrap {
height: 36px;
padding-right: 36px;
.van-tab {
line-height: 36px;
}
.van-tabs__line {
background-color: #3296fa;
height: 2px;
}
}
/deep/ .van-tabs__content{
flex: 1;
overflow: hidden;
}
/deep/ .van-tab__pane{
height: 100%;
.scroll-wrapper{
height: 100%;
overflow-y: auto;
}
}
}
.bar_btn {
width: 36px;
height: 35px;
position: absolute;
top: 0;
right: 0;
&::before {
content: "";
width: 100%;
height: 100%;
position: absolute;
z-index: 999;
box-shadow: 0 0 10px #999;
transform: scale(1, 0.6);
}
.van-icon-wap-nav {
width: 100%;
height: 100%;
background: #fff;
text-align: center;
line-height: 35px;
position: relative;
z-index: 1000;
&::before {
font-size: 20px;
}
}
}
/deep/作用:父级样式影响到子级- /deep/ 这个符号的作用由vue-loader (opens new window)决定
- 默认情况下,scoped模式的样式会自动编译类名 .a[data-v-f3f3eg9]
- 如果类名的前面添加 /deep/ 符号,那么类名就不会被编译,而是保持原始的类名

# 实现上拉加载更新
目标:基于vant组件实现上拉效果
需求:滚动到页面底部时,加载下一页数据
- 基本结构
<van-list v-model="upLoading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<van-cell v-for="item in articles" :key="item">{{item}}</van-cell>
</van-list>
- 状态数据
// ----列表需要的数据----
// 上拉加载中 (列表加载时,自动修改为true,修改完成后需要手动设置为false)
upLoading: false,
// 是否全部加载完成
finished: false,
// 文章列表
articles: []
- 功能实现
- onLoad 组件初始化默认执行一次,如果数据对应的页面不够一屏,自动再加载一次。
- 触发上拉加载触发当前函数 (获取数据,进行列表渲染)
onLoad () {
// 页面触底时触发该函数(这里应该加载一页新的数据)
// onLoad 本身有一个功能:如果加载一次后不够一页数据,会自动再加载一页
window.setTimeout(() => {
// 模拟假数据的分页加载
// 1、获取后端接口的数据(相当于得到了10个数)
const data = []
// 1-10 11-20 21-30 ...
for (let i = this.articles.length + 1; i <= this.articles.length + 10; i++) {
data.push(i)
}
// 2、把得到的数据追加到articles中(放进去10个数,而不是一个数组)
this.articles.push(...data)
// 3、数据加载完成,此时要告诉组件
this.loading = false
// 4、判断数据加载完成
if (this.articles.length >= 50) {
// 加载完成
this.finished = true
}
}, 1000)
}
# 下拉刷新效果
目标:实现列表的下拉刷新效果
- 基本结构
+<van-pull-refresh v-model="downLoading" @refresh="onRefresh" :success-text="refreshSuccessText">
<van-list v-model="upLoading" :finished="finished"
finished-text="没有更多了" @load="onLoad">
<van-cell-group>
<van-cell v-for="item in articles" :key="item">{{item}}</van-cell>
</van-cell-group>
</van-list>
+</van-pull-refresh>
- 状态数据
// 是否正在下拉刷新中
downLoading: false,
// 刷新成功的文案
refreshSuccessText: null
- 功能实现
onRefresh () {
// 控制下拉刷新
window.setTimeout(() => {
// 1、调用接口加载数据
// data 表示后端返回的数据
const data = [1, 2, 3, 4, 5]
// const data = []
if (data.length) {
// 表示有数据要更新
// 2、把得到的数据覆盖默认列表数据
this.articles = data
// 重置加载完成的标志
this.finished = false
// 如果刷新之后,不够一页数据,需要手动再调用一次接口
this.onLoad()
} else {
// 没有数据要更新
this.refreshSuccessText = '没有数据要更新'
}
// 通知下拉组件,刷新完成了
this.refreshing = false
}, 1000)
},
# 列表文章布局
目标:实现文章列表基本布局
- 页面结构-三张图
<div class="article_item">
<h3 class="van-ellipsis">PullRefresh下拉刷新PullRefresh下拉刷新下拉刷新下拉刷新</h3>
<div class="img_box">
<van-image class="w33" fit="cover" src="https://img.yzcdn.cn/vant/cat.jpeg"/>
<van-image class="w33" fit="cover" src="https://img.yzcdn.cn/vant/cat.jpeg"/>
<van-image class="w33" fit="cover" src="https://img.yzcdn.cn/vant/cat.jpeg"/>
</div>
<div class="info_box">
<span>你像一阵风</span>
<span>8评论</span>
<span>10分钟前</span>
<span class="close"><van-icon name="cross"></van-icon></span>
</div>
</div>
- 页面结构-一张图
<div class="article_item">
<h3 class="van-ellipsis">PullRefresh下拉刷新PullRefresh下拉刷新下拉刷新下拉刷新</h3>
<div class="img_box">
<van-image class="w100" fit="cover" src="https://img.yzcdn.cn/vant/cat.jpeg"/>
</div>
<div class="info_box">
<span>你像一阵风</span>
<span>8评论</span>
<span>10分钟前</span>
<span class="close"><van-icon name="cross"></van-icon></span>
</div>
</div>
van-ellipsis vant内置的样式 当文本内容长度超过容器最大宽度时,自动省略多余的文本。
w33 宽度33% 剩余1%当作间距。
w100 宽度100%
- 样式处理
.article_item{
h3{
font-weight: normal;
line-height: 2;
}
.img_box{
display: flex;
justify-content: space-between;
.w33{
width: 33%;
height: 90px;
}
.w100{
width: 100%;
height: 180px;
}
}
.info_box{
color: #999;
line-height: 2;
position: relative;
font-size: 12px;
span {
padding-right: 10px;
&.close{
border: 1px solid #ddd;
border-radius: 2px;
line-height: 15px;
height: 12px;
width: 16px;
text-align: center;
padding-right: 0;
font-size: 8px;
position: absolute;
right: 0;
top: 7px;
}
}
}
}
# 获取频道数据
目标:调用接口获取频道数据
src/api/api-channel.js
- 封装调用频道接口业务模块
/**
* 获取我的频道信息(如果没登录,获取的是后台设置的默认频道列表)
*/
import request from '@/utils/request.js'
export const getMyChannels = () => {
return request({
method: 'get',
url: 'user/channels'
})
}
- 调用接口
import { getMyChannels } from '@/api/api-home.js'
created () {
this.getMyChannels()
},
methods: {
// 获取频道列表
async getMyChannels () {
const data = await getMyChannels()
this.channels = data.data.channels
},
}
- 列表数据
// 频道需要的数据
channels: [],
- 渲染组件
<van-tab :key="item.id" v-for="item in channels" :title="item.name">
# 频道对应文章数据结构分析
目标:熟悉文章和频道数据结构
- 分析:文章列表与频道关系
- 结论:频道数据应该包含文章列表数据
// 获取频道列表
async getMyChannels () {
const data = await getMyChannels()
// data.chennels 数据结构 [{id,name},...]
// 不满足页面的数据要求,转化成另外一种格式。
// map() 数组提供的函数,遍历当前数组,生成一个新的数组,
// 在遍历的时候回调函数的返回值,就是新数组中的每一项。
// 注意:在箭头函数 => {} 解析的时候不是对象 而是代码块
// 写法:如果一定要直接返回对象 包裹小括号
this.channels = data.channels.map(item => ({
id: item.id,
name: item.name,
// 是否正在上拉加载中
upLoading: false,
// 是否正在下拉刷新中
downLoading: false,
// 是否加载了所有的数据
finished: false,
// 文章列表
articles: [],
// 获取数据的时间戳
timestamp: Date.now()
}))
},
- timestamp 相当于分页的页码
- upLoading 加载中
- finished 没有数据
- downLoading 刷新中
- articles 文章列表
# 动态渲染文章列表
目标:动态渲染文章列表
- 封装调用文章列表数据接口方法
// 获取文章列表数据
export const getArticles = (channelId, timestamp) => {
return request({
method: 'get',
url: 'v1_1/articles',
params: {
timestamp: timestamp,
channel_id: channelId,
with_top: 1
}
})
}
- 获取当前频道数据
computed: {
// 当前激活的频道(data中添加activeIndex索引数据)
activeChannel () {
return this.channels[this.activeIndex]
}
},
- 上拉加载 onLoad
// 加载文章列表数据
const data = await getArticles(this.activeChannel.id, this.activeChannel.timestamp)
// 把获取的数据累加到当前频道下的文章列表中
this.activeChannel.articles.push(...data.data.results)
// 结束上拉加载效果
this.activeChannel.upLoading = false
// 是否所有数据已经加载完毕
if (!data.data.pre_timestamp) {
// 已经没有更多数据了
this.activeChannel.finished = true
} else {
// 把后端返回的时间戳 记录下来 下次请求需要使用
this.activeChannel.timestamp = data.data.pre_timestamp
}
- 下拉刷新 onRefresh
this.activeChannel.timestamp = Date.now()
const data = await getArticles(this.activeChannel.id, this.activeChannel.timestamp)
// 结束下拉刷新效果
this.activeChannel.downLoading = false
// 判断是否有数据
if (data.data.results.length) {
this.activeChannel.articles = data.data.results
// 加载有数据的文案
this.refreshSuccessText = '更新成功'
// 防止看到 没有更多了 信息 (重新刷新列表,下一页应该是有数据的)
this.activeChannel.finished = false
// 加上时间戳 加载下一页数据
this.activeChannel.timestamp = data.data.pre_timestamp
// 防止数据不够一屏 再来一次上拉加载数据 onLoad
this.onLoad()
} else {
// 加载没有数据的文案
this.refreshSuccessText = '暂无更新'
}
- 渲染文章列表
<van-tabs swipeable v-model="activeIndex" >
<van-tab :key="channel.id" v-for="channel in channels" :title="channel.name">
<div class="scroll-wrapper">
<van-pull-refresh v-model="activeChannel.downLoading" @refresh="onRefresh" :success-text="refreshSuccessText">
<van-list v-model="activeChannel.upLoading" :finished="activeChannel.finished" finished-text="没有更多了" @load="onLoad">
<van-cell v-for="article in activeChannel.articles" :key="article.art_id.toString()">
<!-- 三张图布局 -->
<div class="article_item">
<h3 class="van-ellipsis">{{article.title}}</h3>
<div class="img_box" v-if="article.cover.type === 3">
<van-image class="w33" fit="cover" :src="article.cover.images[0]"/>
<van-image class="w33" fit="cover" :src="article.cover.images[1]"/>
<van-image class="w33" fit="cover" :src="article.cover.images[2]"/>
</div>
<div class="img_box" v-if="article.cover.type === 1">
<van-image class="w100" fit="cover" :src="article.cover.images[0]"/>
</div>
<div class="info_box">
<span>{{article.aut_name}}</span>
<span>{{article.comm_count}} 评论</span>
<span>{{article.pubdate}}</span>
<span class="close">
<van-icon name="cross"></van-icon>
</span>
</div>
</div>
</van-cell>
</van-list>
</van-pull-refresh>
</div>
</van-tab>
</van-tabs>
# 时间格式处理
目标:自定义过滤器格式化时间
- 封装过滤器
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import 'dayjs/locale/zh-cn'
dayjs.extend(relativeTime)
/**
* 过滤器函数
* @param {String} strDate - 就是过滤器 | 前的表达式的值
*/
const relTime = (strDate) => {
// 转换的逻辑
// moment 插件 dayjs 插件 都是处理时间格式
// dayjs 轻量一些
return dayjs().locale('zh-cn').from(strDate)
}
- 配置过滤器
export default {
install (Vue) {
+ Vue.filter('relTime', relTime)
}
}
- 使用过滤器
<span>{{article.pubdate|relTime}}</span>
# 图片懒加载
目标:实现图片懒加载效果(目的是提高性能) 预期效果:当屏幕中出现图片时,再去发送请求获取图片内容;尚未看到的图片暂时不发请求。 采用这种机制页面的加载效率比较高 如果想在普通的img标签上使用的话,那么可以通过v-lazy指令实现懒加载 //<img v-for="img in imageList" v-lazy="img" />
- vant内置插件,实现图片懒加载。
import Vant, { Lazyload } from 'vant'
// 配置插件
Vue.use(Lazyload)
// 启用
<van-image lazy-load></van-image>