# 主页模块

# 首页频道基本布局

目标:基于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/ 符号,那么类名就不会被编译,而是保持原始的类名

image-20210126154751233

# 实现上拉加载更新

目标:基于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>