上一章我们完成了首页界面,今天完成新闻资讯模块
1. 改造新闻资讯的路由链接
打开首页组件homeContainner.vue,找到新闻资讯的li标签,将a标签改为<router-link>标签,href改成to,跳转链接改成/home/newslist
<li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
<router-link to="/home/newslist">
<img src="../../images/menu1.png" alt="" />
<div class="mui-media-body">新闻资讯</div>
</router-link>
</li>
运行项目,点击新闻资讯,观察地址栏的变化,发现地址栏链接地址为http://localhost:3000/#/home/newslist
接下来创建新闻组件
在components目录下新建news目录,在news目录下新建NewsList.vue,如图:
然后在NewsList.vue中写测试代码
<template>
<div>
<h3>新闻列表页面</h3>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
最后在routers.js中配置路由
1. 导入新闻资讯组件
// 导入新闻组件
import newsList from './components/news/NewsList.vue';
2. 配置路由
{path:'/home/newslist',component:newsList}
2. 绘制新闻列表
我们使用MUI中的media-list.html文件中的列表模式,打开该HTML文件,复制右侧带导航箭头的代码,粘贴到NewsList.vue中
<ul class="mui-table-view mui-table-view-chevron">
<li class="mui-table-view-cell mui-media">
<a class="mui-navigate-right">
<img class="mui-media-object mui-pull-left" src="../../images/news/cbd.jpg">
<div class="mui-media-body">
<h1>CBD</h1>
<p class='mui-ellipsis'>
<span>发表时间:2019-08-24 10:10:10</span>
<span>点击:0次</span>
</p>
</div>
</a>
</li>
<li class="mui-table-view-cell mui-media">
<a class='mui-navigate-right' href="javascript:;">
<img class="mui-media-object mui-pull-left" src="../../images/news/yuantiao.jpg">
<div class="mui-media-body">
<h1>CBD</h1>
<p class='mui-ellipsis'>
<span>发表时间:2019-08-24 10:10:10</span>
<span>点击:0次</span>
</p>
</div>
</a>
</li>
<li class="mui-table-view-cell mui-media">
<a class="mui-navigate-right">
<img class="mui-media-object mui-pull-left" src="../../images/news/shuijiao.jpg">
<div class="mui-media-body">
<h1>CBD</h1>
<p class='mui-ellipsis'>
<span>发表时间:2019-08-24 10:10:10</span>
<span>点击:0次</span>
</p>
</div>
</a>
</li>
</ul>
然后,修改样式
<style lang="scss" scoped>
.mui-table-view {
li {
h1 {
font-size: 14px;
}
.mui-ellipsis {
font-size: 12px;
color: #226aff;
display: flex; // CS3语法
justify-content: space-between;
}
}
}
</style>
效果图:
3. 通过$http获取新闻列表数据
3.1 添加获取新闻列表数据的接口
3.3.1 创建新闻缩略图bean
package com.zoudm.pro.bean;
/**
* @author Administrator
* 新闻资讯缩略图信息
*/
public class BaseNews {
private int id;
private String title;
private String add_time;
private String zhayao;
private int click;
private String img_url;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAdd_time() {
return add_time;
}
public void setAdd_time(String add_time) {
this.add_time = add_time;
}
public String getZhayao() {
return zhayao;
}
public void setZhayao(String zhayao) {
this.zhayao = zhayao;
}
public int getClick() {
return click;
}
public void setClick(int click) {
this.click = click;
}
public String getImg_url() {
return img_url;
}
public void setImg_url(String img_url) {
this.img_url = img_url;
}
@Override
public String toString() {
return "BaseNews [id=" + id + ", title=" + title + ", add_time=" + add_time + ", zhayao=" + zhayao + ", click="
+ click + ", img_url=" + img_url + "]";
}
public BaseNews(int id, String title, String add_time, String zhayao, int click, String img_url) {
super();
this.id = id;
this.title = title;
this.add_time = add_time;
this.zhayao = zhayao;
this.click = click;
this.img_url = img_url;
}
public BaseNews() {
super();
// TODO Auto-generated constructor stub
}
}
3.3.2 创建获取新闻缩略图列表数据
package com.zoudm.pro.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.zoudm.pro.bean.BaseNews;
@RestController
@RequestMapping("/api")
public class NewsController {
@RequestMapping(value="/getnewslist",method=RequestMethod.GET)
public Map<String,Object> getNewsList(HttpServletResponse res){
res.setHeader("Access-Control-Allow-Origin", "*");
// 定义返回数据格式
Map<String,Object> rtnMap = new HashMap<String, Object>();
rtnMap.put("status", 0); // 状态:0 表示成功 1 表示失败
// 创建返回的数据
List<BaseNews> baseNewsList = new ArrayList<>();
baseNewsList.add(new BaseNews(1001, "追星当有底线,国耻岂是追星的“梗”!", "2019-08-24 08:24", "新华视点", 1024, "http://localhost:3000/src/images/news/cbd.jpg"));
baseNewsList.add(new BaseNews(1002, "澳大利亚一个州将停止汉语教学 中国外交部四个“不”回应", "2019-08-24 08:31", "央视网新闻", 10024, "http://localhost:3000/src/images/news/yuantiao.jpg"));
baseNewsList.add(new BaseNews(1003, "有心了!球迷编字帖帮艾克森练习写名字", "2019-08-24 08:52", "赛点", 21, "http://localhost:3000/src/images/news/shuijiao.jpg"));
rtnMap.put("message", baseNewsList);
return rtnMap;
}
}
3.2 获取后台数据
3.2.1 获取数据
<script>
export default {
data(){
return {
info:{
id:'',
title:'',
add_time:'',
zhayao:'',
click:'',
img_url:''
},
infos:[]
}
},
created(){
this.getNewsList();
},
methods:{
getNewsList(){
this.$http.get("http://localhost:8080/api/getnewslist").then(response => {
//console.log("xxx = " + JSON.stringify(response.data.message));
//console.log(response.data.status);
if( response.data.status * 1 == 0 ){
response.data.message.forEach(info => {
this.infos.push(info);
});
}
console.log("this.infos =" + JSON.stringify(this.infos));
});
}
}
}
</script>
3.2.2 渲染页面
将页面静态数据删除,保留其中一个即可,然后利用v-for,将数据渲染到页面
<ul class="mui-table-view mui-table-view-chevron">
<li class="mui-table-view-cell mui-media" v-for="info in infos" :key="info.id">
<a class="mui-navigate-right">
<img class="mui-media-object mui-pull-left" :src="info.img_url">
<div class="mui-media-body">
<h1>{{info.title}}</h1>
<p class='mui-ellipsis'>
<span>发表时间:{{info.add_time}}</span>
<span>点击:{{info.click}}次</span>
</p>
</div>
</a>
</li>
</ul>
注意:img标签的src属性,在用vue的差值表达式的时候,需要用属性绑定,即 :src
4. 设置请求根路径
我们发现,当功能越做越多的时候,页面的请求会越拉越多,这时候,如果我们在每一个$http中都写上请求全链接的话,不但冗余,而且也不便于后期维护,我们可以利用vue-resource给我们提供的设置请求根路径的方法,来设置请求路径。
vue.http.options.root="http://localhost:8080/api";
那么,这句话,放在哪里了,通过上面的描述,知道,这个配置是一个全局的配置,所以需要放在main.js中,由于这句话依赖于vue.resource,所以,我们需要将该设置放在VueResource之后,即
/**
* 导入vue-resource模块
*/
import VueResource from 'vue-resource';
Vue.use(VueResource);
// 设置根路径
vue.http.options.root="http://localhost:8080/api";
改造调用的链接
1. 轮播图链接
this.$http.get("getlunbotu")
2. 新闻资讯链接
this.$http.get("getnewslist")
注意:这里的getlunbotu与getnewslist前面都不能加“ / ”,原因是vue-resource前面是不能带斜线的。如果带了“ / ”,那么,这里就表示vue项目的根目录下了,所以不能带斜线。
5. 优化新闻列表
当我们在后台数据足够多的时候,列表会出现滚动条,我们滚动到最后,发现,最后一项只显示一半,如图:
这是为什么了?我们前面设置了APP根组件的padding-top的,所以,这里我们也需要设置一下底部的样式。
5.1 解决滚动底部显示不全的问题
我们发现底部导航高度为50px,所以我们需要给底部加一个padding-bottom:50px;的样式
.app-container {
padding-top: 40px;
padding-bottom: 50px;
overflow-x: hidden;
}
效果图:
5.2 定义时间过滤器解决时间显示问题
过滤器有两种定义方式,一种是定义全局的过滤器,一种是在组件内部定义私有的过滤器,这里因为时间用到的地方很多,所以我们定义全局过滤器,在main.js中定义,在node中,可以通过moment时间插件来格式化时间
5.2.1 安装moment
npm i moment -S;
5.2.2 在main.js中导入时间插件moment
import moment from 'moment';
注意:
1. moment如果直接调用得到的是当前时间。例如:moment()
2. moment如果带一个时间参数,就是得到给定的时间。例如:moment(dateStr)
5.2.3 定义时间过滤器
// 2. 定义全局过滤器
Vue.filter('dateFormate',function(dateStr,pattern='YYYY-MM-DD HH:mm:ss'){
return moment(dateStr).format(pattern);
});
5.2.4 使用时间过滤器
通过管道符直接使用就可以了
<span>发表时间:{{info.add_time | dateFormate}}</span>
注意:如果我们想要个性化的时间格式,同时也要使用这个过滤器,该怎么办了?我们可以给过滤器传参,例如:
<span>发表时间:{{info.add_time | dateFormate('YYYY:MM:DD')}}</span>
5.3 完成新闻详情路由
5.3.1 将列表每一项改成router-link
将a标签替换为router-link,并添加to属性,属性值为/home/newsinfo/ 加上 id,因为这个id是一个动态的值,所以to需要使用属性绑定
<router-link :to="'/home/newsinfo/' + info.id">
<img class="mui-media-object mui-pull-left" :src="info.img_url">
<div class="mui-media-body">
<h1>{{info.title}}</h1>
<p class='mui-ellipsis'>
<span>发表时间:{{info.add_time | dateFormate('YYYY:MM:DD')}}</span>
<span>点击:{{info.click}}次</span>
</p>
</div>
</router-link>
5.3.2 创建新闻详情组件页面
<template>
<div>
<h3>新闻详情</h3>
</div>
</template>
5.3.3 建立router-link与组件的映射关系
// 导入新闻详情组件
import newsinfo from './components/news/newsinfo.vue';
配置路由
{path:'/home/newsinfo/:id',component:newsinfo}
注意:这里因为id是一个变量,所以需要用“ : ”来做匹配标识,代码id为一个参数,这时候的 :id 起到了占位符的作用。
5.4 完成新闻详情
5.4.1 在页面获取参数
要根据每一个id来查询对应的详情,所以,我们在进入的页面的时候,就需要获取到参数id,这里我们使用$route来获取。
获取url中参数的两种方式:
1. 如果像上面那样通过 : 做匹配的参数,用params来接收。例如:$route.params.id 来获取id值。
2. 如果是用 ? 传参,那么通过query来接收。
注意:如果我们需要多次获取id值的话,每次都需要写$route.params,会显得比较麻烦,这时候,我们可以在data中定义id,并赋值,例如:
data(){
return {
id:this.$route.params.id // 将url传达的id值挂载到data上,便于今后调用
}
}
5.4.2 完成基本布局
<div class="newsinfo-container">
<h3 class="title">新闻标题</h3>
<p class="subttle">
<span>发表时间:</span>
<span>点击:0次</span>
</p>
<hr/>
<div class="content"></div>
</div>
5.4.3 完成基本样式
<style lang="scss" scoped>
.newsinfo-container {
padding: 0 4px; // 左右内边距
.title {
font-size: 16px;
text-align: center;
margin: 15px 0; // 上下外边距
color: red;
}
.subttle {
font-size: 13px;
color: #226aff;
display: flex; // CS3语法
justify-content: space-between; // 两端对齐
}
}
</style>
5.4.4 获取并渲染新闻详情数据
首先,在data中定义newsInfo用于存储新闻新闻详情数据
newsInfo:{}
然后在methods中定义获取新闻详情的方法
getNewsInfo(){
this.$http.get('getnew/'+this.id).then( response => {
this.newsInfo=response.data.message;
} );
}
然后在created方法中调用getNewsInfo
created(){
this.getNewsInfo();
}
最后渲染数据到页面
<div class="newsinfo-container">
<h3 class="title">{{newsInfo.title}}</h3>
<p class="subttle">
<span>发表时间:{{newsInfo.add_time | dateFormate}}</span>
<span>点击:{{newsInfo.click}}次</span>
</p>
<hr/>
<div class="content" v-html="newsInfo.content"></div>
</div>
6. 评论组件
因为评论组件用到的地方很多,所以,我们将该组件定义成公共组件。
6.1 创建评论组件文件
在components目录下新建一个子组件目录,在子组件目录下新建评论组件
定义评论组件模板
<template>
<div>
<h3 >评论子组件 </h3>
</div>
</template>
<script>
export default {
data(){
return {
}
},
created(){
},
methods:{
}
}
</script>
<style lang="scss" scoped >
</style>
现在遇到个问题,我们怎么将评论组件引入到新闻详情组件中去了?
6.2 引入评论子组件到新闻详情页面
步骤:
1. 在需要使用comment组件的页面中,手动导入comment组件
2. 在父组件中,使用‘components’属性,将导入的comment组件,注册为自己的子组件。
3. 将注册子组件的时候的,注册名称,以标签的形式,在界面中引入即可。
6.2.1 导入子组件
import comment from '../subComponents/comment.vue';
6.2.2 注册子组件
components:{ // 用来注册子组件的节点
"comment-box":comment // 给组件comment取一个别名 comment-box
}
6.2.3 在界面引入子组件
<comment-box></comment-box>
完整的newsInfo.vue代码
<template>
<div class="newsinfo-container">
<h3 class="title">{{newsInfo.title}}</h3>
<p class="subttle">
<span>发表时间:{{newsInfo.add_time | dateFormate}}</span>
<span>点击:{{newsInfo.click}}次</span>
</p>
<hr/>
<div class="content" v-html="newsInfo.content"></div>
<comment-box></comment-box>
</div>
</template>
<script>
// 导入评论子组件
import comment from '../subComponents/comment.vue';
export default {
data(){
return {
id:this.$route.params.id,
newsInfo:{}
}
},
created(){
this.getNewsInfo();
},
methods:{
getNewsInfo(){
this.$http.get('getnew/'+this.id).then( response => {
this.newsInfo=response.data.message;
} );
}
},
components:{ // 用来注册子组件的节点
"comment-box":comment // 给组件comment取一个别名 comment-box
}
}
</script>
<style lang="scss">
.newsinfo-container {
padding: 0 4px; // 左右内边距
.title {
font-size: 16px;
text-align: center;
margin: 15px 0; // 上下外边距
color: red;
}
.subttle {
font-size: 13px;
color: #226aff;
display: flex; // CS3语法
justify-content: space-between; // 两端对齐
}
.content {
img {
width:100%;
}
}
}
</style>
6.3 完善评论子组件
6.3.1 评论模板
<template>
<div class="cmt-container">
<h3 >发表评论 </h3>
<hr/>
<textarea placeholder="请输入您的意见(最多120字)。。。" maxlength="120"></textarea>
<mt-button type="primary" size="large">发表评论</mt-button>
<div class="cmt-list">
<div class="cmt-item">
<div class="cmt-title">
第1楼 用户:匿名用户 发表时间:2018-05-23 12:30:30
</div>
<div class="cmt-body">
预计,之后“白鹿”将移入台湾海峡,25日凌晨到早上在福建漳浦到广东澄海一带沿海登陆
</div>
</div>
</div>
<mt-button type="danger" size="large" plain>加载更多。。。</mt-button>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped >
</style>
6.3.2 调整样式
<style lang="scss" scoped >
.cmt-container {
h3 {
font-size: 18px;
}
textarea {
font-size: 14px;
height: 85px;
margin: 0px;
}
.cmt-list {
margin: 5px 0;
.cmt-item {
font-size: 13px;
.cmt-title {
background-color: #ccc;
line-height: 30px;
}
.cmt-body{
line-height: 30px;
text-indent: 2em; // 缩进
}
}
}
}
</style>
6.3.3 加载首页评论内容
1. 定义加载评论数据的函数getComments
getComments(pageindex){
this.$http.get('getcomments/' + this.id +'?pageindex='+pageindex).then(response => {
this.comments = [];
this.comments = response.data.message;
});
}
2. 调用getComments函数
created(){
this.getComments(1);
}
3. 渲染页面
<div class="cmt-list">
<div class="cmt-item" v-for="(c,index) in comments" :key="c.id">
<div class="cmt-title">
第{{index+1}}楼 用户:{{c.user_name}} 发表时间:{{c.add_time | dateFormate}}
</div>
<div class="cmt-body">
{{(c.content === 'undefined' || c.content === '')?'此用户很懒,什么都没有留下。。。':c.content}}
</div>
</div>
</div>
注意:
1. 这个功能涉及到了子父组件传值
传达普通值:在父组件中直接用属性绑定,在子组件中用props来接收
例如:
// 父组件通过属性绑定给子组件传id
<comment-box :id="this.id"></comment-box>
// 子组件通过props属性数组接收
props:[
"id"
],
2. 在加载数据的时候,我们用了concat函数来拼接数组,当后台返回的数组是空数组的时候,concat拼接一个空的数组结果还是原来的数组,所以这里可以直接这样处理。
6.4 完成发表评论功能
思路:
1. 把文本框做成数据的双向绑定
2. 为发表按钮绑定一个事件
3. 校验评论内容
4. 通过vue-resource发送请求,将评论数据提交给服务器保存。
5. 发表之后,刷新列表,以查看最新评论。
备注:这里,后台就不做处理了,只做前台处理的那部分
完整代码
<template>
<div class="cmt-container">
<h3 >发表评论 </h3>
<hr/>
<textarea placeholder="请输入您的意见(最多120字)。。。" maxlength="120" v-model="commentsContent"></textarea>
<mt-button type="primary" size="large" @click="commitComment">发表评论</mt-button>
<div class="cmt-list">
<div class="cmt-item" v-for="(c,index) in comments" :key="c.id">
<div class="cmt-title">
第{{index+1}}楼 用户:{{c.user_name}} 发表时间:{{c.add_time | dateFormate}}
</div>
<div class="cmt-body">
{{(c.content === 'undefined' || c.content === '')?'此用户很懒,什么都没有留下。。。':c.content}}
</div>
</div>
</div>
<mt-button type="danger" size="large" plain @click="clickCount = clickCount+1,getMore(clickCount+1)">加载更多。。。</mt-button>
</div>
</template>
<script>
import { Toast } from "mint-ui";
export default {
data(){
return {
maxId:0,
clickCount:1,
comments:[],
commentsContent:'' // 评论内容
}
},
props:[
"id"
],
created(){
this.getComments(1);
},
methods:{
getComments(pageindex){
this.$http.get('getcomments/' + this.id +'?pageindex='+pageindex).then(response => {
this.comments = this.comments.concat(response.data.message);
});
},
getMore(index){
this.getComments(index);
},
trim(str) { // 去掉字符串首尾空格
if(str == null || typeof str == "undefined"){
return "";
}
return str.replace(/(^\s*)|(\s*$)/g, "");
},
isEmpty(str){ // 判断字符串是否为空
if(str == null || typeof str == "undefined" ||
str == ""){
return true;
}
return false;
},
findMaxId(){
this.comments.filter(item => {
if(item.id>this.maxId){
this.maxId = item.id;
}
})
return this.maxId;
},
commitComment(){
/**
* 先判断commentsContent是否为空
*/
if(this.isEmpty(this.trim(this.commentsContent))){
Toast("请奉上您的高见在提交!");
return;
}
var c = {};
c.artid = this.id;
c.id = this.findMaxId() + 1;
c.user_name = "李四";
var d = new Date();
c.add_time=d.getFullYear() + "-" +(d.getMonth()<10?('0'+d.getMonth()):d.getMonth()) + "-" + d.getDate();
console.log(c.add_time);
c.content = this.commentsContent + "_" + this.maxId;
this.comments.unshift(c);
this.commentsContent = "";
/* 发表评论后台提交 这里后台代码没有实现
发表评论:
参数1:url
参数2:数据对象
参数3:定义提交的时候,表单中数据的格式:emulateJson = true
在main.js中设置全局的post提交数据格式:Vue.http.options.emulateJson = true;
*/
// this.$http.post('api/postComment'+$route.params.id,c)
// 这里参数只需要2个就可以了,因为我们已经设置了全局的数据格式
}
}
}
</script>
<style lang="scss" scoped >
.cmt-container {
h3 {
font-size: 18px;
}
textarea {
font-size: 14px;
height: 85px;
margin: 0px;
}
.cmt-list {
margin: 5px 0;
.cmt-item {
font-size: 13px;
.cmt-title {
background-color: #ccc;
line-height: 30px;
}
.cmt-body{
line-height: 30px;
text-indent: 2em; // 缩进
}
}
}
}
</style>
注意:这里有两个知识点,一个是数组unshift的用法,一个是post提交的数据格式