这篇文章主要介绍了基于vue使用webpack构建的一个简单的CMS(内容管理系统),记录各个步骤的实施以及问题的解决过程。

1. webpack-cms

全局设置时间过滤器

1
2
# 安装时间格式插件
npm i moment -S
1
2
3
4
5
// 在main.js中先导入moment时间格式插件再设置过滤器
import moment from 'moment'
Vue.filter('dataFormat', function (timeStr, patten='YYYY-MM-DD HH:mm:ss'){
return moment(timeStr).format(patten)
});

使用方法:

1
2
3
<span>时间:{{ item.time | dataFormat }}</span>
<!--或者传递事件格式参数自己定义显示的时间格式-->
<span>时间:{{ item.time | dataFormat('YYYY-MM-DD') }}</span>

新闻详情页制作

  • 将新闻列表改造成router-link的形式

    1
    2
    <!--注意:to前面的冒号代表v-bind:to 后面是url后面拼接id,to前面的冒号说明后面是一个表达式,表达式里面是字符串的拼接,所以要用单引号-->
    <router-link :to="'/home/newsList/' + item.id">
  • 创建newsInfo.vue页面

  • 在router.js中配置新闻详情的路由

    1
    2
    3
    4
    // 导入刚创建的页面组件
    import newsInfo from './coms/news/newsInfo.vue'
    // 注意id前面的冒号一定不能少,表示id是一个参数
    { path: '/home/newsList/:id', component: newsInfo },
  • 在newsInfo.vue页面获取id

    1
    2
    // 通过:id传递的参数用params获取,这与?传参不同
    $route.params.id
  • info的内容使用v-html绑定

    1
    <div class="j-info-con" v-html="newsInfo.content"></div>

封装一个comment子组件

  • 在subcom文件夹中创建一个comment.vue子组件

  • 在需要comment的页面导入comment组件

    • import comment from ‘./comment.vue’
  • 在父组件中使用components将comment组件注册为自己的子组件

  • 注册子组件时,将标签名在页面中引用即可

    1
    2
    3
    4
    // newsInfo.vue--从父组件把id传递给子组件 父组件中:
    <comment-box :id="this.id"></comment-box>
    // comments.vue--子组件中添加一个props属性接受id(这个props属性与data,methods是平级的):
    props: ['id']

点击获取更多评论数据

  • 为按钮添加点击事件

    1
    <mt-button type="danger" size="large" plain @click="getMore">加载更多</mt-button>
  • 点击加载更多按钮,让pageNum++,然后调用getCommentList()重新渲染数据

    1
    2
    3
    4
    getMore(){
    this.pageNum++;
    this.getCommentList()
    }
  • 因为每次取10条数据,为防止新数据覆盖旧数据,使用contact将数据连起来

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    getCommentList(){
    this.$http.get("api/getcomments/"+ this.id +"?pageindex=" + this.pageNum).then(res => {
    if(res.body.status === 0){
    // this.cmtList = res.body.message;
    // 为防止后面请求的数据覆盖前面的,每次加载更多都把数组数据拼接起来
    this.cmtList = this.cmtList.concat(res.body.message)
    }else{
    Toast('请求数据失败')
    }
    })
    }

图片分享

  • 制作顶部分类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    1. 使用mui中的tab-top-webview-main.html中的顶部选项卡-可左右拖动(webview)

    问题 1???
    2. 将代码copy到发现picList中发现顶到最顶端,去掉最外层的class类 mui-fullscreen,此时显示正常了,但是不能滑动,通过检查官方文档发现这是一个js组件,引入mui.js并执行初始化:
    import mui from '../../lib/mui/js/mui.js'
    mui('.mui-scroll-wrapper').scroll({
    deceleration: 0.0005 //flick 减速系数,系数越大,滚动速度越慢,滚动距离越小,默认值0.0006
    });

    问题 2???
    // 此时报错了!
    Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

    // 报错显示:mui.js中使用的'caller', 'callee', and 'arguments' 等这些属性在严格模式下不能使用,而webpack打包bundle.js默认启用的时严格模式,这用就有了冲突??
    1
    2
    3
    # 3. 解决冲突:移除webpack的严格模式,使用一下插件
    # https://github.com/genify/babel-plugin-transform-remove-strict-mode
    npm install babel-plugin-transform-remove-strict-mode

    在.babelrc文件中添加:

    1
    2
    3
    {
    "plugins": ["transform-remove-strict-mode"]
    }

    问题 3 ?? 此时能滑动了在移动端模式下页面上点击滑来滑去console中出现一下信息警告:
    [Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive.

    解决方法,在样式中添加:

    1
    * { touch-action: pan-y;}

    原因:(是chrome为了提高页面的滑动流畅度而新折腾出来的一个东西)

    http://www.cnblogs.com/pearl07/p/6589114.html
    https://developer.mozilla.org/zh-CN/docs/Web/CSS/touch-action
    https://www.zhangxinxu.com/wordpress/2018/07/chrome-safari-touchmove-preventdefault-treated-as-passive/

    问题 4??? 进入页面需要刷新一次才能开始执行滑动效果,因为初始化mui的scroll时候,DOM还没有渲染好

    1
    2
    3
    4
    5
    mounted(){ // 在组件中DOM元素被渲染好后,会执行此vue的生命周期mounted钩子函数
    mui('.mui-scroll-wrapper').scroll({
    deceleration: 0.0005 //flick 减速系数,默认值0.0006
    });
    },

    问题 5??? 此时点击底部的路由导航,失灵了,点击无效

    分析:在App.vue中去掉mui-tab-item这个类名,路由又能点击了,可能是这个class类名与之前的某些操作有冲突,解决方法:样式copy不变,将mui-tab-item更名为 j-mui-tab-item

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    .mui-bar-tab .j-mui-tab-item.mui-active {
    color: #007aff;
    }
    .mui-bar-tab .j-mui-tab-item {
    display: table-cell;
    overflow: hidden;
    width: 1%;
    height: 50px;
    text-align: center;
    vertical-align: middle;
    white-space: nowrap;
    text-overflow: ellipsis;
    color: #929292;
    }
    .mui-bar-tab .j-mui-tab-item .mui-icon {
    top: 3px;
    width: 24px;
    height: 24px;
    padding-top: 0;
    padding-bottom: 0;
    }
    .mui-bar-tab .j-mui-tab-item .mui-icon~.mui-tab-label {
    font-size: 11px;
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    }

图片详情

  • 导入评论子组件

    1. 导入comment.vue

      1
      import comment from '../subcoms/comment.vue'
    2. 创建评论组件

      1
      2
      3
      components: {
      'cmt-box': comment
      }
    3. 创建组件标签,要传递一个id过去

      1
      <cmt-box :id="this.id"></cmt-box>
  • 图片展示预览

    1. 使用 vue-preview 这个集成了 PhotoSwipe 图片预览插件

      1
      2
      # NOTICE: This plugin currently support vue2.5 and above
      npm i vue-preview -S
    2. 安装插件,在main.js中

      1
      2
      3
      import VuePreview from 'vue-preview'
      // defalut install
      Vue.use(VuePreview)
    3. 使用

      1
      2
      <!--页面上放下面的代码,其中thumbImg是图片列表数组-->
      <vue-preview :slides="thumbImg" @close="handleClose"></vue-preview>
      1
      2
      3
      4
      5
      6
      7
      // 2. 在获取图片的成功回调中根据文档循环每个图片,补全预览时候图片的宽和高
      res.body.message.forEach(item => {
      item.msrc = item.src;
      item.w = 600;
      item.h = 400;
      });
      this.thumbImg = res.body.message;
      1
      2
      3
      4
      5
      6
      7
      8
      9
      /*因为插件中使用的figure是块元素,想要图片水平排列要加inline-block,同时可以设置显示图片的宽高*/
      figure {
      display: inline-block;
      margin: 5px;
      height: 60px;
      img {
      width: 60px;
      }
      }

在手机上进行项目的预览和测试

  • 保证手机和电脑处于同一WiFi环境
  • 在项目的package.json的dev中添加一个 –host 指令,把当前WiFi的IP地址设置为 –host的指令值
  • Total Contral

编程式的导航

  • 编程式导航中文文档

  • 创建组件模块

  • 引入组件模块,创建路由

    1
    2
    3
    4
    import goodsInfo from './coms/goods/goodsInfo.vue'

    // 商品列表 name是这个路由的别名,为了后面实现编程式导航使用这个属性
    { path: '/home/goodsInfo/:id', component: goodsInfo, name: 'goodsInfo' },
  • 实现跳转:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div class="goods-item" v-for="item in goodsList" :key="item.id" @click="getGoodsInfo(item.id)"></div>

    // 以下三种方法都可以,可根据需求来写,注意:如果提供了path,params会被忽略。
    // 最后都能跳转到 /home/goodsInfo/123
    getGoodsInfo(id=123){
    // this.$router.push({path: `/home/goodsInfo/${id}`})
    // this.$router.push({name: 'goodsInfo', params: {id}})
    this.$router.push('/home/goodsInfo/' + id)
    }

封装轮播图组件

  • 将轮播图抽离出来,做成一个能公用的组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <template>
    <div>
    <mt-swipe :auto="4000">
    <mt-swipe-item v-for="item in banner" :key="item.id">
    <img :src="item.img" :class="{wFull: isFull}">
    </mt-swipe-item>
    </mt-swipe>
    </div>
    </template>
    <script>
    export default {
    props: ['banner', 'isFull']
    }
    </script>
    <style lang="less" scoped>
    .mint-swipe {
    height: 150px;
    .mint-swipe-item {
    text-align: center;
    img {
    height: 100%;
    }
    }
    }
    .wFull {
    width: 100%;
    }
    </style>

    调用的时候:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 引入组件模板,goodsBanner 是传进去的图片循环数组,isFull是宽度是否100%显示
    <swiper :banner="goodsBanner" :isFull="false"></swiper>

    // 引入组件模块
    import swiper from '../subcoms/swiper.vue'

    // 在components中定义
    components: {
    swiper
    }

制作加入购物车的小球动画

  • 需求:点击购物车,小球出现从购买数量处添加到购物车,实现动画效果

  • html

    1
    2
    3
    4
    5
    6
    <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter">
    <div class="ball" v-show="ballFlag" ref="ball"></div>
    </transition>
  • css

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    .ball { /*小球最初在购买数量上面显示,点击加入购物车小球调到购物车里然后隐藏*/
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: red;
    position: absolute;
    top: 313px;
    left: 152px;
    z-index: 99;
    }
  • js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 在data中定义一个 ballFlag:0 默认隐藏小球
    addCart(){
    this.ballFlag = !this.ballFlag
    },
    beforeEnter(el){
    el.style.transform = 'translate(0, 0)'
    },
    enter(el, done){
    // 获取小球需要移动的距离
    // id为badge的元素就是home页面中购物车徽章的那个元素
    let ballPos = this.$refs.ball.getBoundingClientRect(); //小球位置
    let badgePos = document.getElementById('badge').getBoundingClientRect(); //目标位置
    // x和y需要移动的距离
    let xDist = badgePos.left - ballPos.left;
    let yDist = badgePos.top - ballPos.top;

    el.offsetWidth; // 保证小球有动画
    el.style.transform = `translate(${xDist}px, ${yDist}px)`;
    el.style.transition = 'all .8s cubic-bezier(.65,-0.71,1,.59)';
    done(); // 调用afterEnter()
    },
    afterEnter(el){
    this.ballFlag = !this.ballFlag
    }
  • 备注:

将库存数量作为numbox的最大值

  • 父组件-html

    1
    2
    <!--在组件上定义一个max值传递给子组件,这个值就是库存数-->
    <numBox @sendSelNum="getSelNum" :max="goodsInfo.stock_quantity"></numBox>
  • 子组件接受-html

    1
    2
    <!--data-numbox-max前面加冒号表明后面是一个表达式,即接受父组件传递过来的max-->
    <div class="mui-numbox" data-numbox-min='1' :data-numbox-max='max'>
    1
    2
    3
    4
    5
    6
    7
    props: ['max'], // 在props中接受父组件传递来的max
    watch: { // 监听父组件传递过来的max值的变化,将变化后的值作为numbox的最大值
    'max': function (newVal, oldVal){
    // 使用js API设置numbox的最大值
    mui(".mui-numbox").numbox().setOption('max', newVal)
    }
    }