文章目录
初始化
-
创建工程,
vue create 工程名称
-
重置
App.vue
中的代码
<template>
<div>App 根组件</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style lang="less" scoped></style>
- 重置
index.js
中的代码
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
// 清空路由规则
const routes = []
const router = new VueRouter({
routes
})
export default router
-
清空component和views目录下的组件
-
VUE配置ESLint->刚开始可省略这步,不然会很痛苦!!!
参考博文:https://www.cnblogs.com/sinosaurus/p/11275671.html
- 首页和我的两个组件放到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占据了部分空间从而导致元素显示不完全,为了解决这个问题我们可以采用下面两种方法:
- 为最外层的div标签添加padding值
.home-container{
padding: 46px 0 50px 0;
}
- 在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>作者 0评论 发布日期</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的两种方法
-
在main.js中添加
Vue.prototype.$http = axios
,但这种方法无法实现复用,每一个新地址都要重新声明 -
在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 }} {{ comment }} 评论
发布日期 {{ pubTime }}</span
>
<!-- 关闭按钮 -->
<van-icon name="cross" />
</div>
</template>
</van-cell>
</div>
</template>
格式化时间
如果我们想要像csdn这样将文章发布的时间变成相对时间的话,我们可以利用dayjs来快速实现。
- 下载安装dayjs
npm install dayjs --save
- 在main.js入口文件中导入dayjs相关的模块
// 导入 dayjs 的核心模块
import dayjs from 'dayjs'
// 导入计算相对时间的插件
import relativeTime from 'dayjs/plugin/relativeTime'
// 导入中文语言包
import zh from 'dayjs/locale/zh-cn'
- 在main.js入口文件中,配置插件和语言包
// 配置“计算相对时间”的插件
dayjs.extend(relativeTime)
// 配置中文语言包
dayjs.locale(zh)
- 在 main.js 入口文件中,定义格式化时间的全局过滤器:
// dt 参数是文章的发表时间
Vue.filter('dateFormat', dt => {
// 调用 dayjs() 得到的是当前的时间
// .to() 方法的返回值,是计算出来的“相对时间”
return dayjs().to(dt)
})
- 调用过滤器实现相对时间的计算
上拉加载更多、下拉刷新
需要使用到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方法。
- 为了实现上拉请求下一页的十条数据,我们需要将
this.page++
并且重新调用请求数据的方法this.initArticleList()
- 在方法中请求完后需要将this.loading设置为false
- 为了不让新请求到的数据覆盖原来的数据需要将数据进行合并
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
- 引入 Lazyload 指令,并将 Lazyload注册为全局可用的指令
import { Lazyload } from 'vant';
Vue.use(Lazyload);
- 将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
具体实现步骤
- 在data中定义一个属性show,默认值为false,用来控制动作面板的显示与隐藏,action数组中存放的是操作的名称,即”不感兴趣“,”拉黑作者“,”反馈垃圾内容“,动作面板通过 actions 属性来定义选项,actions 属性是一个由对象构成的数组,数组中的每个对象配置一列,
data() {
return {
show: false,
actions: [
{ name: '不感兴趣' },
{ name: '拉黑作者' },
{ name: '反馈垃圾内容' }
]
}
}
- 添加actionSheet动作面板组件
<van-action-sheet v-model="show" :actions="actions" @select="onSelect" />
- 为了实现点击×出现动作面板需要给van-icon绑定click事件,点击×后show变为true,即动作面板显示,
<van-icon name="cross" @click="show = true" />
- 设置选中后的事件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)
}
}
- 不感兴趣和拉黑作者需要将对应的文章内容进行逻辑上的删除,我们可以对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)
}