1.1 项目说明
1、技术架构
vue.js, 模块化,工程化, 移动端
2、目录部署
Css:所有样式文件
Data:所有异步接口
Img:所有图片文件
Js:所有js文件(2.0)
index.html
3、结构说明
1. 一个页面看成是一个组件,所以要创建三个组件
2. 页面中只能同时显示一个组件,那么如果只有一个容器元素,就可以实现渲染一个了
3. Vue中定义了一个叫component自定义元素,跟transition一样,有特殊的功能,用来渲染组件
component是一个万能的组件容器的元素
Is属性的属性值是谁,就渲染谁
为了让属性值是动态变量,我们可以使用v-bind指令
4. 使用component指定当前显示的组件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, width=device-width"> <title>爱创课堂团购网</title> </head> <body> <div id="app"> <!-- 定义组件容器:定义一个万能的组件元素 view是谁就会渲染谁--> <component v-bind:is="view"></component> </div> <script src="vue.js"></script> <script src="index.js"></script> </body> </html>
// 第一步:使用Vue.extend方法,定义三个组件、每个组件实质就是一个页面 var Home = Vue.extend({ // home组件:模拟显示home页面 template:'<h1>Home</h1>' }); var List = Vue.extend({ // list组件:模拟显示list页面 template:'<h1>List</h1>' }); var Detail = Vue.extend({ // detail组件:模拟显示detail页面 template:'<h1>Detail</h1>' }); // 第二步:将组件注册到页面中 Vue.component Vue.component('Home',Home); Vue.component('list',List); Vue.component('detail',Detail); // 创建vue实例化对象 var app = new Vue({ el:'#app', data:{ view:'home' // 定义默认渲染视图的名称 } }); // route路由执行的函数,来控制页面显示的组件(home、list、detail) var route = function () { var hash = location.hash; // 获取到url后面传入的参数 hash = hash.replace(/#/?/,''); // url中 '#' 和 '#/' 是无意义的,因此要过滤掉 hash = hash.split('/'); var map = { // 定义了组件的名称才能够渲染 home:true, list:true, detail:true }; if (map[hash[0]]){ // hash[0]在map表中存在才能渲染 hash[0]= home、list、detail app.view = hash[0] // 将页面设置成url传入的那个组件 }else { app.view = 'home' // 否则进入默认路由(home这个组件) } app.query = hash.slice(1); // 从第二个成员开始表示路由参数了 // http://localhost:63342/vuejsPro/bbb/demo/index.html?_ijt=p7pgchp7pvfueq4e4tem6c0n0r#home/type/2 // app.query = ["type", "2"] }; // 实现路由:hashchange 当页面hash改变就会触发此事件 window.addEventListener('hashchange',route); // 每次url给不就会触发 route(); // 当页面加载文成也要执行route路由函数 // window.addEventListener('load',route);
1.2 定义路由
1、路由原理
1. 我们定义三套组件:home、list、detail,每个组件实际对应了一个页面
2. 并且每个组件对应一个自定义路由,前端的路由是根据hash实现的,所以我们要监听hash的改变
3. Hash的改变会触发hashchange事件,所以我们订阅该事件,然后解析路由,切换组件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, width=device-width"> <link rel="stylesheet" type="text/css" href="css/index.css"> <title>爱创课堂团购网</title> </head> <body> <div id="app"> <!-- 定义组件容器:定义一个万能的组件元素 view是谁就会渲染谁--> <component v-bind:is="view"></component> </div> <script src="js/vue.js"></script> <script src="js/index.js"></script> </body> </html>
// 第一步:使用Vue.extend方法,定义三个组件、每个组件实质就是一个页面 var Home = Vue.extend({ // home组件:模拟显示home页面 template:'<h1>Home</h1>' }); var List = Vue.extend({ // list组件:模拟显示list页面 template:'<h1>List</h1>' }); var Detail = Vue.extend({ // detail组件:模拟显示detail页面 template:'<h1>Detail</h1>' }); // 第二步:将组件注册到页面中 Vue.component Vue.component('Home',Home); Vue.component('list',List); Vue.component('detail',Detail); // 创建vue实例化对象 var app = new Vue({ el:'#app', data:{ view:'home' // 定义默认渲染视图的名称 } }); // route路由执行的函数,来控制页面显示的组件(home、list、detail) var route = function () { var hash = location.hash; // 获取到url后面传入的参数 hash = hash.replace(/#/?/,''); // url中 '#' 和 '#/' 是无意义的,因此要过滤掉 hash = hash.split('/'); var map = { // 定义了组件的名称才能够渲染 home:true, list:true, detail:true }; if (map[hash[0]]){ // hash[0]在map表中存在才能渲染 hash[0]= home、list、detail app.view = hash[0] // 将页面设置成url传入的那个组件 }else { app.view = 'home' // 否则进入默认路由(home这个组件) } app.query = hash.slice(1); // 从第二个成员开始表示路由参数了 // http://localhost:63342/vuejsPro/bbb/demo/index.html?_ijt=p7pgchp7pvfueq4e4tem6c0n0r#home/type/2 // app.query = ["type", "2"] }; // 实现路由:hashchange 当页面hash改变就会触发此事件 window.addEventListener('hashchange',route); // 每次url给不就会触发 route(); // 当页面加载文成也要执行route路由函数 // window.addEventListener('load',route);
1.3 异步请求
1、运行node后端服务
C:Users om>cd C:Users omPycharmProjectsvuejsProbbdemo # 进入app.js文件夹下
C:Users omPycharmProjectsvuejsProbbdemo> node app.js # 运行
server running at port 3001
/** * 服务器 */ var MINE_TYPES = { 'html': 'text/html', 'xml': 'text/xml', 'txt': 'text/plain', 'css': 'text/css', 'js': 'text/javascript', 'json': 'application/json', 'pdf': 'application/pdf', 'swf': 'application/x-shockwave-flash', 'svg': 'image/svg+xml', 'tiff': 'image/tiff', 'png': 'image/png', 'gif': 'image/gif', 'ico': 'image/x-icon', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'wav': 'audio/x-wav', 'wma': 'audio/x-ms-wma', 'wmv': 'video/x-ms-wmv', 'woff2': 'application/font-woff2' }; var PORT = 3001; var http = require('http'); var url = require('url'); var fs = require('fs'); var path = require('path'); var root = process.cwd(); var server = http.createServer(function(request, response) { var pathname = decodeURIComponent(url.parse(request.url).pathname); var realPath = path.join(root, pathname); var ext = path.extname(realPath); if (!ext) { realPath = path.join(realPath, '/home.html'); ext = '.html' } fs.exists(realPath, function(exists) { if (exists) { fs.readFile(realPath, 'binary', function(err, file) { if (!err) { response.writeHead(200, { 'Content-Type': MINE_TYPES[ext.slice(1)] || 'text/plain' }); response.write(file, 'binary'); response.end(); } else { response.writeHead(500, { 'Content-Type': 'text/plain' }); response.write('ERROR, the reason of error is ' + err.code + '; Error number is ' + err.errno + '.'); response.end(); } }) } else { response.writeHead(404, { 'Content-Type': 'text/plain' }); response.write('This request URL ' + pathname + ' was not found on this server.'); response.end(); } }); }); server.listen(PORT); console.log("server running at port " + PORT);
{ "errno": 0, "name":"zhangsan", "age":100 }
<!DOCTYPE html> <html lang="en"> <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>Title</title> </head> <body> <h1>home</h1> <script src="vue.js"></script> <script> // 实现异步请求 var Util = { ajax:function (url, fn) { // url:请求地址;fn:请求回调函数 var xhr = new XMLHttpRequest(); // 初始化xhr xhr.onreadystatechange = function () { // 监听事件 if (xhr.readyState === 4){ // 判断状态 if (xhr.status === 200 ){ // 判断状态码 fn && fn(JSON.parse( xhr.responseText) ) // 执行函数fn: xhr.responseText 是从服务端获取到的数据 } } }; xhr.open('GET', url, true); // 打开链接 xhr.send(null) // null代替发送数据内容 } }; // 测试 Util.ajax('home.json',function (res) { console.log(res,3333333333333333333) }); </script> </body> </html>
1.4 头部样式
说明:公用的部分,不要写在组件中,尽量写在父组件(vue实例化对象中),这样组件可以复用。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, width=device-width"> <link rel="stylesheet" type="text/css" href="css/index.css"> <title>爱创课堂团购网</title> </head> <body> <!-- 定义容器元素 --> <div id="app"> <header class="header"> <div class="title"> <div class="go-back" v-on:click="goBack"><span class="arrow"><span class="arrow green"></span></span></div> <div class="login">登录</div> <h1>爱创课堂团购网</h1> </div> <div class="search" v-show="showSearch"> <input type="text" placeholder="请输入搜索关键字" v-on:keyup.enter="gotoSearch"> </div> </header> </div> <script type="text/javascript" src="js/vue2.js"></script> <script type="text/javascript" src="js/index.js"></script> </body> </html>
// 实例化vue
var app = new Vue({
el: '#app', // 绑定数据
data: {
view: 'home', // 存储路由参数
query: [],
search: '', // 是否显示搜索框
showSearch: true
},
methods: { // 定义方法
gotoSearch: function (e) { // 点击enter进行搜索
this.search = e.target.value // 第一种方式,通过组件间通信
},
goBack: function () { // 返回逻辑
history.go(-1)
}
}
});
1.5 分类按钮: home页面中的不变数据
1、说明
1. 分类按钮的数据是不变的,因此不变的数据,通常直接写在js中,变的通过异步请求获取
2. 直接写在页面中的数据,通常称之为同步的数据,通过异步请求获取的数据,通常称之为异步数据
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, width=device-width"> <link rel="stylesheet" type="text/css" href="css/index.css"> <title>爱创课堂团购网</title> </head> <body> <!-- 定义容器元素 --> <div id="app"> <component v-bind:searchquery="search" v-bind:is="view"></component> </div> <!-- 定义首页模板 --> <script type="text/template" id="tpl_home"> <!-- 定义首页组件容器 --> <section class="home"> <ul class="types clearfix"> <li v-for="item in types"> <a v-bind:href="'#list/type/' + item.id"> <img v-bind:src="'img/icon/' + item.url" alt=""> <p>{{item.title}}</p> </a> </li> </ul> </section> </script> <script type="text/javascript" src="js/vue2.js"></script> <script type="text/javascript" src="js/index.js"></script> </body> </html>
// 拓展工具方法 var Util = { /*** * 获取模板的方法 * @id 模板标签元素id * return 模板内容 **/ tpl: function (id) { return document.getElementById(id).innerHTML; }, /** * 我们要实现异步请求的方法 * @url 请求地址 * @fn 执行的方法 ***/ ajax: function (url, fn) { // 创建xhr对象 var xhr = new XMLHttpRequest(); // 订阅事件 xhr.onreadystatechange = function () { // 监听状态 if (xhr.readyState === 4) { // 判断状态 if (xhr.status === 200) { // 执行回调函数 // console.log(xhr) fn(JSON.parse(xhr.responseText)) } } }; xhr.open('GET', url, true); // 打开链接 xhr.send(null) // 发送数据 } }; // 定义三个组件 var Home = Vue.extend({ template: Util.tpl('tpl_home'), data: function () { // 定义数据 return { // 返回值才是真正的数据 types: [ {id: 1, title: '美食', url: '01.png'}, {id: 2, title: '电影', url: '02.png'}, {id: 3, title: '酒店', url: '03.png'}, {id: 4, title: '休闲娱乐', url: '04.png'}, {id: 5, title: '外卖', url: '05.png'}, {id: 6, title: 'KTV', url: '06.png'}, {id: 7, title: '周边游', url: '07.png'}, {id: 8, title: '丽人', url: '08.png'}, {id: 9, title: '小吃快餐', url: '09.png'}, {id: 10, title: '火车票', url: '10.png'} ], } }, }); // 注册三个组件 Vue.component('home', Home); // 实例化vue var app = new Vue({ el: '#app', // 绑定数据 data: { view: 'home', // 存储路由参数 search: '', // 是否显示搜索框 }, }); // 定义路由 function router () { var hash = location.hash; hash = hash.replace(/^#/?/, ''); hash = hash.split('/'); // 定义规则 var map = { home: true, }; if (map[hash[0]]) { app.view = hash[0] } else { app.view = 'home' } app.query = hash.slice(1); } window.addEventListener('hashchange', router); window.addEventListener('load', router);
1.6 广告模块:home页面中通过异步请求获取数据
1、说明
1. 这里的数据是可变的,所以要写在异步请求中,
2. 什么时候请求数据?看组件什么时候创建完成。所以要看组件生命周期,组件的生命周期
3. Vue将组件看成是一个有生命的个体,跟人一样,定义了各个阶段,组件的生命周期:组件的创建过程
4. 组件生命周期钩子函数:当组件处在某个阶段,要执行某个方法,来通知我们,组件进入某个阶段,这个方法就是组件生命周期的钩子函数
5. 这些方法在组件中直接定义,会按照顺序执行,没有参数,作用域都是组件实例化对象
6. 注:为了让数据属性具有特性,我们一定要将该数据在绑定的数据(data)中定义出来
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta charset="UTF-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, width=device-width"> <link rel="stylesheet" type="text/css" href="css/index.css"> <title>爱创课堂团购网</title> </head> <body> <!-- 定义容器元素 --> <div id="app"> <component v-bind:searchquery="search" v-bind:is="view"></component> </div> <!-- 定义首页模板 --> <script type="text/template" id="tpl_home"> <!-- 定义首页组件容器 --> <section class="home"> <!-- 定义ad广告 --> <ul class="ad clearfix"> <li v-for="(item, index) in ad"> <a v-bind:href="'#/detail/' + item.id"> <h3>{{item.title}}</h3> <p>{{item.description}}</p> <img v-bind:src="'img/ad/' + item.url" alt=""> </a> </li> </ul> </section> </script> <script type="text/javascript" src="js/vue2.js"></script> <script type="text/javascript" src="js/index.js"></script> </body> </html>
// 拓展工具方法 var Util = { /*** * 获取模板的方法 * @id 模板标签元素id * return 模板内容 **/ tpl: function (id) { return document.getElementById(id).innerHTML; }, /** * 我们要实现异步请求的方法 * @url 请求地址 * @fn 执行的方法 ***/ ajax: function (url, fn) { // 创建xhr对象 var xhr = new XMLHttpRequest(); // 订阅事件 xhr.onreadystatechange = function () { // 监听状态 if (xhr.readyState === 4) { // 判断状态 if (xhr.status === 200) { // 执行回调函数 // console.log(xhr) fn(JSON.parse(xhr.responseText)) } } }; xhr.open('GET', url, true); // 打开链接 xhr.send(null) // 发送数据 } }; // 定义三个组件 var Home = Vue.extend({ template: Util.tpl('tpl_home'), data: function () { // 定义数据 return { // 返回值才是真正的数据 ad: [], // 将数据定义出来,否则没有特性 } }, created: function () { this.$parent.showSearch = true; var me = this; Util.ajax('data/home.json', function (res) { // 请求数据 if (res && res.errno === 0) { me.ad = res.data.ad; // 存储数据 me.list = res.data.list; // 2.0版本不建议使用$set更新数据 // me.$set('ad', res.data.ad) } }) } }); // 注册三个组件 Vue.component('home', Home); // 实例化vue var app = new Vue({ el: '#app', // 绑定数据 data: { view: 'home', // 存储路由参数 search: '', // 是否显示搜索框 }, }); // 定义路由 function router () { var hash = location.hash; hash = hash.replace(/^#/?/, ''); hash = hash.split('/'); // 定义规则 var map = { home: true, }; if (map[hash[0]]) { app.view = hash[0] } else { app.view = 'home' } app.query = hash.slice(1); } window.addEventListener('hashchange', router); window.addEventListener('load', router);
1.7 效果图
1.8 项目模块化
1、项目模块化目录结构
Lib: 库文件
Util:工具方法
Filter:定义过滤器
Router: 定义路由
Vm:存储所有组件
Home:home组件以及样式
List:list组件以及样式
Detail:detail组件以及样式
App.js:vue实例化对象
App.css: app样式
Bootstrap.js 启动的文件
Reset.js 默认样式
2、个文件代码展示
1. index.html和app.js
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, width=device-width"> <!--<link rel="stylesheet" type="text/css" href="css/index.css">--> <title>爱创课堂团购网</title> </head> <body> <!-- 定义容器元素 --> <div id="app"> <header class="header"> <div class="title"> <div class="go-back" v-on:click="goBack"><span class="arrow"><span class="arrow green"></span></span></div> <div class="login">登录</div> <h1>爱创课堂团购网</h1> </div> <div class="search" v-show="showSearch"> <input type="text" placeholder="请输入搜索关键字" v-on:keyup.enter="gotoSearch"> </div> </header> <!-- 定义组件容器元素 --> <!-- <home></home> <list></list> <detail></detail> --> <!-- 同时只能渲染一个组件 --> <component v-bind:searchquery="search" v-bind:is="view"></component> </div> <!-- 定义首页模板 --> <script type="text/template" id="tpl_home"> <!-- 定义首页组件容器 --> <section class="home"> <ul class="types clearfix"> <li v-for="item in types"> <a v-bind:href="'#list/type/' + item.id"> <img v-bind:src="'img/icon/' + item.url" alt=""> <p>{{item.title}}</p> </a> </li> </ul> <!-- 定义ad广告 --> <ul class="ad clearfix"> <li v-for="(item, index) in ad"> <a v-bind:href="'#/detail/' + item.id"> <h3>{{item.title}}</h3> <p>{{item.description}}</p> <img v-bind:src="'img/ad/' + item.url" alt=""> </a> </li> </ul> <!-- 定义商品列表 --> <ul class="list-container"> <li v-for="item in list"> <a v-bind:href="'#detail/' + item.id"> <img v-bind:src="'img/list/' + item.img" alt=""> <div class="content"> <h3>{{item.title}}</h3> <p> <span class="price">{{item.price | price}}</span> <span class="origin-price">{{item.orignPrice | orignPrice}}</span> <span class="sales">{{item.sales | sales}}</span> </p> </div> </a> </li> </ul> </section> </script> <!-- 定义list模板 --> <script type="text/template" id="tpl_list"> <section class="list"> <ul class="types clearfix"> <li v-for="ickt in types" v-on:click="sortBy(ickt.key)"> <span>{{ickt.value}}</span> <span class="arrow"></span> </li> </ul> <ul class="list-container"> <!-- <li v-for="item in list | filterBy query"> --> <!-- <li v-for="item in list | filterBy searchquery"> --> <li v-for="item in dealList"> <a v-bind:href="'#detail/' + item.id"> <img v-bind:src="'img/list/' + item.img" alt=""> <div class="content"> <h3>{{item.title}}</h3> <p> <span class="price">{{item.price | price}}</span> <span class="origin-price">{{item.orignPrice | orignPrice}}</span> <span class="sales">{{item.sales | sales}}</span> </p> </div> </a> </li> </ul> <div class="load-more" v-show="others.length" v-on:click="loadMore"> <span>查看剩余{{others.length}}条团购</span> <span class="arrow"> <span class="arrow white"></span> </span> </div> </section> </script> <!-- 定义详情页模板 --> <script type="text/template" id="tpl_detail"> <section class="product"> <div class="image-part"> <img v-if="data.src" v-bind:src="'img/item/' + data.src" alt=""> <h1>{{data.title}}</h1> <p>{{data.description}}</p> </div> <div class="price-part"> <span class="price">{{data.price}}</span> <span class="sign">元</span> <span class="origin">{{data.orignPrice | orignPrice}}</span> <span class="buy">立即购买</span> </div> <ul class="sale-part clearfix"> <li>支持自动退货</li> <li>支持随时退货</li> <li>{{data.sales | sales}}</li> </ul> <div class="store-part part"> <div class="header">店家信息</div> <div class="body"> <p>{{data.storeName}}</p> <p>{{data.storeAddress}}</p> </div> <div class="footer">查看{{data.storeNum}}家分店</div> </div> <div class="buy-part part"> <div class="header">购买须知</div> <div class="body"> <ul> <li> <h3>有效期</h3> <p>{{data.validateTime}}</p> </li> <li> <h3>使用时间</h3> <p>{{data.useTime}}</p> </li> <li> <h3>使用规则</h3> <p v-for="item in data.rules">{{item}}</p> </li> </ul> </div> </div> </section> </script> <!-- 引入脚本文件 --> <!--<script type="text/javascript" src="static/lib/vue2.js"></script>--> <script type="text/javascript" src="static/lib/sea.js"></script> <script type="text/javascript" src="static/lib/seajs-css.js"></script> <script type="text/javascript" src="static/lib/seajs-preload.js"></script> <script> // 定义配置引入入口文件 seajs.config({ // 配置根目录 base:'/static', // 预加载 preload:['lib/vue'] }); // 引入入口文件 seajs.use('bootstrap') </script> </body> </html>
/** * 服务器 */ var MINE_TYPES = { 'html': 'text/html', 'xml': 'text/xml', 'txt': 'text/plain', 'css': 'text/css', 'js': 'text/javascript', 'json': 'application/json', 'pdf': 'application/pdf', 'swf': 'application/x-shockwave-flash', 'svg': 'image/svg+xml', 'tiff': 'image/tiff', 'png': 'image/png', 'gif': 'image/gif', 'ico': 'image/x-icon', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'wav': 'audio/x-wav', 'wma': 'audio/x-ms-wma', 'wmv': 'video/x-ms-wmv', 'woff2': 'application/font-woff2' }; var PORT = 3001; var http = require('http'); var url = require('url'); var fs = require('fs'); var path = require('path'); var root = process.cwd(); var server = http.createServer(function(request, response) { var pathname = decodeURIComponent(url.parse(request.url).pathname); var realPath = path.join(root, pathname); var ext = path.extname(realPath); if (!ext) { realPath = path.join(realPath, '/index.html'); ext = '.html' } fs.exists(realPath, function(exists) { if (exists) { fs.readFile(realPath, 'binary', function(err, file) { if (!err) { response.writeHead(200, { 'Content-Type': MINE_TYPES[ext.slice(1)] || 'text/plain' }); response.write(file, 'binary'); response.end(); } else { response.writeHead(500, { 'Content-Type': 'text/plain' }); response.write('ERROR, the reason of error is ' + err.code + '; Error number is ' + err.errno + '.'); response.end(); } }) } else { response.writeHead(404, { 'Content-Type': 'text/plain' }); response.write('This request URL ' + pathname + ' was not found on this server.'); response.end(); } }); }); server.listen(PORT); console.log("server running at port " + PORT);
2. static文件夹下文件
define(function (require, exports, module) { // 定义过滤器 Vue.filter('price', function (value) { return value + '元'; }) // 定义门市价过滤器 Vue.filter('orignPrice', function (value) { return '门市价:' + value + '元' }) // 定义销量过滤器 Vue.filter('sales', function (value) { return '销量' + value }) // 过滤器无需暴露接口,可以直接使用 });
define(function (require, exports, module) { // 引入vue实例化对象 var app = require('vm/app'); // 定义路由 function router () { // 解析hash就要获取hash var hash = location.hash; // 删除# // hash = hash.slice(1); // 删除起始的/ // hash = hash.replace(/^//, '') hash = hash.replace(/^#/?/, ''); // 对/进行切割,保留第一部分,就是组件名称,后面的成员就是参数 hash = hash.split('/'); // 定义规则 var map = { home: true, list: true, detail: true }; // 只有在map中存在的组件,才能渲染 if (map[hash[0]]) { // 切换组件 app.view = hash[0] } else { // 进入默认路由 app.view = 'home' } // 我们还可以将参数存储 app.query = hash.slice(1); } // 监听路由改变 window.addEventListener('hashchange', router); // 页面加载没有触发hashchange事件,我们可以手动触发hashchange事件,或者监听load事件 window.addEventListener('load', router) // 暴露接口 module.exports = router; });
define(function (require, exports, module) { // 拓展工具方法 var Util = { /*** * 获取模板的方法 * @id 模板标签元素id * return 模板内容 **/ tpl: function (id) { return document.getElementById(id).innerHTML; }, /** * 我们要实现异步请求的方法 * @url 请求地址 * @fn 执行的方法 ***/ ajax: function (url, fn) { // 创建xhr对象 var xhr = new XMLHttpRequest(); // 订阅事件 xhr.onreadystatechange = function () { // 监听状态 if (xhr.readyState === 4) { // 判断状态 if (xhr.status === 200) { // 执行回调函数 // console.log(xhr) fn(JSON.parse(xhr.responseText)) } } } // 打开链接 xhr.open('GET', url, true); // 发送数据 xhr.send(null) } } // 暴露接口 module.exports = Util; });
define(function (require, exports, module) { // 引入组件 var home = require('vm/home/home'); var list = require('vm/list/list'); var detail = require('vm/detail/detail'); // 引入样式 require('vm/app.css'); // 实例化vue var app = new Vue({ el: '#app', // 绑定数据 data: { view: 'home', // 存储路由参数 query: [], search: '', // 是否显示搜索框 showSearch: true }, // 定义方法 methods: { // 点击enter进行搜索 gotoSearch: function (e) { // 第一种方式,通过组件间通信 this.search = e.target.value // 第二种方式存储在路由中 // location.hash = '#/list/search/' + e.target.value // console.log(e.target.value) }, // 返回逻辑 goBack: function () { history.go(-1) } } }); // 暴露接口 module.exports = app; });
define(function (require, exports, module) { // 引入样式 require('./detail.css'); // 使用过滤器 require('filter/filter'); // 使用工具 var Util = require('util/util'); // 详情页 var Detail = Vue.extend({ template: Util.tpl('tpl_detail'), // 定义数据 data: function () { return { data: {} } }, // 请求数据 created: function () { // 隐藏搜索框 this.$parent.showSearch = false; var me = this; // 获取商品id var id = me.$parent.query[0]; // console.log(id) // 请求数据 Util.ajax('data/product.json', function (res) { if (res && res.errno === 0) { // 存储数据 me.data = res.data } }) } }); // 注册组件 Vue.component('detail', Detail) module.exports = Detail; });
define(function (require, exports, module) { // 引入样式 require('vm/home/home.css'); // 引入过滤器 require('filter/filter'); // 引入Util模块 var Util = require('util/util'); var Home = Vue.extend({ template: Util.tpl('tpl_home'), // 定义数据 data: function () { // 返回值才是真正的数据 return { types: [ {id: 1, title: '美食', url: '01.png'}, {id: 2, title: '电影', url: '02.png'}, {id: 3, title: '酒店', url: '03.png'}, {id: 4, title: '休闲娱乐', url: '04.png'}, {id: 5, title: '外卖', url: '05.png'}, {id: 6, title: 'KTV', url: '06.png'}, {id: 7, title: '周边游', url: '07.png'}, {id: 8, title: '丽人', url: '08.png'}, {id: 9, title: '小吃快餐', url: '09.png'}, {id: 10, title: '火车票', url: '10.png'} ], // num: 111 // 将数据定义出来,否则没有特性 ad: [], list: [] } }, created: function () { this.$parent.showSearch = true; var me = this; Util.ajax('data/home.json', function (res) { if (res && res.errno === 0) { // 存储数据 me.ad = res.data.ad; console.log(me.ad,111111111111222222222) me.list = res.data.list; // 2.0版本不建议使用$set更新数据 // me.$set('ad', res.data.ad) } }) } }); Vue.component('home',Home); // 暴露接口 module.exports = Home; });
define(function (require, exports, module) { // 引入样式 require('./list.css'); // 引入过滤器 require('filter/filter'); // 引入工具 var Util = require('util/util'); // 列表页 var List = Vue.extend({ template: Util.tpl('tpl_list'), // 获取属性数据 props: ['searchquery'], data: function () { // 返回值才是绑定的数据 return { types: [ {value: '价格排序', key: 'price'}, {value: '销量排序', key: 'sales'}, {value: '好评排序', key: 'evaluate'}, {value: '优惠排序', key: 'discount'} ], // 定义存储数据的变量 list: [], // 剩余的产品 others: [], // 搜索字段 query: '' } }, // 动态数据 computed: { dealList: function () { var me = this; return this.list.filter(function (obj) { // console.log(arguments) return obj.title.indexOf(me.searchquery) >= 0; }) } }, // 定义方法 methods: { // 点击加载更多按钮 loadMore: function () { // 将other中数据传递给list this.list = this.list.concat(this.others); // 此时others中应该没有数据了 this.others = []; }, // 点击排序按钮 sortBy: function (type) { // 如果字段是优惠,我们要单独处理 if (type === 'discount') { this.list.sort(function (a, b) { // 比较原价-现价的插值 // 升序 // return (a.orignPrice - a.price) - (b.orignPrice - b.price) // 降序 return (b.orignPrice - b.price) - (a.orignPrice - a.price) }) } else { // 数组排序 this.list.sort(function (a, b) { // 升序 // return a[type] - b[type] // 降序 return b[type] - a[type] }) } // console.log(22, type) } }, // 视图渲染完成执行的方法 created: function () { this.$parent.showSearch = true; // console.log(this) // 用$parent的数据更新query this.query = this.$parent.query[1] var me = this; var url = 'data/list.json?' + this.$parent.query[0] + '=' + this.$parent.query[1] // 请求数据 Util.ajax(url, function (res) { // console.log(res) // 请求成功,存储数据 if (res && res.errno === 0) { // 默认显示三条 me.list = res.data.slice(0, 3); me.others = res.data.slice(3) } }) } }); // 注册 Vue.component('list', List); // 可以暴露接口,也可以不暴露接口 module.exports = List; });