Vue实战

crazestylus 2019-06-28

创建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引入

<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>

引用图标库

访问阿里巴巴图标库选择图标创建项目后获取样式链接

Vue实战

在index.html文件对应的引入

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

创建底部导航点击后访问的路由组件

Vue实战

引入与配置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.vue

<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中

<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实战

创建通用顶部搜索框非路由组件

路径:src/components/HeaderSearch/HeaderSearch.vue

<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中

/* 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>

运行效果
Vue实战

创建广告轮播组件

路径:src/components/Banner/Banner.vue

<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>

运行效果:
Vue实战

相关推荐

lyjava / 0评论 2020-07-30