Vue實戰

建立Vue項目

# 全局安裝 vue-cli
$ cnpm install --global vue-cli
# 建立一個基於 webpack 模板的新項目
$ vue init webpack my-project

項目運行

# 開發者模式運行[默認訪問:localhost:8080]
$ npm run dev

# 打包運行[默認訪問:localhost:5000]
$ npm run build
$ npm install -g serve
$ serve dist

開發目錄設計

src
》api            與後臺交互模塊文件夾
》common        通用資源文件夾,如fonts/img/stylus
》components    非路由組件文件夾
》filter        自定義過濾器模塊文件夾
》mock            模擬數據接口文件夾
》pages            路由組件文件夾
》router        路由器文件夾
》store            vuex相關模塊文件夾
- App.vue        入口應用組件
- main.js        入口JS

依賴stylus

$ npm install stylus stylus-loader --save-dev
# App.vue裏的<style>改爲以下:
<style lang='stylus' rel='stylesheet/stylus'>

使用stylus

  • 結構:經過縮進控制,不須要大括號和分號,冒號是可選的
  • 父級引用:使用字符&指向父選擇器
  • 定義變量(推薦變量以$開頭): name=value
  • 引用變量:name
  • 導入:經過@import引入其餘樣式文件

引入Reset CSS

重置瀏覽器標籤的樣式表:Reset CSS連接
src同目錄下的static文件夾下建立css/reset.css文件
訪問連接並複製裏面的CSS樣式粘貼到reset.css文件中
index.html引入css

<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>yu-mall</title>
    <link rel="stylesheet" href="http://at.alicdn.com/t/font_867696_7k42ws2p9ew.css">
    <link rel="stylesheet" href="/static/css/reset.css">
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

引用圖標庫

訪問阿里巴巴圖標庫選擇圖標建立項目後獲取樣式連接html

clipboard.png

在index.html文件對應的引入vue

<link rel="dns-prefetch" href="http://at.alicdn.com/t/font_867696_7k42ws2p9ew.css"/>

建立底部導航點擊後訪問的路由組件

圖片描述

引入與配置vue-router

# 安裝
$ npm install vue-router --save
# 建立路由配置JS 路徑:src/router/index.js
# index.js代碼塊
import Vue from 'vue'
import Router from 'vue-router'
import Home from '../pages/Home/Home'
import Category from '../pages/Category/Category'
import Cart from '../pages/Cart/Cart'
import User from '../pages/User/User'
Vue.use(Router)
export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      redirect: '/Home' // 重定向到首頁
    },
    {
      path: '/Home',
      component: Home // 首頁
    },
    {
      path: '/Category',
      component: Category // 分類
    },
    {
      path: '/Cart',
      component: Cart // 購物車
    },
    {
      path: '/User',
      component: User // 個人
    }
  ]
})

# main.js代碼塊
import Vue from 'vue'
import App from './App'
import router from './router/index'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

建立底部導航非路由組件

路徑:src/components/FooterGuide/FooterGuide.vuewebpack

<template>
  <div class="footer-guide">
    <div class="guide-item" v-for="guide in guides" :class="{on:guide.path === $route.path}" @click="goTo(guide.path)">
      <span class="item-icon">
        <i class="iconfont" :class="[guide.path === $route.path ? guide.choice_icon : guide.icon]"></i>
      </span>
      <span>{{ guide.text}}</span>
    </div>
  </div>
</template>

<script>
export default {
  name: 'FooterGuide',
  data () {
    return {
      'guides': [
        {
          text: '首頁',
          icon: 'icon-home',
          choice_icon: 'icon-homefill',
          path: '/home'
        },
        {
          text: '分類',
          icon: 'icon-form_light',
          choice_icon: 'icon-formfill',
          path: '/category'
        },
        {
          text: '購物車',
          icon: 'icon-cart',
          choice_icon: 'icon-cartfill',
          path: '/cart'
        },
        {
          text: '個人',
          icon: 'icon-people',
          choice_icon: 'icon-peoplefill',
          path: '/user'
        }
      ]
    }
  },
  methods: {
    goTo (path) {
      this.$router.replace(path)
    }
  }
}
</script>

<style lang='stylus' rel='stylesheet/stylus'>
@import "../../common/stylus/mixins.styl" /* $hr=1px #EEE solid */
.footer-guide
  border-top $hr
  position fixed
  z-index 100
  left 0
  bottom 0
  background #FFF
  width 100%
  height 50px
  display flex
  .guide-item
    text-decoration none
    display flex
    flex 1
    text-align center
    flex-direction column
    align-items center
    margin 5px
    color #8a8a8a
    &.on
      color #ff001e
    span
      font-size 12px
      margin-top 2px
      margin-bottom 2px
      .iconfont
        font-size 22px
</style>

將該組件添加到App.vue中web

<template>
  <div id="app">
    <router-view/>
    <footer-guide/>
  </div>
</template>
<script>
import FooterGuide from './components/FooterGuide/FooterGuide'
export default {
  name: 'App',
  components: {
    FooterGuide
  }
}
</script>

效果圖:
圖片描述vue-router

建立通用頂部搜索框非路由組件

路徑:src/components/HeaderSearch/HeaderSearch.vuevuex

<template>
    <div class="header-search">
      <div class="search-box">
        <a href="/search"><i class="iconfont icon-search"></i><span>請輸入你想找的商品</span></a>
      </div>
    </div>
</template>

<script>
export default {
  name: 'HeaderSearch',
  data () {
    return {
    }
  }
}
</script>

<style lang="stylus" rel="stylesheet/stylus">
@import "../../common/stylus/mixins.styl"
.header-search
  width 100%
  height 44px
  background #FFF
  border-bottom $hr
  .search-box
    background: #f3f5f4;
    border-radius: 14px;
    height: 28px;
    overflow: hidden;
    position relative;
    top: 8px
    margin 0 10px
    a
      text-decoration none
      i
        position absolute
        top 5px
        left 10px
        color #999
        font-size 18px
        font-weight bold
      span
        font-size 14px
        color #ccc
        position absolute
        left 36px
        line-height 28px
</style>

將該組件引入Home.vue和Category.vue中vue-cli

/* Home.vue */
<template>
  <div class="home">
    <header-search/>
  </div>
</template>
<script>
import HeaderSearch from '../../components/HeaderSearch/HeaderSearch'
export default {
  name: 'home',
  components: {
    HeaderSearch
  }
}
</script>

/* Category.vue */
<template>
  <div class="category">
    <header-search/>
  </div>
</template>

<script>
import HeaderSearch from '../../components/HeaderSearch/HeaderSearch'
export default {
  name: 'category',
  components: {
    HeaderSearch
  }
}
</script>

運行效果
圖片描述npm

建立廣告輪播組件

路徑:src/components/Banner/Banner.vueapi

<template>
  <div class="banner" ref="bannerImg" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend">
    <ul class="banner-img"
        @transitionend="transitionend"
        :style="{width: 100*(bannerData.length+2)+'%',transform: 'translateX('+translateX+'px)'}"
        :class="{transform:!isMove}">
      <li>
        <a :href="bannerData[bannerData.length-1].path">
          <img :src="bannerData[bannerData.length-1].imgUrl"/>
        </a>
      </li>
      <li v-for="data in bannerData">
        <a :href="data.path">
          <img :src="data.imgUrl"/>
        </a>
      </li>
      <li>
        <a :href="bannerData[0].path">
          <img :src="bannerData[0].imgUrl"/>
        </a>
      </li>
    </ul>
    <ul class="banner-bullet" :style="{'margin-left': -(16*this.bannerData.length)/2 + 'px'}">
      <li v-for="(data,i) in bannerData" :class="{on:i === bulletIndex }"></li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'home',
  props: ['banner-data'], /* {imgUrl:'',path:''} */
  data () {
    return {
      width: 0,
      index: 1, /* 0~bannerData.length + 1 */
      translateX: '0px',
      isMove: true,
      bannerTouch: {
        startX: 0,
        endX: 0,
        distanceX: 0
      },
      timer: {},
      timerData: 3000 /* 不能小於或等於過渡時間200秒 */
    }
  },
  mounted () {
    let p = this
    this.width = this.$refs.bannerImg.clientWidth
    this.translateX = -this.width
    this.timer = setInterval(function () {
      p.isMove = false
      p.index++
      p.translateX = -p.index * p.width
    }, this.timerData)
  },
  computed: {
    bulletIndex () {
      if (this.index >= this.bannerData.length + 1) {
        return 0
      } else if (this.index <= 0) {
        return this.bannerData.length - 1
      }
      return this.index - 1
    }
  },
  methods: {
    touchstart (ev) {
      this.bannerTouch.startX = ev.touches[0].clientX
    },
    touchmove (ev) {
      clearInterval(this.timer)
      var move = ev.touches[0].clientX
      this.bannerTouch.distanceX = move - this.bannerTouch.startX
      this.translateX = -this.index * this.width + this.bannerTouch.distanceX
      this.isMove = true
    },
    touchend (ev) {
      if (Math.abs(this.bannerTouch.distanceX) < this.width / 3) {

      } else if (this.bannerTouch.distanceX > 0) {
        this.index-- // = this.bannerData.length // 切換到倒數2
      } else { // if (this.index >= this.bannerData.length + 1) {
        this.index++
      }
      this.translateX = -this.index * this.width
      this.isMove = false
      let p = this
      clearInterval(this.timer)
      this.timer = setInterval(function () {
        p.isMove = false
        p.index++
        p.translateX = -p.index * p.width
      }, this.timerData)
    },
    transitionend () {
      if (this.index >= this.bannerData.length + 1) {
        this.index = 1
        this.isMove = true
        this.translateX = -this.index * this.width
      } else if (this.index <= 0) {
        this.index = this.bannerData.length
        this.isMove = true
        this.translateX = -this.index * this.width
      }
    }
  }
}
</script>

<style lang="stylus" rel="stylesheet/stylus">
  .banner
    width 100%
    height auto
    overflow-x hidden
    position relative
    .banner-img
      display flex
      &.transform
        transition all .2s
      li
        flex 1
      a
        display block
      img
        width 100%
        display block
        border none
    .banner-bullet
      position absolute
      bottom 15px;
      z-index 101
      display flex
      left 50%
      li
        &.on:before
          background #bc0000
          box-shadow 0px 0px 3px #bc0000
      li:before
        content ''
        width 6px
        height 6px
        border-radius 3px
        border 1px solid #FFF
        display block
        margin 0px 4px
        box-shadow 0px 0px 3px #ccc
</style>

修改Home.vue,引入該組件

<template>
  <div class="home">
    <header-search/>
    <banner :banner-data="bannerData"/>
  </div>
</template>

<script>
import HeaderSearch from '../../components/HeaderSearch/HeaderSearch'
import Banner from '../../components/Banner/Banner'
export default {
  name: 'home',
  components: {
    Banner,
    HeaderSearch
  },
  data () {
    return {
      bannerData: [
        {
          imgUrl: 'http://upload.shopncdemo.com/image/d5/3d/d53d507063c5195c74f24aabd9a0ec8a.jpg',
          path: '/100'
        },
        {
          imgUrl: 'http://upload.shopncdemo.com/image/06/48/064879ea8043a03787ec849c10fa4400.jpg',
          path: '/2'
        },
        {
          imgUrl: 'http://upload.shopncdemo.com/image/fb/c6/fbc658c0075ef4743e8cf0dd484a3796.jpg',
          path: '/3'
        },
        {
          imgUrl: 'http://upload.shopncdemo.com/image/24/65/24656dd4e23f398abb9afc2543b8f8de.jpg',
          path: '/4'
        }
      ]
    }
  }
}
</script>

運行效果:
圖片描述

相關文章
相關標籤/搜索