# 内容管理-文章列表
# 回顾
- 主页
- axios拦截器统一处理token
- axios响应拦截器处理返回的数据
- 导航守卫和拦截器的关系
- 处理token失效的问题(token的续签)
- 退出功能
- 原生的事件是否可以直接绑定到组件的标签上?不可以
- 如果确实想使用原生的事件,需要在添加事件修饰符native
- 基于组件库提供的规则实现事件的绑定
- 文章列表
- 配置组件的基本路由
- 控制左侧菜单的高亮 $route.path
- 文章列表页面的基本布局
- 顶部筛选条件
- 底部的内容列表
- 获取文章列表的数据并且填充表格
- 插槽
- 基本的插槽用法
- 具名插槽的用法
- 作用域插槽
# 列表页面功能
# 文章列表渲染完善
目标:通过作用域插槽定制相关列的布局样式
- 在
el-table-column组件之间通过插槽定制列的模板内容 - 如何获取每一列的数据?
<template v-slot='slotProps'>slotProps.row可以获取每一行对应的表格数据
<el-table
:data='list'>
<el-table-column label="封面">
<!-- 定制图片的效果 -->
<!-- slotProps.row表示其中一条数据,来源于自组件 -->
<template v-slot='slotProps'>
<el-image class="cover-img" :src="slotProps.row.cover.images[0]">
<!-- 如果图片的地址加载失败,就显示标签之间的内容 -->
<div slot="error" class="image-slot">
<i class="el-icon-picture-outline"></i>
</div>
</el-image>
</template>
</el-table-column>
<el-table-column label="标题" prop="title"></el-table-column>
<el-table-column label="状态">
<!-- 默认显示数组 -->
<!-- 这一列的内容由插槽决定 -->
<template v-slot='slotProps'>
<!-- status这个数据从哪里来的?子组件来的 -->
<el-tag v-if="slotProps.row.status===0" type="info">草稿</el-tag>
<el-tag v-else-if="slotProps.row.status===1">待审核</el-tag>
<el-tag v-else-if="slotProps.row.status===2" type="success">审核通过</el-tag>
<el-tag v-else-if="slotProps.row.status===3" type="warning">审核失败</el-tag>
<el-tag v-else type="danger">已删除</el-tag>
</template>
</el-table-column>
<el-table-column label="发布时间" prop="pubdate"></el-table-column>
<el-table-column label="操作" width="120px">
<!-- 这里是插槽内容 -->
<template>
<el-button plain type="primary" icon="el-icon-edit" circle></el-button>
<el-button plain type="danger" icon="el-icon-delete" circle></el-button>
</template>
</el-table-column>
</el-table>
- 模仿ElementUi内部实现
<!-- 父组件 -->
<el-table-column label="状态" >
<!-- 定制这一列的结果 -->
<!-- 需求:不同的状态值,显示不同的文本 -->
<!-- 这里如何获取子组件的值 -->
<template v-slot='abc'>
<div v-if='abc.row.status===2'>审核通过</div>
</template>
</el-table-column>
<!-- 子组件 -->
<!-- el-table-column组件的定义 -->
<template>
<div>
<td>
<slot v-bind:row='row'>{{row.status}}</slot>
</td>
</div>
</template>
<script>
export default {
data () {
return {
row: {
status: 2
}
}
}
}
</script>
# 频道条件动态渲染
目标:动态渲染下拉列表数据
- 获取频道列表数据
import { loadArticleList, loadChannelList } from '@/api/article.js'
// 获取筛选条件的频道列表数据
async loadChannelList () {
try {
const ret = await loadChannelList()
this.channels = ret.data.channels
} catch (e) {
console.log(e)
this.$message.error('获取频道列表数据失败!')
}
},
- 动态渲染频道列表数据
<!-- 频道列表 -->
<el-form-item label="频道列表:">
<el-select value='' placeholder="请选择频道列表">
<el-option :key='item.id' v-for='item in channels' :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
# 筛选功能实现
- 单选框数据绑定
- 下拉列表数据绑定
- 日期数据绑定
data () {
return {
filterParams: {
// 文章的状态
status: null,
// 文章所属频道
channel_id: null,
// 开始时间
begin_pubdate: null,
// 结束时间
end_pubdate: null,
// 页码
page: 1,
// 每页的条数
per_page: 10
},
// 文章发布时间范围
rangeTime: '',
// 文章列表数据
list: [],
// 文章列表总数
total: 0,
// 频道列表数据
channels: []
}
},
- 绑定操作
都是用v-model方式绑定数据
<el-radio-group v-model='filterParams.status'>
<el-date-picker
v-model='filterTime'
@change='handleChange'
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
></el-date-picker>
handleChange (date) {
// 监控时间返回的选择动作
this.filterParams.begin_pubdate = date[0]
this.filterParams.end_pubdate = date[1]
},
- 点击按钮触发接口调用
// 查询操作
handleQuery () {
// 点击查询后应该发生什么?调用接口查询文章列表数据并且传递参数
// 查询数据时,需要把页码重置为1,保证能查询到数据
this.filterParams.page = 1
this.loadArticleList()
},
# 文章列表分页
目标:熟悉分页组件的基本用法
- 分页基本布局
- layout 控制分页条的布局
- total 控制整个列表的总数
- current-page 表示当前的页码
- page-size 每页显示的条数
- current-change 表示页码切换时触发的事件
- 页面切换操作
<el-pagination
style="margin-top:20px;text-align:right"
background
layout="prev, pager, next"
@current-change="changePage"
:total="total"
:current-page="filterParams.page"
:page-size="filterParams.per_page"
></el-pagination>
// 改变分页
changePage (page) {
this.filterParams.page = page
this.loadArticleList()
}
# 删除文章
目标:能够实现删除文章的功能
步骤:
1、绑定删除按钮的点击事件并且获取id
2、调用接口删除文章
3、重新渲染文章列表
- 绑定删除文章事件
<el-button @click="handleDelete(scopeProps.row.id)" plain type="danger" icon="el-icon-delete" circle></el-button>
- 实现删除文章功能
// 删除文章
handleDelete (id) {
this.$confirm('确认要删除吗?')
.then(async () => {
await deleteArticle(id)
this.loadArticleList()
})
.catch((e) => {
// 这个catch有两个作用:1、点击取消触发;2、有异常触发
// 如果点击取消,那么参数e的值就是cancel
if (e === 'cancel') {
console.log('取消删除')
} else {
this.$message.error('删除文章失败!')
}
})
},
# 处理删除文章id超范围问题
js表示的整数最大值 Number.MAX_SAFE_INTEGER = 9007199254740991
超过这个范围后,js无法表示这个数
- 基于第三方包 json-bigint (opens new window) 解决这个问题
- 基本使用
- 安装依赖包
npm i json-bigint
- 导入包并使用
// json字符串
// 原生js把json字符串转换为对象时,不支持这么大的数
var obj = '{ "value" : 1367686301318381568, "v2": 123 }'
var info = JSON.parse(obj)
// 所以,你非要用原生js转换,就会导致数据失真
console.log(info.value) // 1367686301318381600
// 使用第三方包 json-bigint 提供的api可以正常处理这么大的数据
var JSONbig = require('json-bigint');
var info1 = JSONbig.parse(obj)
console.log(info1.value.toString()) // 1367686301318381568
- axios统一处理返回的数据
// 专门处理返回的数据格式
axios.defaults.transformResponse = [(data) => {
// 参数data表示后端返回的数据
// 删除成功后,后端有返回数据吗?没有
try {
// 把后端返回的超大整数转换为正确的数据
return JSONbig.parse(data)
} catch (e) {
console.log(e)
// 如果返回的数据无法转换为正常的数据,就返回原始内容
return data
}
}]
# 频道通用组件封装
目标:能够完成频道通用组件的封装
- 为什么要封装频道组件?多个地方都需要用到这个功能
- 封装组件的步骤
1、封装组件代码
- value 决定下拉列表默认选中谁(从父组件传递过来)
- input事件监听选中值的变化
- 值发生变化后,把最新的选中的值传递回父组件
this.$emit('select-channel', id)
<el-select :value='channel_id' @input='handleSelect' placeholder="请选择频道列表:">
<el-option v-for='item in channels' :key='item.id' :label="item.name" :value="item.id"></el-option>
</el-select>
2、使用组件
:channel_id='filterParams.channel_id'把值传递给子组件的value属性@select-channel='updateChannelId'用于接收子组件传递回来的最新选中的项目id:channels='channels'把频道列表数据传递给子组件用于遍历下拉列表
<my-channel
@select-channel='updateChannelId'
:channel_id='filterParams.channel_id'
:channels='channels'></my-channel>
// 更新频道id
updateChannelId (id) {
// 接收子组件传递过来的id值
this.filterParams.channel_id = id
},
# 另一种实现
- 先封装组件功能 (单文件组件 MyChannel.vue)
<template>
<el-select :value='value' @input='handleSelect' placeholder="请选择频道列表:">
<el-option v-for='item in channels' :key='item.id' :label="item.name" :value="item.id"></el-option>
</el-select>
</template>
<script>
import { loadChannelList } from '@/api/article.js'
export default {
name: 'MyChannel',
data () {
return {
channels: []
}
},
props: {
// channels: {
// // 配置类型的目的是进行类型验证,如果传递的数据不是这个类型,就会警告
// type: Array
// },
value: {
type: Number
}
},
methods: {
async loadChannelList () {
// 加载频道列表数据
try {
const ret = await loadChannelList()
this.channels = ret.data.channels
} catch (e) {
console.log(e)
this.$message.error('获取频道列表数据失败!')
}
},
handleSelect (id) {
// 这里的input事件函数中获取到的直接就是选项的id
// 这个规则是Element规定的
// 这里选中的id,需要传递给父组件
this.$emit('input', id)
}
},
created () {
this.loadChannelList()
}
}
</script>
- 再导入并使用组件
// 1、导入组件
import MyChannel from '@/components/MyChannel.vue'
export default {
// 2、配置组件
components: {
MyChannel
}
}
<!-- 3、使用组件 -->
<my-channel v-model='filterParams.channel_id'></my-channel>