【转】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 
     }
  ]
})

 

posted @ 2018-09-26 18:16  花影疏帘  阅读(124)  评论(0)    收藏  举报