[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");
~~~
運行效果示意圖:

# 能力提升
## 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