vue.js三十五—— 从无到有完整的项目实战2

 

 

上一章我们完成了首页界面,今天完成新闻资讯模块

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楼&nbsp;&nbsp;&nbsp;用户:匿名用户&nbsp;&nbsp;&nbsp;发表时间: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}}楼&nbsp;&nbsp;&nbsp;用户:{{c.user_name}}&nbsp;&nbsp;&nbsp;发表时间:{{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}}楼&nbsp;&nbsp;&nbsp;用户:{{c.user_name}}&nbsp;&nbsp;&nbsp;发表时间:{{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提交的数据格式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值