【转】5-vue实战
作者:datura_lj
链接:https://www.jianshu.com/p/ec436222c608
來源:简书
demo github地址:https://github.com/datura-lj/vue_cli_demo
一、 项目说明
ps:这个简单小项目只提供一个小小小的骨架,需要向“它”身上具体加多少“肉”,需要大家考虑好功能和布局后进行完善。
1.首先看下主页效果:如下图

主页分析:大体上分为上(header)、中(body或content)、下(footer)三部分,中间body部分是由若干个相同的li组成的“列表”,所以我们可将li定义为一个组件。
2.再来看下商品详情页:如下图

详情页分析:也分为上、中、下三部分。
二、 配置目录文件
在src文件夹(也就是我们码农主要工作区)下,创建assets文件夹(用来保存项目所需图片)、components(存组件CommonFooter.vue、DetailHeader.vue、HomeHeader.vue、List.vue)、pages(存页面GoodsDetail.vue、Home.vue)和main.js文件。详情请看下图:
ps: 1.具体文件夹以及文件名称可根据自己项目进行“自拟”。 2.这里的每一个*.vue文件都是一个组件。
三、配置相关接口
主页商品信息及图片, 是从服务器端返回的json数据,不可能所以商品都“写死”。故这里需要模拟后台建立了一个数据文件。在根目录下建立一个goods.json文件用来放“伪数据”,如下图:

goods数据
注意:下面的注释不要复制进去。否则可能会报错。
/* 模拟数据_代码 */
/* 这里只简单模拟一组比较规整数据,如大家有需求可自行加所需数据 */
{
"goods": [
{ "price": "69.9", "title": "德芙", "img": "http://m.360buyimg.com/babel/s211x211_jfs/t3688/270/776223567/128582/fa074fb3/58170f6dN6b9a12bf.jpg!q50.jpg.webp" },
{ "price": "63", "title": "费列罗", "img": "http://m.360buyimg.com/babel/s211x211_jfs/t613/100/1264998035/221234/1a29d51f/54c34525Nb4f6581c.jpg!q50.jpg.webp"},
{ "price": "29.9", "title": "大米", "img": "http://m.360buyimg.com/babel/s211x211_jfs/t1258/40/17387560/108696/aced445f/54e011deN3ae867ae.jpg!q50.jpg.webp"},
{ "price": "54.9", "title": "安慕希", "img": "http://m.360buyimg.com/babel/s211x211_jfs/t2734/15/680373407/215934/3abaa748/572057daNc09b5da7.jpg!q50.jpg.webp"},
{ "price": "58", "title": "金典", "img": "http://m.360buyimg.com/babel/s211x211_jfs/t2482/145/1424008556/91991/d62f5454/569f47a2N3f763060.jpg!q50.jpg.webp"},
{ "price": "60", "title": "味可滋", "img": "http://m.360buyimg.com/babel/s211x211_jfs/t2368/3/874563950/70786/7b5e8edd/563074c8N4d535db4.jpg!q50.jpg.webp" },
{ "price": "248.00", "title": "泸州老窖", "img": "http://m.360buyimg.com/babel/s211x211_jfs/t283/166/1424018055/189580/7c0792b7/543b4958N05fa2feb.jpg!q50.jpg.webp"},
{ "price": "328.8", "title": "剑南春", "img": "http://m.360buyimg.com/babel/s350x350_g15/M05/1A/0A/rBEhWlNeLAwIAAAAAAHyok3PZY0AAMl8gO8My0AAfK6307.jpg!q50.jpg.webp"},
{ "price": "49.00", "title": "蓝莓", "img": "http://m.360buyimg.com/babel/s211x211_jfs/t2332/148/2952098628/94387/e64654e2/56f8d76aNb088c2ab.jpg!q50.jpg.webp" },
{ "price": "68", "title": "芒果", "img": "http://m.360buyimg.com/n0/jfs/t3709/334/1378702984/206759/5c100ab5/58253588Naaa05c5c.jpg!q70.jpg"}
]
}
数据文件创建完成后,就需要写一个接口了,在build文件夹下dev-server.js文件进行配置。(版本调整,具体设置见【转】6-dev-server失踪问题解答)
使用nodeexpress

注意: 这里安装vue-resource后需要在main.js注册并使用下

在webpack.dev.conf配置express并设置路由规则

运行可以看到接口数据

四、编写页面代码
1. 页面入口index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>secondproject</title> <style media="screen"> /* 简单进行默认样式重置 */ * {margin: 0;padding: 0} li {list-style: none;} a {text-decoration: none;} img {vertical-align: middle;} </style> </head> <body> <div id="app"> <router-view></router-view> <!-- 这里是展示来自路由页面数据的 --> </div> </body> <script type="text/javascript"> /* 因为我们后面用的是rem布局,所以这里做下处理,根据不用设备分辨率更改跟字体大小。 rem相关布局[请参考](http://www.jianshu.com/p/65f80d4b44bb)*/ (function(win,doc){ change(); function change(){ doc.documentElement.style.fontSize = doc.documentElement.clientWidth *20/320+'px'; } win.addEventListener('resize',change,false); win.addEventListener('orientationchange',change,false); /* 这个是移动端设备横屏、竖屏转换时触发的事件处理函数 */ })(window,document); </script> </html>
2. 主要模版home.vue:这个是来展示数据的,并通过vue的路由展示到index.html
<template lang="html"> <div class="head"> <div class="header"> <h4 class="header-cont">主页</h4> </div> </div> </template> <script> export default { } </script> <style lang="css" scoped> .head{ width: 100%; height: 2rem; } .header{ width: 100%; height: 2rem; position: fixed; left: 0; top: 0; background-color: #fff; border-bottom: 2px solid #ff8000; } .header h4 { width: 100%; text-align: center; line-height: 2rem; font-size: 1.4rem; } </style>
b). commonFooter.vue:
说明:定义一个footer并定位到页面的底部,在ul内写4个li来模拟按钮。
<template lang="html"> <div class="footer"> <ul class="footer-cont"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul> </div> </template> <script> export default { } </script> <style lang="css"> .footer { height: 1.8rem; width: 100%; position: fixed; bottom: 0; left: 0; border-top: 2px solid #ccc; background-color: #fff; } .footer-cont li { float: left; width: 25%; height: 1.8rem; line-height: 1.8rem; font-size: 1.4rem; color: #ff8000; text-align: center; } .footer-cont li:active { background-color: #ccc; } .footer-cont:after { content: ''; display: block; clear: both; width: 0; height: 0; } </style>
c). list.vue:这个组件用来展示每个商品列表信息
<template lang="html"> <li class="goods-list"> <router-link to="/detail" class="goods-list-link"> <!-- 这个是用来跳转页面的,可以理解为a标签 --> <div class="goods-list-pic"> <img :src="img" alt=""> </div> <div class="goods-list-desc"> <p class="goods-list-name">{{ title }}</p> <p class="goods-list-price">{{ price|dTofixed|dCurrency }}</p> <!-- 这里用到了过滤器货币形式和保留两位小数 --> </div> </router-link> </li> </template> <script> export default { props: ['price', 'title', 'img'] /* props是子组件获取父组件数据用的 */ } </script> <style lang="css" scoped> .goods-list { width: 50%; float: left; box-sizing: border-box; margin-bottom: 0.2rem; } .goods-list:nth-of-type(odd) { border-right: 0.15rem solid #ccc; } .goods-list:nth-of-type(even) { border-left: 0.15rem solid #ccc; } .goods-list-link { display: block; padding: 0.5rem 0; margin: 0 0.3rem; text-align: center; background-color: #fff; } .goods-list:nth-of-type(even) .goods-list-link{ margin-left: 0; } .goods-list:nth-of-type(odd) .goods-list-link{ margin-right: 0; } .goods-list-pic { padding: 0.5rem; } .goods-list-pic > img { width: auto; height: 4rem; } .goods-list-desc { padding: 0 0.5rem; } .goods-list-desc:after { display: block; content: ''; clear: both; } .goods-list-name,.goods-list-price { width: 50%; height: 1.2rem; line-height: 1.2rem; font-size: 0.8rem; color: #333; float: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .goods-list-price { color: #ff8000; float: right; } </style>
d)home.vue
<template lang="html"> <div class="container"> <home-header></home-header> <!-- 展示引入的header组件 --> <div class="content"> <ul class="cont-ul"> <!-- list组件展示区,并用v-for来将数据遍历,:xx="xxx" 是用来给子组件传递数据的 --> <list v-for="item in items" :price="item.price" :title="item.title" :img="item.img"></list> </ul> </div> <common-footer></common-footer> <!-- 展示引入的footer组件 --> </div> </template> <script> import HomeHeader from '../components/HomeHeader' /* 本页面中用到了HomeHeader组件,所以就需要在这里引入一下 */ import CommonFooter from '../components/commonFooter' import List from '../components/list' export default { data () { return { items: [] /* 定义一个空数组数据items */ } }, components: { HomeHeader, CommonFooter, List }, created () { /* 这个是vue的钩子函数,当new Vue()实例创建完毕后执行的函数 */ this.$http.get('/api/goods').then((data) => { /* 调用vue的ajax来请求数据,promise语法,并用es6的箭头函数 */ this.items = data.body.data; }) } } </script> <style lang="css" scoped> .container { width: 100%; margin: 0 auto; } .content { margin-bottom: 1.8rem; } .cont-ul { padding-top: 0.5rem; background-color: #ccc; } .cont-ul::after { content: ''; display: block; clear: both; width: 0; height: 0; } </style>
home-header这个是在HTML中的写法,HomeHeade这样写也是可以的,这看公司的规范和个人习惯。但是需要注意的一点就是在JS中一定不要home-header。这个HomeHeader是在js中驼峰命名,在html中就用中划线“-”连接了
3. 商品详情页goodsDetail.vue:
<template lang="html"> <div class="head"> <div class="header"> <a href="javascript:;" class="go-back" @click="goBack">返回</a> <h4 class="header-cont">商品详情页</h4> </div> </div> </template> <script> export default { methods: { goBack () { window.history.back(); } } } </script> <style lang="css" scoped> .head{ height: 2rem; } .header{ width: 100%; height: 2rem; position: fixed; left: 0; top: 0; background-color: #fff; border-bottom: 2px solid #ccc; } .header .go-back { width: 2rem; height: 2rem; text-align: center; color: #ccc; font-size: 0.8rem; float: left; line-height: 2rem; margin-left: 1rem; position: absolute; left: 0; top: 0; } .header .header-cont { width: 100%; text-align: center; line-height: 2rem; font-size: 1.2rem; } </style>
b). goodsDetail.vue
<template lang="html"> <div class="detail"> <detail-header></detail-header> <p class="site-title">树懒果园 泰国进口大金煌芒果</p> <p class="site-cont">5斤装,约2-4个果,大!!!甜!!!</p> <common-footer></common-footer> </div> </template> <script> import DetailHeader from '../components/DetailHeader' import CommonFooter from '../components/commonFooter' export default { data () { return { } }, components: { DetailHeader, CommonFooter } } </script> <style lang="css"> .detail { padding: 0.25rem; font-size: 12px; } .detail > img { display: block; width: 80%; margin: 0 auto 0.5rem; } .detail > p { font-size: 1.1rem; line-height: 1.5rem; text-align: left; padding-bottom: 0.5rem; } .detail > p.site-title { color: #ff8000; } .detail > p.site-cont { color: #666; font-size: 0.9rem; } </style>
到此页面完成
注意有两个地方还需要修改
1,main.js最终文件
import Vue from 'vue' //import App from './App' //import router from './router' import VueResource from 'vue-resource' Vue.use(VueResource) import router from './router' Vue.config.productionTip = false /* eslint-disable no-new */ //new Vue({ // el: '#app', // router, // components: { App }, // template: '<App/>' //}) new Vue({ el: '#app', router, data:function(){ return{} } }) Vue.filter('dCurrency', function(value) { return '¥'+value }) Vue.filter('dTofixed', function(value) { var isNum = parseFloat(value); if( !isNum ){ alert("请输入数字") return "请输入数字"; } else { var value = Math.round(isNum*100)/100; var item = value.toString().split("."); if(item.length == 1){ value = value.toString()+".00"; return value; } if(item.length > 1){ if(item[1].length < 2){ value = value.toString()+"0"; } return value; } } })
router/index.js
import Vue from 'vue' import Router from 'vue-router' //import HelloWorld from '@/components/HelloWorld' import Detail from '../pages/GoodsDetail' Vue.use(Router) export default new Router({ routes: [ /*{ path: '/', name: 'HelloWorld', component: HelloWorld }*/ { path: '/', component: resolve => require(['../pages/home'],resolve), meta: { title:'home' } }, { path: '/detail', component: Detail } ] })

浙公网安备 33010602011771号