[TOC]
# 建立目录
在项目根目录下执行以下命令创建目录
~~~
mkdir -p ./src/views/portal/news
~~~
# 创建新闻列表文件
/src/views/portal/news/index.vue
~~~
<template>
<div>
<ChannelHeader>
<NavigatorPath :path="navigatorPathData"></NavigatorPath>
<template v-slot:extra>
<Input
search
enter-button
v-model="keywords"
placeholder="输入关键字..."
@on-search="doSearch"
/>
</template>
</ChannelHeader>
<Card
class="card"
v-for="(item, index) of tableData.data"
:key="index"
:to="{
path: '/portal/news/detail',
query: { id: item.id, menu: 'news' },
}"
>
<Row :gutter="20">
<Col span="6">
<div class="banner">
<img :src="item.cover" /></div
></Col>
<Col span="18">
<div class="info">
<div class="title" v-line-clamp="1">
{{ item.title }}
</div>
<div class="tag">
<Tag color="orange">
{{ item.create_at | formatDate("yyyy-MM-dd") }}</Tag
>
</div>
<div class="abstract" v-line-clamp="3">{{ item.abstract }}</div>
</div>
</Col>
</Row>
</Card>
<Row>
<Page
:page-size="tableData.pageSize"
:total="tableData.count"
:current="tableData.currentPage"
@on-change="changePage"
show-total
></Page>
</Row>
</div>
</template>
<script>
import NavigatorPath from "@/components/navigator-path";
import ChannelHeader from "@/components/channel-header";
import { news } from "@/common/api/portal";
export default {
components: {
NavigatorPath,
ChannelHeader,
},
data() {
return {
navigatorPathData: [{ title: "首页", path: "/", query: {} }, "新闻动态"],
keywords: "", //检索关键字
map: { pageSize: 3, page: 1 }, //搜索条件
tableData: {}, //搜索结果
};
},
methods: {
doSearch() {
this.map.keywords = this.keywords;
this.map.page = 1; //开启新的搜索,显示第一页内容
this.getList();
},
changePage(index) {
this.map.page = index; //改变页码
this.getList();
},
getList() {
news.getList(this.map).then((res) => {
if (res.errno == 0) {
this.tableData = res.data;
//对数据排序
this.tableData.data = this.tableData.data.sort((x, y) => {
return x.create_at < y.create_at ? 1 : -1;
});
} else {
this.$Message.error(res.errmsg);
}
});
},
async getList2() {
let res = await news.getList(this.map);
if (res.errno == 0) {
this.tableData = res.data;
} else {
this.$Message.error(res.errmsg);
}
},
},
mounted() {
this.doSearch();
},
filters: {
// 移动到main.js实现全局注册,方便其他页面使用
formatDate(date, fmt = "yyyy-MM-dd") {
date = new Date(date);
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
(date.getFullYear() + "").substr(4 - RegExp.$1.length)
);
}
let o = {
"M+": date.getMonth() + 1,
"d+": date.getDate(),
"h+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds(),
};
for (let k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
let str = o[k] + "";
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length === 1 ? str : ("00" + str).substr(str.length)
);
}
}
return fmt;
},
},
};
</script>
<style scoped lang="scss">
.card {
margin-bottom: 10px;
}
.banner {
img {
height: 150px;
width: 100%;
}
}
.info {
line-height: 1.8;
.title {
font-size: large;
}
.tag {
font-size: small;
}
.abstract {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
font-size: 16px;
}
}
</style>
~~~
# 代码解释
## 1、数组排序
为了让新闻按照时间倒序排列,在拿到数据时候实行本地排序,数组排序需要对排序中的比较函数进行重载,定义自己的实现比价大小的逻辑
~~~
//对数据排序
this.tableData.data = this.tableData.data.sort((x, y) => {
return x.create_at < y.create_at ? 1 : -1;
});
~~~
## 2、v-for指令注意事项
vuejs模板中使用v-for执行循环渲染一组数据,需要指定key属性,而且这个key需要唯一,否则会严重影响性能,一般简单的办法是使用数组下标index,使用Card组件显示内容时候,可以使用to属性绑定类似于vue-router的参数对象实现跳转链接
~~~
<Card
class="card"
v-for="(item, index) of tableData.data"
:key="index"
:to="{
path: '/portal/news/detail',
query: { id: item.id, menu: 'news' },
}"
>
</Card>
~~~
## 3、async/await语法
API调用可以使用链式语法也可以使用async/await语法
使用链式调用
~~~
getList() {
news.getList(this.map).then((res) => {
if (res.errno == 0) {
this.tableData = res.data;
//对数据排序
this.tableData.data = this.tableData.data.sort((x, y) => {
return x.create_at < y.create_at ? 1 : -1;
});
} else {
this.$Message.error(res.errmsg);
}
});
},
~~~
使用async/await语法
~~~
async getList() {
let res = await news.getList(this.map);
if (res.errno == 0) {
this.tableData = res.data;
} else {
this.$Message.error(res.errmsg);
}
},
~~~
## 4、搜索功能
执行搜索的时候一定要重置显示的页面为第一页
~~~
doSearch() {
this.map.keywords = this.keywords;
this.map.page = 1; //开启新的搜索,显示第一页内容
this.getList();
},
~~~
## 5、添加路由
添加新的页面一定要修改路由文件,将新页面的路由配置加进去,如果项目路由很多,还需要分目录组织路由,不同模块的路由分开组织,便于编辑和理解。
测试访问路径:http://127.0.0.1:8080/portal/news
~~~
{
path: "/poral/news",
name: "PoraltNews",
component: () => import("../views/portal/news/index.vue"),
},
~~~
完整的路由文件
/router/index.js
~~~
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/about",
name: "About",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue"),
},
{
path: "/poral/news",
name: "PoraltNews",
component: () => import("../views/portal/news/index.vue"),
},
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
export default router;
~~~
## 6、过滤器的定义
过滤器的第一个参数是通过管道传递过来的
~~~
<Tag color="orange">
{{ item.create_at | formatDate("yyyy-MM-dd") }}
</Tag>
~~~
如果需要全局注册一个filter,则需要在main.js中完成注册过程
~~~
//注册一个全局的过滤器,用于格式化显示日期和时间
Vue.filter("formatDate", function(date, fmt = "yyyy-MM-dd") {
date = new Date(date);
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
(date.getFullYear() + "").substr(4 - RegExp.$1.length)
);
}
let o = {
"M+": date.getMonth() + 1,
"d+": date.getDate(),
"h+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds(),
};
for (let k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
let str = o[k] + "";
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length === 1 ? str : ("00" + str).substr(str.length)
);
}
}
return fmt;
});
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");
~~~
运行效果示意图:
![](https://img.kancloud.cn/43/54/43543fd159063c15f8ec36943a6c10ef_727x785.png)
# 能力提升
## 1、 新闻列表文件用到了几个自定义的组件
~~~
NavigatorPath、ChannelHeader
~~~
這些组件都是基础组件,实现相对比较简单,后续我们分解组件的源码
自定义组件需要在使用的时候import并在components属性中注册
~~~
import NavigatorPath from "@/components/navigator-path";
import ChannelHeader from "@/components/channel-header";
import { news } from "@/common/api/portal";
export default {
components: {
NavigatorPath,
ChannelHeader,
},
...
~~~
自定义组件一般一个组件放在一个目录下,入口文件使用index.vue,即便整个组件只有一个文件也要遵循改规则,导入的时候只需要写目录的名称,不需要写index.vue,但是要注意,不能存在与目录同级的后缀名为.vue的与目录同名文件,否则会出错。
~~~
import NavigatorPath from "@/components/navigator-path";
~~~
该组件使用的其他相关文件放在该组件的目录下。
## 2、用到了ViewDesign UI库中Card、Row、Col、Input和Page组件
> ViewDesign UI库的组件通过iview-loader实现了自动加载,可以全局直接使用。
- 文档说明
- 服务端开发指南
- 客户端开发指南
- 请求拦截器
- API接口实例分析
- 页面文件
- NPM包管理
- 创建NPM包项目
- 课程设计
- 概述
- 内容管理系统项目
- 配置开发环境
- 设计静态原型
- 快速构建项目
- 构建CMS系统前端界面
- 门户模块
- 新闻列表
- API接口规范
- 生成模拟数据
- 显示新闻列表
- NavigatorPath组件
- ChannelHeader组件
- v-line-clamp指令
- formatDate过滤器
- 新闻详情页
- 修改顶部导航菜单
- 实现访问远程API
- 扩展功能
- 组件开发
- 服务端项目
- 编写服务模块
- 项目配置
- 数据库
- 创建数据库脚本
- 配置数据库
- 创建模拟数据
- 新闻模块控制器
- 添加逻辑验证层
- 实现接口
- 书栈模块
- QA