前后端分离实践AngularJS/ReactJS/VueJS组件化开发示例与对比(四)——Vue构建项目示例

Vue是一个轻量级的前端UI框架,简单、轻量、数据驱动、组件化等使Vue框架得到很多前端开发者的认可。

本实例将使用Vue框架构建一个典型企业站的架构和新闻中心模块。

 

一、项目目录结构

前后端分离实践AngularJS/ReactJS/VueJS组件化开发示例与对比

从上面的项目目录结构可以看出,使用Vue构建项目非常简单。vue.min.js是Vue的主库文件,vue-resource.min.js是Vue的官方插件,用于请求远程资源。main.js是主要程序逻辑文件,Vue的初始化、数据请求、组件定义等等都在这个文件中实现。

 

二、项目页面构建

1、新闻列表页面

新闻列表页面主要包括两部分:

一是引入库文件和主程序逻辑文件:

<script src=”lib/vue.min.js”></script>

<script src=”lib/vue-resource.min.js”></script>

<script src=”js/main.js”></script>

 

二是建立页面主布局,置入自定义的组件:

<div class="container">
    <div id="app">
        <my-header></my-header>
        <my-menu :menus="menus"></my-menu>
        <div class="main">
            <article class="newsList">
                <my-news></my-news>
            </article>
            <my-sidebar></my-sidebar>
        </div>

        <my-footer></my-footer>
    </div>
</div>

 

入口页面代码如下:

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Vue</title>
    <meta name="description" content="">
    <link rel="apple-touch-icon" href="apple-touch-icon.png">
    <link rel="stylesheet" href="css/main.css">
</head>
<body ng-controller="mainCtrl">
    <div class="container">
        <div id="app">
            <my-header></my-header>
            <my-menu :menus="menus"></my-menu>
            <div class="main">
                <article class="newsList">
                    <my-news></my-news>
                </article>
                <my-sidebar></my-sidebar>
            </div>

            <my-footer></my-footer>
        </div>
    </div>

    <script src="lib/vue.min.js"></script>
    <script src="lib/vue-resource.min.js"></script>
    <script src="js/main.js"></script>
</body>
</html>

 

2、新闻详情页面

新闻详情与上面新闻列表页面相类,页面布局一样,只是将列表组件改为新闻详情组件。

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Vue</title>
    <meta name="description" content="">
    <link rel="apple-touch-icon" href="apple-touch-icon.png">
    <link rel="stylesheet" href="css/main.css">
</head>
<body ng-controller="mainCtrl">
    <div class="container">
        <div id="app">
            <my-header></my-header>
            <my-menu :menus="menus"></my-menu>
            <div class="main">
                <article class="newsContent">
                    <my-newscontent></my-newscontent>
                </article>
                <my-sidebar></my-sidebar>
            </div>
            <my-footer></my-footer>
        </div>
    </div>

    <script src="lib/vue.min.js"></script>
    <script src="lib/vue-resource.min.js"></script>
    <script src="js/main.js"></script>
</body>
</html>

 

3、样式文件

body{background-color: #f8f8f8;margin:0;}
.container{margin:0 auto; width:1000px;}

img{display: block;}
a{text-decoration: none;}

/************************************************************/
/************************************************************/
/************************************************************/
/************************************************************/
/********************* 添加新闻资讯 *************************/
/************************************************************/
/************************************************************/
/************************************************************/
/************************************************************/

h1{width:100%; height: 90px; background-color: #dd3333; color:#fff; line-height: 90px; text-align: center; margin:0;font-family: '微软雅黑'; font-weight: normal; margin-bottom: 20px;}
.pure-control-group{display: flex; justify-content: center; align-items: center;}
.pure-form-aligned .pure-control-group label{width:4rem;}
.pure-control-group input{flex:0.96;}
.pure-control-group textarea{flex:0.96;}
.richTextEditor{flex:0.92;border:1px solid #eee;}
.pure-form button{margin: 0 auto;}

.news-content{padding:20px;}
trix-editor{min-height: 200px !important;}


/************************************************************/
/************************************************************/
/************************************************************/
/************************************************************/
/********************* 前台页面布局 *************************/
/************************************************************/
/************************************************************/
/************************************************************/
/************************************************************/

.main{display: flex;flex-direction: row; justify-content: space-between;margin-bottom: 10px;}
header{width:100%;overflow: hidden;height: 100px;display: flex; justify-content: space-between; align-items: center;}
nav{width:100%;overflow: hidden;height: 50px;background-color: #444; margin:10px 0 20px 0;}
article{flex:1;min-height: 600px;}
aside{width:300px; margin-left: 10px;}
footer{width:100%;overflow: hidden;height: 150px;background-color: #444;display: flex;justify-content: center; align-items: center;flex-direction: column;}

header .logoContainer{display: flex;align-items: center;}
header .logoContainer span{font-size: 2.4rem;line-height: 2.4rem; font-family: Arial, sans-serif;}
header .ngLogo{width:80px;margin-left: 10px;}
nav ul{margin:0;display: flex;flex-direction: row;padding:0;padding-left: 50px; padding-right: 50px;}
nav li{list-style: none;display: inline-block;flex:1;}
nav li a{display:block;height:50px; line-height:50px;color:#fff;flex:1; text-decoration: none;text-align: center;}
nav li a:hover{background-color: #dd3333;}
nav li .active{background-color: #dd3333;}

footer .footerLogoContainer{display: flex;align-items: center;}
footer .footerLogoContainer span{color:#fff; margin-left:1rem;font-size: 2.4rem;line-height: 2.4rem; font-family: Arial, sans-serif;}
footer .footerLogo{display: block;width: auto; height: 90px;}
footer p{text-align: center; color:#ddd;}

/*搜索框*/
.cf:before, .cf:after{content:""; display:table;}
.cf:after{clear:both;}
.cf{zoom:1;}    
 /* Form wrapper styling */
.search-wrapper {width: 280px;	margin-right: 10px;	box-shadow: 0 1px 1px rgba(0, 0, 0, .4) inset, 0 1px 0 rgba(255, 255, 255, .2);}
/* Form text input */
.search-wrapper input {width: 198px;height: 20px;padding: 10px 5px;	float: left;font: bold 15px 'lucida sans', 'trebuchet MS', 'Tahoma';border: 0;background: #444;	color:#fff;	border-radius: 3px 0 0 3px;}
.search-wrapper input:focus {outline: 0;background: #444;box-shadow: 0 0 2px rgba(0,0,0,.8) inset;}
.search-wrapper input::-webkit-input-placeholder {color: #eee;font-weight: normal;font-style: italic;}
.search-wrapper input:-moz-placeholder {color: #eee;font-weight: normal; font-style: italic;}
.search-wrapper input:-ms-input-placeholder {color: #eee;font-weight: normal; font-style: italic;}    
/* Form submit button */
.search-wrapper button {overflow: visible;position: relative;float: right;border: 0;padding: 0;cursor: pointer;	height: 40px;width: 72px;font: bold 15px/40px 'lucida sans', 'trebuchet MS', 'Tahoma';	color: white;text-transform: uppercase;	background: #D83C3C;border-radius: 0 3px 3px 0;text-shadow: 0 -1px 0 rgba(0, 0, 0, .3);}
.search-wrapper button:hover{background: #e54040;}   
.search-wrapper button:active,.search-wrapper button:focus{background: #c42f2f; outline: 0;}
.search-wrapper button:before {content: ''; position: absolute; border-width: 8px 8px 8px 0;border-style: solid solid solid none;border-color: transparent #d83c3c transparent;top: 12px;left: -6px;}
.search-wrapper button:hover:before{border-right-color: #e54040;}
.search-wrapper button:focus:before,.search-wrapper button:active:before{border-right-color: #c42f2f;}      
.search-wrapper button::-moz-focus-inner {border: 0; padding: 0;}    

.newsList{flex:1;}
.newsList .newsItem{display: flex;flex-direction: row;padding:10px; border-bottom:1px solid #eee;}
.newsList .newsItem img{width: 220px; height: 122px; margin-right: 16px;}
.newsList .newsItem div{flex:1; display: flex;flex-direction: column; padding-right: 10px;}
.newsList h3,.newsList h4, .newsList h5{margin:0; padding:0; font-weight: normal;}
.newsList h3{font-family: 'Microsoft YaHei'; color:#333;max-height:52px;text-overflow: -o-ellipsis-lastline;overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical;}
.newsList .newsItem h4{margin:10px 0;color:#666;font-size:14px;height:32px;text-overflow: -o-ellipsis-lastline;overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical;}
.newsList .newsItem h5{font-size: 12px;color:#666;}
.newsItem span{color:#dd3333; font-weight: 600;}
.newsList .loadmore{text-align: center;height:40px;line-height: 40px;margin-top: 10px;cursor: pointer; background-color: #eee;}
.newsList a, .newsList a:visited{color:#444;}
.newsList a:hover{color:#dd3333 !important;}

.newsContent{flex:1;padding-right: 30px;margin-bottom: 2rem;}
.newsContent h3 {font-size: 30px;}
.newsContent h4, .newsContent h5{display: inline;font-weight: normal;margin-right: 10px;}
.newsContent hr{ height:0px; border-top:1px solid #ddd; border-right:0px; border-bottom:0px; border-left:0px; }
.newsContent .content{margin-top: 2rem;margin-bottom: 2rem;text-justify : auto; text-align:justify;}
.newsContent .keyword span{margin-right:10px; background-color: #dd3333; color: #fff; padding:6px 10px; text-align: center; border-radius: 3px; line-height: 100%;}

.randomNews{width:100%;margin-top: 10px; border:1px solid #eee;}
.randomNews .newsItem{display: flex; padding:10px 0 10px 10px; border-bottom: 1px solid #eee;}
.randomNews img{width: 80px; height: 45px; border-radius: 4px; margin-right: 10px;}
.randomNews div{flex:1;flex-direction: row;padding-right: 10px;}
.randomNews h3,.randomNews h4, .randomNews h5{margin:0; padding:0; font-weight: normal;}
.randomNews h3{font-family: '微软雅黑'; background-color: #444;color:#fff;padding:6px 10px;}
.randomNews h4{font-size: 16px;}
.randomNews h5{color:#444;}
.randomNews a, .randomNews a:visited{color:#444;}

 

三、自定义组件及逻辑实现

程序逻辑文件main.js包括了所有的Vue初始化、逻辑控制和自定义组件等内容。以下进行详情分析。

程序逻辑文件主要包括以下几部分内容:

1、初始化、定义必要函数

var apiUrl = 'http://localhost:8080';
var page = 1;
var pagesize = 5;
var randomNewsCount = 5;

function GetQueryString(name){
  var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
  var r = decodeURI(window.location.search).substr(1).match(reg);
  if(r!=null)return  unescape(r[2]); return null;
}

 

2、自定义格式化日期过滤

Vue.filter('formatDate', function(dateStr){
	var dateObj = new Date(Date.parse(dateStr.replace(/-/g, "/")));
	return dateObj.getFullYear() + '/' + (dateObj.getMonth()+1) + '/' + dateObj.getDate();
});

 

3、实例化Vue

new Vue({
	el: '#app',
	data: {
		menus: [
			{link: '#', title:'首页'},
			{link: '#', title:'关于我们'},
			{link: 'index.html', title:'新闻中心'},
			{link: '#', title:'产品中心'},
			{link: '#', title:'服务范围'},
			{link: '#', title:'在线商店'},
			{link: '#', title:'联系我们'}
		]
	},
	created: function(){
		console.log('load');
	},
	methods: {
	}
})

 

4、定义组件

(1)定义头部组件

Vue使用Vue.component定义组件,template定义组件渲染效果。头部组件中,搜索栏文本框v-model绑定,搜索按钮使用v-on:click指定响应事件,同时接收文本框的参数传值。响应事件在组件定义的methods对象中定义,响应事件为重定向到新闻列表页,并在地址栏中携带搜索关键字。

Vue.component('myHeader', {
	template: `
		<header>
			<div class="logoContainer">
		        <img class="ngLogo" src="img/vue.png" />
		        <span>Vue.js</span>
	        </div>
	        <div class="search-wrapper cf">
	            <input type="text" placeholder="请输入搜索关键词..." v-model="searchText" />
	            <button v-on:click="search(searchText)" :disabled="!searchText.length">搜索</button>
	        </div>
	    </header>
	`,
	data: function(){
		return {
			searchText: ''
		}
	},
	methods: {
		search: function(keyword){
			window.location.href = encodeURI('index.html?s=' + keyword);
		}
	}
});

 

(2)底部组件

底部组件渲染静态内容,无须数据交互。

Vue.component('myFooter', {
	template: `
		<footer>
			<div class="footerLogoContainer">
		        <img class="footerLogo" src="img/vue.png" />
		        <span>Vue.js</span>
	        </div>
            <p>Vue.js 组件化开发示例</p>
        </footer>
	`
});

 

(3)菜单组件

菜单组件接收其父级组件或页面一个menus数组格式的参数(使用props指定),并通过v-for循环输出菜单项。

Vue.component('myMenu', {
	props: ['menus'],
	template: `
		<nav>
			<ul>
	            <li v-for="menu in menus"><a href="{{menu.link}}" v-bind:class="{'active': menu.title == '新闻中心'}">{{menu.title}}</a></li>
	        </ul>
	    </nav>
	`
});

 

(4)右边栏组件

右边栏组件中,在methods方法中定义获取远程数据的方法fetchData,并在ready对象中执行,表示当组件开始要加载时,就执行fetchData方法获取数据。fetchData方法中获取到数据后,将获取到的数据设置给组件内data对象。fetchData方法使用了Vue用来获取远程数据请求的$http.get方法(需要引入vue-resource.min.js)。

Vue.component('mySidebar', {
	template: `
		<aside>
		    <img src="img/banner.jpg" />
		    <section class="randomNews">
		        <h3>随机新闻</h3>
		        <div class="newsItem" v-for="randomNew in data.data">
					<a href="newscontent.html?id={{randomNew.id}}"><img v-bind:src="randomNew.featured_img" alt="{{randomNew.title}}" /></a>
                    <div>
                        <a href="newscontent.html?id={{randomNew.id}}">
                            <h4>{{randomNew.title}}</h4>
                            <h5>{{randomNew.create_time | formatDate}}</h5>
                        </a>
                    </div>
		        </div>
		    </section>
		</aside>
	`,
	data: function(){
		return {
			data: {}
		}
	},
	ready: function(){
		this.fetchData();
	},
	methods: {
		fetchData: function(){
			var vm = this;
			this.$http.get(apiUrl + '/random/' + randomNewsCount)
			.then(function(response){
				return response.json()
			}).then(function(data){
				vm.$set('data', data);
			})
		}
	}
});

 

(5)新闻列表组件

与上面边栏组件类型地,新闻列表组件在methods方法中定义获取远程数据的方法fetchData,并在ready对象中执行,表示当组件开始要加载时,就执行fetchData方法获取数据。fetchData方法中获取到数据后,将获取到的数据设置给组件内data对象。

不同的是,fetchData方法同时兼具搜索的功能,在页面加载时,先判断页面是否有搜索关键词传值,如果有,则说明此次请求是搜索,调用不同的API请求获取数据,并控制搜索信息条的显示状态。

如果是新闻列表显示,则在请求时分页请求和渲染,并动态地对比请求获取得到的数据量,来控制“加载更多”链接的显示状态。

Vue.component('myNews', {
	template: `
		<section class="newsItem" v-show="data.showSearchTip">
			<p>共为您找到<span>{{data.data.length}}</span>个符合关键词"<span>{{data.keyword}}</span>"的条目</p>
		</section>
		<section class="newsItem" v-for="newsItem in data.data">
	        <a href="newscontent.html?id={{newsItem.id}}"><img v-bind:src="newsItem.featured_img" alt="{{newsItem.title}}" /></a>
	        <div>
	        	<a href="newscontent.html?id={{newsItem.id}}">
		            <h3>{{newsItem.title}}</h3>
		            <h4>{{newsItem.description}}</h4>
		            <h5>{{newsItem.create_time | formatDate}}</h5>
	            </a>
	        </div>
	    </section>
	    <div class="loadmore" v-on:click="fetchData" v-show="data.showLoadmoreBtn">加载更多</div>
	`,
	data: function(){
		return {
			data: {
				status: false,
				data: [],
				info: '',
			}
		}
	},
	ready: function(){
		this.fetchData();
	},
	methods: {
		fetchData: function(){
			var vm = this;
			var keyword = GetQueryString('s');
			if(keyword && keyword.length){
				
				console.log(apiUrl + '/search?s=' + keyword);
				this.$http.get(apiUrl + '/search?s=' + keyword)
				.then(function(response){
					return response.json()
				}).then(function(data){
					if(data.status){
						vm.$set('data', data);
					}
					vm.$set('data.showLoadmoreBtn', false);
					vm.$set('data.showSearchTip', true);
					vm.$set('data.keyword', keyword);
				});
			}else{
				this.$http.get(apiUrl + '/news?page=' + page + '&pagesize=' + pagesize)
				.then(function(response){
					return response.json()
				}).then(function(data){
					if(data.status){
						var perCount = data.data.length;
						if(data.data.length){
							data.data = this.data.data.concat(data.data);
						}
						vm.$set('data', data);
						vm.$set('data.showLoadmoreBtn', true);
						if(perCount < pagesize){
							vm.$set('data.showLoadmoreBtn', false);
						}
					}else{
						vm.$set('data.showLoadmoreBtn', false);
					}
				});
				page = page + 1;
			}
		}
	}
});

 

(6)新闻详情组件

新闻详情组件接收来自网址传参的新闻ID值,并进行请求获取新闻详情信息。

Vue.component('myNewscontent', {
	template: `
		<section v-if="data.data">
            <h3 v-text="data.data.title"></h3>
            <h4>{{data.data.source}}</h4>   <h5>{{data.data.create_time}}</h5>
            <hr />
            <div class="content" v-html="data.data.content"></div>
            <div class="keyword">
                <span v-for="keyword in data.data.keywordArr">{{keyword}}</span>
            </div>
        </section>
	`,
	data: function(){
		return {
			data: {}
		}
	},
	ready: function(){
		this.fetchData();
	},
	methods: {
		fetchData: function(){
			var id = GetQueryString('id');			
			var vm = this;
			this.$http.get(apiUrl + '/news/' + id)
			.then(function(response){
				return response.json()
			}).then(function(data){
				if(data.status){
					data.data.keywordArr = data.data.keyword.split(',');
					vm.$set('data', data);
				}
			});
		}
	}
});

 

程序逻辑文件最后完整代码如下:

var apiUrl = 'http://localhost:8080';
var page = 1;
var pagesize = 5;
var randomNewsCount = 5;

function GetQueryString(name){
  var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
  var r = decodeURI(window.location.search).substr(1).match(reg);
  if(r!=null)return  unescape(r[2]); return null;
}

//自定义过滤
Vue.filter('formatDate', function(dateStr){
	var dateObj = new Date(Date.parse(dateStr.replace(/-/g, "/")));
	return dateObj.getFullYear() + '/' + (dateObj.getMonth()+1) + '/' + dateObj.getDate();
});

//头部组件
Vue.component('myHeader', {
	template: `
		<header>
			<div class="logoContainer">
		        <img class="ngLogo" src="img/vue.png" />
		        <span>Vue.js</span>
	        </div>
	        <div class="search-wrapper cf">
	            <input type="text" placeholder="请输入搜索关键词..." v-model="searchText" />
	            <button v-on:click="search(searchText)" :disabled="!searchText.length">搜索</button>
	        </div>
	    </header>
	`,
	data: function(){
		return {
			searchText: ''
		}
	},
	methods: {
		search: function(keyword){
			window.location.href = encodeURI('index.html?s=' + keyword);
		}
	}
});

//底部组件
Vue.component('myFooter', {
	template: `
		<footer>
			<div class="footerLogoContainer">
		        <img class="footerLogo" src="img/vue.png" />
		        <span>Vue.js</span>
	        </div>
            <p>Vue.js 组件化开发示例</p>
        </footer>
	`
});


//菜单组件
Vue.component('myMenu', {
	props: ['menus'],
	template: `
		<nav>
			<ul>
	            <li v-for="menu in menus"><a href="{{menu.link}}" v-bind:class="{'active': menu.title == '新闻中心'}">{{menu.title}}</a></li>
	        </ul>
	    </nav>
	`
});

//新闻列表组件
Vue.component('myNews', {
	template: `
		<section class="newsItem" v-show="data.showSearchTip">
			<p>共为您找到<span>{{data.data.length}}</span>个符合关键词"<span>{{data.keyword}}</span>"的条目</p>
		</section>
		<section class="newsItem" v-for="newsItem in data.data">
	        <a href="newscontent.html?id={{newsItem.id}}"><img v-bind:src="newsItem.featured_img" alt="{{newsItem.title}}" /></a>
	        <div>
	        	<a href="newscontent.html?id={{newsItem.id}}">
		            <h3>{{newsItem.title}}</h3>
		            <h4>{{newsItem.description}}</h4>
		            <h5>{{newsItem.create_time | formatDate}}</h5>
	            </a>
	        </div>
	    </section>
	    <div class="loadmore" v-on:click="fetchData" v-show="data.showLoadmoreBtn">加载更多</div>
	`,
	data: function(){
		return {
			data: {
				status: false,
				data: [],
				info: '',
			}
		}
	},
	ready: function(){
		this.fetchData();
	},
	methods: {
		fetchData: function(){
			var vm = this;
			var keyword = GetQueryString('s');
			if(keyword && keyword.length){
				
				console.log(apiUrl + '/search?s=' + keyword);
				this.$http.get(apiUrl + '/search?s=' + keyword)
				.then(function(response){
					return response.json()
				}).then(function(data){
					if(data.status){
						vm.$set('data', data);
					}
					vm.$set('data.showLoadmoreBtn', false);
					vm.$set('data.showSearchTip', true);
					vm.$set('data.keyword', keyword);
				});
			}else{
				this.$http.get(apiUrl + '/news?page=' + page + '&pagesize=' + pagesize)
				.then(function(response){
					return response.json()
				}).then(function(data){
					if(data.status){
						var perCount = data.data.length;
						if(data.data.length){
							data.data = this.data.data.concat(data.data);
						}
						vm.$set('data', data);
						vm.$set('data.showLoadmoreBtn', true);
						if(perCount < pagesize){
							vm.$set('data.showLoadmoreBtn', false);
						}
					}else{
						vm.$set('data.showLoadmoreBtn', false);
					}
				});
				page = page + 1;
			}
		}
	}
});

//新闻内容组件
Vue.component('myNewscontent', {
	template: `
		<section v-if="data.data">
            <h3 v-text="data.data.title"></h3>
            <h4>{{data.data.source}}</h4>   <h5>{{data.data.create_time}}</h5>
            <hr />
            <div class="content" v-html="data.data.content"></div>
            <div class="keyword">
                <span v-for="keyword in data.data.keywordArr">{{keyword}}</span>
            </div>
        </section>
	`,
	data: function(){
		return {
			data: {}
		}
	},
	ready: function(){
		this.fetchData();
	},
	methods: {
		fetchData: function(){
			var id = GetQueryString('id');			
			var vm = this;
			this.$http.get(apiUrl + '/news/' + id)
			.then(function(response){
				return response.json()
			}).then(function(data){
				if(data.status){
					data.data.keywordArr = data.data.keyword.split(',');
					vm.$set('data', data);
				}
			});
		}
	}
});

//右边栏组件
Vue.component('mySidebar', {
	template: `
		<aside>
		    <img src="img/banner.jpg" />
		    <section class="randomNews">
		        <h3>随机新闻</h3>
		        <div class="newsItem" v-for="randomNew in data.data">
					<a href="newscontent.html?id={{randomNew.id}}"><img v-bind:src="randomNew.featured_img" alt="{{randomNew.title}}" /></a>
                    <div>
                        <a href="newscontent.html?id={{randomNew.id}}">
                            <h4>{{randomNew.title}}</h4>
                            <h5>{{randomNew.create_time | formatDate}}</h5>
                        </a>
                    </div>
		        </div>
		    </section>
		</aside>
	`,
	data: function(){
		return {
			data: {}
		}
	},
	ready: function(){
		this.fetchData();
	},
	methods: {
		fetchData: function(){
			var vm = this;
			this.$http.get(apiUrl + '/random/' + randomNewsCount)
			.then(function(response){
				return response.json()
			}).then(function(data){
				vm.$set('data', data);
			})
		}
	}
});


new Vue({
	el: '#app',
	data: {
		menus: [
			{link: '#', title:'首页'},
			{link: '#', title:'关于我们'},
			{link: 'index.html', title:'新闻中心'},
			{link: '#', title:'产品中心'},
			{link: '#', title:'服务范围'},
			{link: '#', title:'在线商店'},
			{link: '#', title:'联系我们'}
		]
	},
	created: function(){
		console.log('load');
	},
	methods: {
	}
})

 

至此,使用Vue构建示例项目已全部完成。

 

四、运行效果

前后端分离实践AngularJS/ReactJS/VueJS组件化开发示例与对比

前后端分离实践AngularJS/ReactJS/VueJS组件化开发示例与对比

前后端分离实践AngularJS/ReactJS/VueJS组件化开发示例与对比

 

 

那时那我

jinyunblogadmin

发表评论

电子邮件地址不会被公开。 必填项已用*标注