一起从零开始学VUE(13)移动端头条案例

初始化
  1. 创建工程,vue create 工程名称

  2. 重置App.vue中的代码

<template>
  <div>App 根组件</div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style lang="less" scoped></style>

  1. 重置index.js中的代码
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

// 清空路由规则
const routes = []

const router = new VueRouter({
  routes
})

export default router
  1. 清空component和views目录下的组件

  2. VUE配置ESLint->刚开始可省略这步,不然会很痛苦!!!

参考博文:https://www.cnblogs.com/sinosaurus/p/11275671.html

  1. 首页和我的两个组件放到views文件夹下,与路由相关的组件放到views下
完成User组件的内容
<template>
    <div class="user-container">
      <!-- 用户基本信息面板 -->
      <div class="user-card">
        <!-- 用户头像、姓名 -->
        <van-cell>
          <!-- 使用 title 插槽来自定义标题 -->
          <template #icon>
            <img src="../../assets/logo.png" alt="" class="avatar">
          </template>
          <template #title>
            <span class="username">用户名</span>
          </template>
          <template #label>
            <van-tag color="#fff" text-color="#007bff">申请认证</van-tag>
          </template>
        </van-cell>
        <!-- 动态、关注、粉丝 -->
        <div class="user-data">
          <div class="user-data-item">
            <span>0</span>
            <span>动态</span>
          </div>
          <div class="user-data-item">
            <span>0</span>
            <span>关注</span>
          </div>
          <div class="user-data-item">
            <span>0</span>
            <span>粉丝</span>
          </div>
        </div>
      </div>

      <!-- 操作面板 -->
      <van-cell-group class="action-card">
        <van-cell icon="edit" title="编辑资料" is-link />
        <van-cell icon="chat-o" title="小思同学" is-link />
        <van-cell icon="warning-o" title="退出登录" is-link />
      </van-cell-group>
    </div>
  </template>

<script>
export default {
  name: 'User',
  data() {
    return {
    }
  },
  created() {
  },
  computed: {
  },
  methods: {
  }
}
</script>
<style lang="less" scoped>
.user-container {
  .user-card {
    background-color: #007bff;
    color: white;
    padding-top: 20px;
    .van-cell {
      background: #007bff;
      color: white;
      &::after {
        display: none;
      }
      .avatar {
        width: 60px;
        height: 60px;
        background-color: #fff;
        border-radius: 50%;
        margin-right: 10px;
      }
      .username {
        font-size: 14px;
        font-weight: bold;
      }
    }
  }
  .user-data {
    display: flex;
    justify-content: space-evenly;
    align-items: center;
    font-size: 14px;
    padding: 30px 0;
    .user-data-item {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      width: 33.33%;
    }
  }
}

</style>

在这里插入图片描述

使用Vant组件库中的tabbar组件和navbar组件

官方链接:https://vant-contrib.gitee.io/vant/#/zh-CN/tabbar

在Home.vue中添加navBar导航栏

<van-nav-bar title="首页" fixed />

在App.vue组件中添加Tabbar标签栏,点击Tabbar中的item会有路由跳转,因此还需要配置index.js

<!-- 路由占位符 -->
<router-view></router-view>
<!-- tabbar -->
<van-tabbar route>
    <van-tabbar-item replace to="/" icon="home-o">首页</van-tabbar-item>
    <van-tabbar-item replace to="/user" icon="user-o">我的</van-tabbar-item>
</van-tabbar>
routes: [
    { path: '/', component: Home },
    { path: '/user', component: User }
]

请添加图片描述

从上面的动图中我们可以看见,首页中显示的最后一个元素是123456,但是实际中最后一个元素应该是单一元素1,这是由于固定在底部的tabbar占据了部分空间从而导致元素显示不完全,为了解决这个问题我们可以采用下面两种方法:

  1. 为最外层的div标签添加padding值
.home-container{
    padding: 46px 0 50px 0;
}
  1. 在NavBar或TabBar中添加 fixed placeholder

请添加图片描述

修改NavBar的默认样式

如果使用的是placeholder的方法,修改默认样式的时候需要添加/deep/,否则无效

/deep/ .van-nav-bar{
    background: #007bff;
}
/deep/ .van-nav-bar__title{
    color: white;
}

在这里插入图片描述

覆盖第三方样式时,如果直接覆盖不生效,则需要添加/deep/,这样修改的局限性在于只能修改当前组件的样式,如果想要修改全部的,可以使用定制主题,使其全局生效。

完成articleItme.vue组件的布局结构
<template>
  <div>
    <van-cell>
      <!-- 标题区域的插槽 -->
      <template #title>
        <div class="title-box">
          <!-- 标题 -->
          <span>文章的标题噢</span>
          <!-- 单张图片 -->
          <img src="https://www.escook.cn/vuebase/pics/1.png" alt="" class="thumb">
        </div>
        <!-- 三张图片 -->
        <div class="thumb-box">
          <img src="https://www.escook.cn/vuebase/pics/2.png" alt="" class="thumb">
          <img src="https://www.escook.cn/vuebase/pics/2.png" alt="" class="thumb">
          <img src="https://www.escook.cn/vuebase/pics/2.png" alt="" class="thumb">
        </div>
      </template>
      <!-- label 区域的插槽 -->
      <template #label>
        <div class="label-box">
          <span>作者 &nbsp;&nbsp; 0评论 &nbsp;&nbsp; 发布日期</span>
          <!-- 关闭按钮 -->
          <van-icon name="cross" />
        </div>
      </template>
    </van-cell>
  </div>
</template>

<style lang="less" scoped>
.home-container {
  padding: 46px 0 50px 0;
  /deep/ .van-nav-bar {
    background: #007bff;
  }
  /deep/ .van-nav-bar__title {
    color: white;
  }
}
</style>

使用request模块进行请求数据
挂载axios的两种方法
  1. 在main.js中添加Vue.prototype.$http = axios,但这种方法无法实现复用,每一个新地址都要重新声明

  2. 在utils文件夹中创建request.js

    import axios from 'axios'
    
    const request = axios.create({
      baseURL: 'https://www.escook.cn'
    })
    
    export default request
    
获取文章的列表数据

为了获取到文章的列表数据,我们需要定义一个initArticleList函数来向接口发起GET请求。并在生命周期函数created()中进行调用

created() {
    this.initArticleList()
},
methods: {
    async initArticleList() {
        // 发起GET请求,获取文章的列表数据
        // 解构赋值
        const { data: res } = await request.get('/articles', {
            //   请求参数
            params: {
                _page: this.page,
                _limit: this.limit
            }
        })
        console.log(res)
    }
}

为了方便代码的复用,我们将请求列表数据的代码进行封装

// 向外按需导出一个方法
import request from '@/utils/request.js'

export const getArticleListAPI = function(_page, _limit) {
  // 返回一个Promise实例
  return request.get('/articles', {
    params: {
      _page,
      _limit
    }
  })
}

在需要使用API接口的组件中,按需进行导入和传参

import { getArticleListAPI } from '@/utils/articleAPI.js'

传递文章详情信息

在父组件中,将元素传递给子组件,子组件中使用插值表达式和自定义属性进行接收。

  • 父组件Home.vue向子组件articleItem.vue进行传值
<articleItem v-for='item in artList' :key='item.id'
             :title="item.title"
             :comment='item.comm_count'
             :author='item.aut_name'
             :pubTime='item.pubdate'
             :imgList = 'item.cover'
             ></articleItem>
  • 子组件接收父组件的值

**注意:**带有默认值的对象或数组,其默认值必须从一个工厂函数获取

props: {
    title: {
      type: String,
      default: ''
    },
    author: {
      type: String,
      default: ''
    },
    imgList: {
      type: Object,
      default: function() {
        return { type: 0 }
      }
    },
    pubTime: {
      type: String,
      default: ''
    },
    comment: {
      type: [Number, String],
      default: 0
    }
  }

通过观察效果图,我们发现图片的显示形式有两种,一种是三张图片并排,另一种是单张图片,通过type=1和type=3来控制,因此为了区分显示这两种形式,我们使用v-if进行判断,具体代码如下:
在这里插入图片描述

<template>
  <div>
    <van-cell>
      <!-- 标题区域的插槽 -->
      <template #title>
        <div class="title-box">
          <!-- 标题 -->
          <span>{{ title }}</span>
          <!-- 单张图片 -->

          <img
            alt=""
            class="thumb"
            v-if="imgList.type === 1"
            :src="imgList.images[0]"
          />
        </div>
        <!-- 三张图片 -->
        <div class="thumb-box" v-if="imgList.type === 3">
          <img
            alt=""
            class="thumb"
            v-for="(image,index) in imgList.images"
            :key=index
            :src="image"
          />
        </div>
      </template>
      <!-- label 区域的插槽 -->
      <template #label>
        <div class="label-box">
          <span
            >作者 {{ author }} &nbsp;&nbsp; {{ comment }} 评论 &nbsp;&nbsp;
            发布日期 {{ pubTime }}</span
          >
          <!-- 关闭按钮 -->
          <van-icon name="cross" />
        </div>
      </template>
    </van-cell>
  </div>
</template>
格式化时间

如果我们想要像csdn这样将文章发布的时间变成相对时间的话,我们可以利用dayjs来快速实现。

  1. 下载安装dayjs
npm install dayjs --save
  1. 在main.js入口文件中导入dayjs相关的模块
// 导入 dayjs 的核心模块
import dayjs from 'dayjs'

// 导入计算相对时间的插件
import relativeTime from 'dayjs/plugin/relativeTime'

// 导入中文语言包
import zh from 'dayjs/locale/zh-cn'

  1. 在main.js入口文件中,配置插件和语言包
// 配置“计算相对时间”的插件
dayjs.extend(relativeTime)

// 配置中文语言包
dayjs.locale(zh)

  1. 在 main.js 入口文件中,定义格式化时间的全局过滤器:
// dt 参数是文章的发表时间
Vue.filter('dateFormat', dt => {
  // 调用 dayjs() 得到的是当前的时间
  // .to() 方法的返回值,是计算出来的“相对时间”
  return dayjs().to(dt)
})

  1. 调用过滤器实现相对时间的计算

在这里插入图片描述
在这里插入图片描述

上拉加载更多、下拉刷新

需要使用到vant 2的list组件,通过loading和finished两个变量控制加载的状态,当组件滚动到底部时,会触发load事件并将load设置为true,此时应该发起请求加载更多数据。在请求新一页的数据时,会将loading的值变成true,如果这期间,用户频繁上拉加载框,此时不会多次调用load事件,只有load为false时,才会触发请求事件。此处需要注意load的状态的变化。

官方示例:https://vant-contrib.gitee.io/vant/v2/#/zh-CN/list

上拉加载更多

在Home.vue中添加van-list组件后,需要在methods节点下定义onLoad方法。

  1. 为了实现上拉请求下一页的十条数据,我们需要将this.page++并且重新调用请求数据的方法this.initArticleList()
  2. 在方法中请求完后需要将this.loading设置为false
  3. 为了不让新请求到的数据覆盖原来的数据需要将数据进行合并this.artList = [...this.artList, ...res]旧数据在前,新数据在后
async initArticleList() {
    // 发起GET请求,获取文章的列表数据
    // 解构赋值
    const { data: res } = await request.get('/articles', {
        //   请求参数
        params: {
            _page: this.page,
            _limit: this.limit
        }
    })
    this.artList = [...this.artList, ...res]
    this.loading = false
    console.log(this.artList)
    // 如果请求的数据为空数组,则说明到底了,finished =true
    if (res.length === 0) {
        this.finished = true
    }
},
    // 滚动到底部
    onLoad() {
        console.log('loading')
        this.page++
        this.initArticleList()
    }
下拉刷新

list组件可以与pullRefresh组件结合使用,实现下拉刷新功能,操作完成后将v-model设置为false表示加载完成。

注:此处下拉刷新做成从头开始请求数据

onRefresh() {
    console.log('refreshing')
    // 清空列表数据
    this.finished = false

    // 重新加载数据
    // 将 loading 设置为 true,表示处于加载状态
    if (this.refreshing) {
        this.page = 1
        this.artList = []
        this.initArticleList()
        this.refreshing = false
    }
    this.loading = true
}

请添加图片描述

文章列表图片的懒加载

为了优化网站的性能,可以采用图片懒加载。此处使用基于 Vant 的 Lazyload 懒加载,可以轻松实现列表中图片的懒加载效果。

官方示例:https://vant-contrib.gitee.io/vant/v2/#/zh-CN/lazyload

  1. 引入 Lazyload 指令,并将 Lazyload注册为全局可用的指令
import { Lazyload } from 'vant';
Vue.use(Lazyload);
  1. 将v-lazy指令的值设置为你需要懒加载的图片
<div class="title-box">
    <!-- 标题 -->
    <span>{{ title }}</span>
    <!-- 单张图片 -->

    <img
         alt=""
         class="thumb"
         v-if="imgList.type === 1"
         v-lazy="imgList.images[0]"
         />
</div>
<!-- 三张图片 -->
<div class="thumb-box" v-if="imgList.type === 3">
    <img
         alt=""
         class="thumb"
         v-for="(image,index) in imgList.images"
         :key=index
         v-lazy="image"
         />
</div>

反馈操作

接下来我们要实现点击×出现一级反馈页面,可以选择”不感兴趣“,”拉黑作者“,”反馈垃圾内容“三种选项,进行前两种操作后,页面中对应的文章信息将会被”删除“,选择反馈垃圾内容则会出现二级反馈页面。

一级反馈页面

请添加图片描述
使用vant的ActionSheet 动作面板能够轻松的帮助我们实现反馈页面
官方示例:https://vant-contrib.gitee.io/vant/v2/#/zh-CN/action-sheet
具体实现步骤

  1. 在data中定义一个属性show,默认值为false,用来控制动作面板的显示与隐藏,action数组中存放的是操作的名称,即”不感兴趣“,”拉黑作者“,”反馈垃圾内容“,动作面板通过 actions 属性来定义选项,actions 属性是一个由对象构成的数组,数组中的每个对象配置一列,
data() {
    return {
      show: false,
      actions: [
        { name: '不感兴趣' },
        { name: '拉黑作者' },
        { name: '反馈垃圾内容' }
      ]
    }
  }
  1. 添加actionSheet动作面板组件
<van-action-sheet v-model="show" :actions="actions" @select="onSelect" />
  1. 为了实现点击×出现动作面板需要给van-icon绑定click事件,点击×后show变为true,即动作面板显示,
          <van-icon name="cross" @click="show = true" />
  1. 设置选中后的事件onSelect:默认情况下,点击选项时动作面板不会自动收起可以通过改变show的值来控制显示与隐藏,也可以为van-action-sheet添加close-on-click-action 属性开启自动收起
    onSelect(item) {
      // 默认情况下点击选项时不会自动收起
      // 可以通过 close-on-click-action 属性开启自动收起
      if (item.name === '不感兴趣') {
        this.show = false
      } else if (item.name === '拉黑作者') {
        this.show = false
      } else {
        console.log(item.name)
      }
    }
  1. 不感兴趣和拉黑作者需要将对应的文章内容进行逻辑上的删除,我们可以对item的id进行操作,通过自定义属性将操作好的id值传递给父组件Home.vue,在Home.vue组件中对id进行过滤输出
 onSelect(item) {
      if (item.name === '不感兴趣') {
        this.show = false
        console.log(this.artId)
        this.$emit('remove-article', this.artId)
      } else if (item.name === '拉黑作者') {
        // this.show = false
      } else {
        console.log(item.name)
      }
    }
  }

其中this.artId是计算属性,将文章的art_id转换成字符串类型

  computed: {
    artId() {
      return this.article.art_id.toString()
    }

在Home.vue中对原数组进行filter方法的过滤

    removeArticle(id) {
    // 对原数组进行 filter 方法的过滤
      this.artList = this.artList.filter(item => item.art_id.toString() !== id)
    }
二级反馈页面

二级反馈页面与一级反馈页面的操作累死,为了区分两个页面,在data中添加isFirst属性,默认isFirst为true

<van-action-sheet
  v-model="show"
  :actions="actions"
  @select="onSelect"
  v-if="isFirst"
/>
<van-action-sheet
  v-model="show"
  :actions="reports"
  cancel-text="取消"
  close-on-click-action
  @select="onSelect2"
  @closed="isFirst = true"
  v-else
/>

点击一级菜单中的反馈垃圾内容时,将this.isFirst = false此时二级菜单会显示出来,其他操作与一级反馈菜单一致

    onSelect2(item) {
      Toast('举报成功')
      this.$emit('remove-article', this.artId)
    }

请添加图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值