17 Dec 2017

VUE2项目搭建

简介:
vue 是尤雨溪大神的作品,在国内已位列三大框架之一。
个人感觉vue集合了Angular1和React优点的项目
注意 因占位符冲突,本文用\{\{ \}\} 代替 双大括号(去掉\)

技术栈

  1. vue2 构建用户界面自底向上增量开发的的渐进式框架
  2. vuex 状态机制
  3. vue-router 路由机制
  4. webpack2 文件打包
  5. es6
  6. vue-resource 网络请求机制
  7. iview css框架
  8. sublimeText格式化插件:
    HTML/CSS/JS Prettify安装后 tools->HTML/CSS/JS Prettify->set prettify preference:在”allowed_file_extensions”: ["htm", "html", "xhtml", "shtml", "xml", "svg","vue"] 加上vue就好了
  9. 淘宝源:npm config set registry https://registry.npm.taobao.org

基本骨架

vue-cil构建项目

npm install -g vue-cli webpack
npm install -g vue-cli  
vue init webpack-simple vue2demo
# vue init webpack vue2demo  (完整的项目骨架)
cd vue2demo && npm install&&npm run dev

启动文件;start.bat

@echo off  
start cmd /k "cd vue2demo&&npm run dev"  
start http://172.168.1.70:5555

目录结构及相关依赖:

  • vueDemo
    • dist
    • src
      • assets
        • img
        • style
        • js
      • components
        • main
          • Nav
          • top
          • nav.vue
          • top.vue
          • foot.vue
        • common
          • directive.js
          • filter.js
        • page
          • edit.vue
          • home.vue
          • list.vue
          • map.vue
        • main.vue
        • bus.js
        • router.js
      • App.vue
      • main.js
    • index.html
    • package.json
    • webpack.config.js

构建项目结构:

cd src&&mkdir components&&cd assets&&mkdir img style js&&cd ../components&&mkdir main common page&&touch main.vue bus.js&& cd main&&mkdir Nav top && touch nav.vue top.vue foot.vue&& cd ../common && touch directive.js filter.js && cd ../page &&touch edit.vue home.vue list.vue map.vue && cd ../.. && touch components/router.js 

安装全家桶及相关依赖:

npm install --save  vue-router vue-resource vuex jquery@2.2.3  iview 
npm install --save-dev css-loader style-loader less less-loader url-loader html-webpack-plugin

修改webpack.config.js

var HtmlWebpackPlugin = require('html-webpack-plugin')
// npm install --sava-dev html-webpack-plugin
 module: {
    //文件处理
        rules: [{
                test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|otf|svg|svgz)(\?.+)?$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 10000
                    }
                }]
            }, { 
                test: /\.less$/, loader: 'style-loader!css-loader!less-loader' },{ 
                test: /\.css$/, loader: 'style-loader!css-loader' }
        ]
    },
    resolve: { 
    //拓展名      
        extensions: ['.js', '.vue', '.json'],
        //简写
        alias: {
            'vue$': 'vue/dist/vue.esm.js',
            '@': path.resolve(__dirname, './src')
        },
        //去哪找依赖
        modules: [
            path.join(__dirname, './node_modules')
        ]
    },
     devServer: {
        historyApiFallback: true,
        noInfo: true,
        port: 3333,
        host: '172.168.1.70'
    },
    //注册全局变量
 module.exports.plugins = (module.exports.plugins || []).concat([
    new webpack.ProvidePlugin({
        $: "jquery",
        jQuery: "jquery",
        "window.jQuery": "jquery"
    }),
//    new webpack.optimize.CommonsChunkPlugin('common'),
    new HtmlWebpackPlugin()
])
// new webpack.optimize.CommonsChunkPlugin('common.js'),
//在.vue文件中引入第三方非NPM模块  exports-loader
//var Showbo = require("exports?Showbo!./path/to/showbo.js");

vue-router 路由

  1. touch components/page/demo.vue
    <template>
        <div class="main">
            <h1>\{\{ $route.params.id2 \}\}  </h1>
            <!-- <router-link :to="{path:'details',query: {id:el.tog_line_id}}">
                    id = this.$route.query.id;  -->
            <router-link to="/子路由">子路由</router-link>
            <hr>pathDemo
            <router-view name='pathDemo'></router-view>
            <hr>default
             <router-view></router-view>
        </div>
        </div>
    </template>
    <script>
    export default {
        name: 'main',
        data() {
            return {
                msg: 'main'
            }
        },
        watch: {
            '$route' (to, from) {
                // 对路由参数变化作出响应...
            }
        }
    }
    </script>
    <style scoped>
    </style>
    
  2. touch components/page/demo2.vue
    <template>
        <div class="main">
            <h1>\{\{ $route.params.name \}\} </h1>
            <router-view></router-view>
        </div>
        </div>
    </template>
    <script>
    export default {
        name: 'main',
        data() {
            return {
                msg: 'main'
            }
        },
        watch: {
            '$route' (to, from) {
                // 对路由参数变化作出响应...
            }
        }
    }
    </script>
    <style scoped>
    </style>
    
  3. 修改 app.vue
    <template>
        <div id="app">
            <h1>\{\{msg\}\}</h1>
            <router-view></router-view>
        </div>
    </template>
    <script>
    export default {
        name: 'app',
        data() {
            return {
                msg: 'Welcome'
            }
        }
    }
    </script>
    
  4. 修改 main.js
    import Vue from 'vue'
    import App from './App'
    import router from './components/router'
    import Vuex from 'vuex'
    import VueResource from 'vue-resource'
    Vue.use(VueResource)
    Vue.use(router)
    Vue.use(Vuex)
    new Vue({
     el: '#app',
     router,
     render: h => h(App)
    })
    
  5. touch components/router.js
    import Vue from 'vue'
    import Router from 'vue-router'
    import demo from './page/demo'
    import demo2 from './page/demo2'
    Vue.use(Router)
    export default new Router({
     routes: [{
         path: '/',
         component: demo
     }, {
         // /user/:username/post/:post_id
         path: '/demo/:id',
         component: demo,
         children: [{
             path: '/:name',
             components: {
                 pathDemo: demo2,
                 default: demo
             }
         }]
     }]
    })
    

多页面

  1. webpack.config.js
    var HtmlWebpackPlugin = require('html-webpack-plugin')
    var glob = require('glob')
    var entryJS = glob.sync('./src/*.js').reduce(function(prev, curr) {
        prev[curr.slice(6, -3)] = curr;
        return prev;
    }, {});
    //entryJS['common'] = ['vue', 'jquery', 'iview', 'router', 'Vuex', 'VueResource', 'directive']
    entryJS['common'] = ['vue', 'jquery', 'router'];
       
    var htmls = glob.sync('./*.html').map(function(item) {
        return new HtmlWebpackPlugin({
            filename: item,
            template: item,
            inject: false,
            chunks: [item.slice(2, -5), 'common']
        });
    });
    //...
     entry: entryJS,
        output: {
            path: path.resolve(__dirname, './dist'),
            publicPath: '/dist/',
            // filename: '[name].[hash].js'
            filename: '[name].js'
        },
    //...
    //注册全局变量
    module.exports.plugins = (module.exports.plugins || []).concat([
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery",
            "window.jQuery": "jquery"
        }),
        new webpack.optimize.CommonsChunkPlugin('common')
        // , new HtmlWebpackPlugin()
    ]).concat(htmls);
    
  2. 修改 index.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <title>vue2demo-index</title>
    </head>
    <body>
        <div id="index"></div>
        <script type="text/javascript" src="/dist/common.js"></script>
        <script type="text/javascript" src="/dist/index.js"></script>
    </body>
    </html>
    
  3. touch main.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <title>vue2demo-main</title>
    </head>
    <body>
        <div id="app"></div>
        <script type="text/javascript" src="/dist/common.js"></script>
        <script type="text/javascript" src="/dist/main.js"></script>
    </body>
    </html>
    
  4. touch index.js
    import Vue from 'vue'
    import App from './index.vue'
    import router from './components/router'
    import Vuex from 'vuex'
    import VueResource from 'vue-resource'
    Vue.use(VueResource)
    Vue.use(router)
    Vue.use(Vuex)
    new Vue({
     el: '#index',
     router,
     render: h => h(App)
    })
    
  5. touch index.vue
    <template>
        <div id="index">
            <h1>\{\{msg\}\}</h1>
            <a href='./main.html'>去主页</a>
        </div>
    </template>
    <script>
    export default {
        name: 'index',
        data() {
            return {msg: 'index'}
        }
    }
    </script>
    

网络请求

vue-resource

修改 main.js

import VueResource from 'vue-resource'
Vue.use(VueResource)

router.beforeEach((to, from, next) => {
    console.log('路由跳转前的预处理');
    // next({ path: '/' })
    next();
})
Vue.http.interceptors.push((request, next) => {
    console.log('请求前的预处理和配置');
    request.method = 'GET'; 
    next((response) => {
        console.log('响应后传给then前对response修改');
        if (!response.ok) {
            console.log('success');
        } else {
            console.log('error');
        }
        return response;
    });
});

修改 App.vue

<template>
    <div id="app">
        <h1>\{\{msg\}\}</h1>
        <button @click.stop='resource()'>resource</button>
        <router-view></router-view>
    </div>
</template>
<script>
export default {
    name: 'app',
    data() {
        return {
            msg: 'Welcome'
        }
    },
    methods: {
        resource: function() {
            this.$http.post(
                'https://cnodejs.org/api/v1/topics', {
                    emulateJSON: true,
                    emulateHTTP: true,
                    data: {
                        'page': 1,
                        'tab': 'job',
                        'limit': 2,
                        'mdrender': true
                    }
                }).then((res) => {
                // this.items=res.body.data;
                // this.$set(this.$data, 'items', res.body.data);
                console.log(res.body.data);
            }, (res) => {
                console.log(res);
            }).catch((res) => {
                console.log(res)
            });
        }
    }
    }
</script>

ajax

修改 App.vue

        ajax: function() {
            var isEmpty = function(object) {
                for (var name in object) {
                    return false;
                }
                return true;
            }
            var data={};
            var param = {
                'page': 1,
                'tab': 'job',
                'limit': 2,
                'mdrender': true
            };
            var trimStr = function(key, value) {
                if (typeof(value) === 'string') {
                    return $.trim(value);
                }
                return value;
            }
            if (Object.keys(param).length > 0) {
                var obj = Object.keys(param).length === 1 ? param[Object.keys(param)[0]] : param;
                data = {
                    // data: typeof(obj) === 'string' ? trimStr(obj) : JSON.stringify(obj, trimStr)
                    data: typeof(obj) === 'string' ? obj : JSON.stringify(obj)
                }
            }
            var converEmptyProperty = function(object) {
                for (var i in object) {
                    var value = object[i];
                    // Object.prototype.toString.call(object) == '[object Null]'   '[object Undefined]')
                    if (typeof value === 'object') {
                        if (Array.isArray(value)) {
                            if (value.length == 0) {
                                //   delete object[i];
                                console.log(i);
                                object[i] = [];
                                continue;
                            }
                        } else if (Object.prototype.toString.call(value) == '[object Null]') {
                            object[i] = '';
                        } else if (Object.prototype.toString.call(value) == '[object Undefined]') {
                            object[i] = '';
                        } else if (isEmpty(value)) {
                            object[i] = {};
                        }
                        converEmptyProperty(value);

                    } else {
                        if (value === '' || value === null || value === undefined) {
                            object[i] = '';
                        } else {
                            // console.log('check ', i, value);
                        }
                    }
                }
            }
            $.ajax($.extend({}, data, {
                url: 'https://cnodejs.org/api/v1/topics',
                type: 'get',
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                crossDomain: true,
                xhrFields: {
                    withCredentials: true
                },
                beforeSend: function(jqXHR, settings) {
                    //请求前数据处理
                    if (settings.type == 'GET') {
                        settings.url = settings.url.replace(/\:/g, '=')
                        .replace(/,\"/g, '&').replace(/[{}"]/g, "")
                        .replace("=//","h://");
                    } else {
                        if (settings.data) {
                            settings.data = JSON.stringify(JSON.parse(settings.data), trimStr);
                        }
                    }
                    return true;
                },
                dataFilter: function(data, type) {
                    // 返回的数据处理
                    var preData = JSON.parse(data)
                    converEmptyProperty(preData);
                    return JSON.stringify(preData)
                }
            })).error(function(jx) {
                console.log(jx);
            });
        }

ajax 文件上传

 var formData = new FormData();
 let _self=this;
 // var name = $("input").val();
 // formData.append("name",name);
 let _file = $("#" + this.id)[0];
 if (_file && _file.files[0]) {
     formData.append("file", _file.files[0]);
     $.ajax({
         url: store.state.systemInfo.uploadUrl,
         type: 'post',
         data: formData,
         // 告诉jQuery不要去处理发送的数据
         processData: false,
         // 告诉jQuery不要去设置Content-Type请求头
         contentType: false,
         enctype: 'multipart/form-data',
         cache: false,
         beforeSend: function() {
             console.log("正在进行,请稍候");
         },
         success: function(data) {
          console.log("error"); 
         },
         error: function(data) {
             console.log("error");
         }
     });
 }

集成vuex

  1. touch components/common/vuexStore.js
    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    const vuexStore = new Vuex.Store({
     state: {
         dic: {}
     },
     mutations: {
         addDic(state, dics) {
             $.extend(state.dic, dics);
         }
     },
     getters: {
         getDic: (state) => (key) => {
             return `${state.dic.lable}---${state.dic.code}----${key} `;
         }
     }
    })
    export { vuexStore as default };
    
  2. 编辑main.js
    import store from './components/common/vuexStore.js'
    //....
    new Vue({
    el: '#app',
    store,
    router,
    render: h => h(App)
    })
    
  3. 编辑App.Vue
    <span>\{\{dicMsg\}\}</span>
    <button @click.stop='addDic()'>addDic</button>
    <button @click.stop='getDic1()'>getDic1</button>
    <button @click.stop='getDic2()'>getDic2</button>
    //.....
     addDic: function() {
             this.msg = this.msg + " " + this.msg;
             var n = this.msg.match(/Welcome/g) || [];
             this.$store.commit("addDic", {
                 'code': n.length,
                 'lable': this.msg
             });
         },
         getDic1: function() {
             this.dicMsg = this.$store.state.dic;
         },
         getDic2: function() {
             this.dicMsg = this.$store.getters.getDic('key');
         }
    

bus.js

import Vue from 'vue'
export default new Vue();
(function(global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
        typeof define === 'function' && define.amd ? define(factory) :
        (global.VueBus = factory());
}(this, (function() {
    'use strict';
    function VueBus(Vue) {
        var bus = new Vue();
        bus.data = {host: ""}
        Object.defineProperty(Vue.prototype, '$bus', {
            get: function() {
                return bus;
            }
        });
    }
    if (typeof window !== 'undefined' && window.Vue) {
        window.Vue.use(VueBus);
    }
    return VueBus;
})));

使用

//引入文件
import bus from './bus.js'
//广播消息
 bus.$emit('toggleLoading', {'showMsg':false});
//监听消息
 bus.$on('toggleLoading', (show) => {
            this.toShow = show;
        });

自定义组件

  1. 编写组件文件 touch components/common/pagePlugin.vue
    <template>
        <div class="am-u-lg-12 am-cf">
            <div class="am-fr">
                <ul class="am-pagination tpl-pagination">
                    <li :class="[(pages[0]==1)?'am-disabled':'']"><a href="javascript:;" v-on:click="prevClick()">«</a></li>
                    <li v-for="index in pages" :class="[(pageData.pageNo == index)?'am-active':'']">
                        <a href="javascript:;" v-on:click="btnClick(index)">\{\{ index \}\}</a>
                    </li>
                    <li :class="[(pages[pages.length-1]==pageData.totalPages)?'am-disabled':'']">
                        <a href="javascript:;" v-on:click="nextClick()">»</a></li>
                    <li><a><i>\{\{pageData.totalPages\}\}(\{\{pageData.pageNo\}\})</i>页/共<i>\{\{pageData.totalRecords\}\}</i></a></li>
                </ul>
            </div>
        </div>
    </template>
    <script>
    import $ from 'jquery';
    export default {
        name: 'pagePlugin',
        props: {
            pageData: Object,
            goto: Function
        },
        data() {
            return {
                pages: []
            }
        },
        methods: {
            getData: function(event) {
                this.pages = [];
                let startNo = 1,
                    endNo = 1;
                if (this.pageData.pageNo < 4) {
                    endNo = this.pageData.totalPages;
                } else if (this.pageData.pageNo + 4 > this.pageData.totalPages) {
                    endNo = this.pageData.totalPages;
                    if (this.pageData.totalPages > 6) {
                        startNo = this.pageData.totalPages - 5
                    }
                } else {
                    startNo = this.pageData.pageNo - 5, endNo = this.pageData.pageNo + 4;
                }
                for (let i = startNo; i <= endNo; i++) {
                    this.pages.push(i);
                }
            },
            prevClick: function(event) {
                let newPages = [];
                let arrInxSize = this.pages.length - 1;
                for (let a = 0; a < 11; a++) {
                    if ((this.pages[0] - a) > 0) {
                        newPages.push(this.pages[0] - a);
                    } else {
                        for (let aa = 1; aa <= this.pages.length - a; aa++) {
                            newPages.push(this.pages[0] + aa);
                        }
                        break;
                    }
                }
                if (newPages) {
                    this.pages = newPages.sort(function(a, b) {
                        return a - b;
                    });
                }
            },
            btnClick: function(index) {
                this.goto(index);
            },
            nextClick: function(event) {
                let newPages = [];
                let arrInxSize = this.pages.length - 1;
                for (let a = 1; a < 11; a++) {
                    if ((this.pages[arrInxSize] + a) <= this.pageData.totalPages) {
                        newPages.push(this.pages[arrInxSize] + a);
                    } else {
       
                        if (newPages != []) {
                            for (let aa = arrInxSize; aa >= a - 1; aa--) {
                                newPages.push(this.pages[aa]);
                            }
                            break;
                        }
                    }
                }
                if (newPages) {
                    this.pages = newPages.sort(function(a, b) {
                        return a - b;
                    });
                }
            }
        },
        watch: {
            "pageData.pageNo": 'getData',
            "pageData.pageSize": 'getData'
        },
        mounted: function() {
                this.getData();
            }
            /*,
                created() {
                    bus.$on('toggleLoading', (show) => {
                        this.toShow = show;
                    });
                }*/
    }
    </script>
    
  2. 全局注册
    1. 在main.js 引入并全局注册
      import pagePlugin from './components/common/pagePlugin'
      Vue.component('page-plugin', pagePlugin)
      
    2. 独立全局组件文件 touch components/common/plugin.js
      import Vue from 'vue'
      import pagePlugin from './pagePlugin'
      Vue.component('page-plugin', pagePlugin)
      

      main.js

      import plugin from './components/common/plugin'
      
  3. 使用: <page-plugin :pageData="pageData" :goto="goto"></page-plugin>
     pageData: {
                 'pageNo': 1,
                 'totalPages': 10,
                 'totalRecords': 100,
                 'pageSize': 10
             }
    //....
    goto:function(data){
    console.log(data);
    },
    

自定义指令

  1. 编写文件 touch components/common/directive.js
    import $ from 'jquery'
    import Vue from 'vue'
    Vue.directive('example', {
     // deep: true, //相关属性也是一个对象
     twoWay: true, //双向绑定
     acceptStatement: true, //让指令像 v-on 一样接受内联语句
     // isLiteral: true, //值被看成字符串,不会建立数据监视 
     //<div v-my-directive="a++"></div>
     bind: function(el, binding, vnode) { //初始化动作
         bind: function(el, binding, vnode) { //初始化动作
         // this.handler = function() {
         //     this.set(el.value) //赋值
         // }.bind(this)
         // el.addEventListener('input', this.handler);
         el.innerHTML=binding.value;
     },
     },
     // this.vm.$on('rotate', () => { })
     inserted() { //被绑定元素插入父节点时调用
         // console.log('inserted');
     },
     update(el, binding, vnode, oldVnode) { //模板更新和绑定数据改变时触发        
         //       this.vm.$emit('crop', event)       
     },
     componentUpdated() { //模板完成一次更新周期时调用
         // console.log('componentUpdated');
     },
     unbind: function() { //指令与元素解绑时调用
         // this.vm.$off('rotate')
         this.el.removeEventListener('input', this.handler)
     }
    })
    Vue.directive('options', function(el, binding) {
     let value = binding.value.value;
     let lable = binding.value.lable;
     let opts = binding.value.options;
     let optStr = '<option value=""></option>';
     if (Array.isArray(opts)) {
         for (let opt of opts) {
             optStr += '<option value="' + opt[value || 'id'] + '">' + opt[lable || 'lable'] + '</option>';
         }
         $(el).empty();
         $(el).append(optStr);
     } else {
         console.log('指令(v-options)参数有误!!!');
     }
    })
    // $(el).trigger("input");
    // el.dispatchEvent(new Event('change', { target: el.target })) ;
    // el.dispatchEvent(new Event('input', { target: el.target }));
    Vue.directive('focus', {
     isLiteral: true, //值被看成字符串,不会建立数据监视 
     //   inserted:  
     function(el, binding) {
         el.focus();
         // el.style.backgroundColor = binding.value.color
     }
    })
    Vue.directive('example2', {
     params: ['a'],
     paramWatchers: {
      a: function (val, oldVal) {
    console.log('a changed!')
      }
     }
    })
    
  2. 注册 main.js
    import directive from './components/common/directive.js'

  3. 使用:
    <span v-example="pageData"></span>

自定义filter

  1. 编写指令 touch components/common/filter.js
     Vue.filter( 'discount' , function(value,discount) {
         return value  * ( discount / 100 ) ;
     // read: function (value) {
     //    return '$' + value.toFixed(2)
     //  },
     //  write: function (value) {
     //    var number = +value.replace(/[^\d.]/g, '')
     //    return isNaN(number) ? 0 : number
     //  }
      });
    

    mian.js引入

    import filter from './components/common/filter.js'
    

    调用:

    <ul>
          <li v-for="product in products">
           \{\{ product.name \}\} - \{\{ product.price| discount(25)\}\}
          </li>
    </ul>
    

    // getter,返回已注册的过滤器 var myFilter = Vue.filter(‘discount’)

编辑 main.js 引用

import demoDirective from './components/common/demoDirective'

使用

<div  v-demo:hello.a.b="message" style='color:red'></div>

全局组件文件

  1. 编写文件 touch components/common/myPlugin.js
    ;(function() {
     var MyPlugin = {}
     MyPlugin.install = function(Vue, options) {
         // 全局方法或属性
         //组件外使用 Vue.test();
             Vue.test = function() {
                 alert("123")
             },
             // 2. 全局资源(指令,过滤器)
             // <div v-test></div>
             Vue.directive('test', {
                 isFn: true,
                 acceptStatement: true,
                 bind(el, binding, vnode, oldVnode) {
                     // 逻辑...
                     console.log("添加全局资源");
                 },
                 update: function(fn) {},
                 unbind: function() {}
             })
           // 3. 注入组件
           //在 vue 初始化对象的时候,把 Vue.mixin 的参数 复制到了初始化对象中
         Vue.mixin({
                 created: function() {
                     // 逻辑...
                     // console.log("注入组件");
                 }
             })
             //添加实例方法
             //组件内 this.doubleNumber(2);
         Vue.prototype.doubleNumber = function(val) {
             if (typeof val === 'number') {
                 return val * 2;
             } else if (!isNaN(Number(val))) {
                 return Number(val) * 2;
             } else {
                 return null
             }
         }
     }
     if (typeof exports == "object") {
         module.exports = MyPlugin
     } else if (typeof define == "function" && define.amd) {
         define([], function() {
             return MyPlugin
         })
     } else if (window.Vue) {
         window.MyPlugin = MyPlugin
         Vue.use(MyPlugin)
     }
    })()
    
  2. main.js
    import MyPlugin from './components/common/myPlugin.js'
    Vue.use(MyPlugin)
    

测试环境搭建

需要安装的包

1. karma //提供测试所需的浏览器环境、监测代码改变自动重测、整合持续集成等功能
2. phantomjs-prebuilt //phantomjs,在终端运行的浏览器虚拟机
3. mocha //test framework,测试框架,运行测试
4. chai //assertion framework, 断言库,提供多种断言,与测试框架配合使用
5. sinon //测试辅助工具,提供 spy、stub、mock 三种测试手段,帮助捏造特定场景
6. karma-webpack //karma 中的 webpack 插件 
7. karma-mocha //karma 中的 mocha 插件
8. karma-sinon-chai //karma 中的 sinon-chai 插件
9. sinon-chai //karma 中的 chai 插件
10. karma-sourcemap-loader //karma 中的 sourcemap 插件
11. karma-phantomjs-launcher //karma 中的 phantomjs 插件
12. karma-spec-reporter //在终端输出测试结果
13. babel-plugin-istanbul //babel插件,es6代码产生instanbul覆盖率
14. karma-coverage //Karma插件,生成代码覆盖率

安装依赖

npm install --save-dev  karma  phantomjs-prebuilt mocha chai  sinon karma-webpack karma-mocha karma-sinon-chai sinon-chai karma-sourcemap-loader karma-phantomjs-launcher karma-spec-reporter babel-plugin-istanbul karma-coverage

配置karma.conf.js

  1. 在package.json文件中写入执行脚本
       "scripts": {      
          "test": "cross-env NODE_ENV=browse karma start karma.conf.js"
        }
    

    安装 npm install --save babel-polyfill
    在main.js 头部 import 'babel-polyfill'

  2. karma.conf.js
      cmd: karma init->mocha,no require.js,PhantomJS,默认目录"test/*/Spec.js"
    var path = require('path')
    module.exports = function(config) {
     config.set({
         // basePath: '',
         frameworks: ['mocha', 'sinon-chai'],
         // 测试入口文件
         files: ['./test/unit/index.js'],
         //         exclude: [    ],
          // 用webpack解析,同时显示测试文件路径
         preprocessors: {
             './test/unit/index.js': ['webpack', 'sourcemap'],
         },
          // 设置测试覆盖率输出插件
         reporters: ['spec', 'coverage'],
         // karma-coverage配置,配置测试覆盖率的输出目录及格式
         coverageReporter: {
             dir: './coverage',
             reporters: [
                 { type: 'lcov', subdir: '.' },
                 { type: 'text-summary' },
             ]
         },
         // port: 9876,
         // logLevel: config.LOG_INFO,
         // concurrency: Infinity,
         colors: true,
         autoWatch: false,
         // 设置默认打开的浏览器
         browsers: ['PhantomJS'],
         //  设置运行完成是否自动退出
         singleRun: true,
          // 是否打印webpack打包信息
         webpackMiddleware: {
             noInfo: true
         },
         // webpack 配置用来解析js文件和vue文件,如果有css请自行配置css-loader
         webpack: {
             module: {
                 loaders: [{
                         test: /\.js$/,
                         loader: 'babel-loader',
                         exclude: /node_modules/
                     },
                     {
                         test: /\.vue$/,
                         loaders: [{
                             loader: 'vue-loader',
                         }]
                     },
                 ]
             },
             resolve: {
                 //拓展名      
                 extensions: ['.js', '.vue', '.json'],
                 //简写
                 alias: {
                     'vue$': 'vue/dist/vue.esm.js',
                     '@': path.resolve(__dirname, './src'),
                     'router': path.resolve(__dirname, './src/components/router'),
                 },
                 //去哪找依赖
                 modules: [
                     path.join(__dirname, './node_modules')
                 ]
             }
         }
     })
    }
    
  3. 测试文件
     1. mkdir test test\unit && touch test/unit/index.js
     import 'babel-polyfill'
      // load 所有的测试用例文件
      const testsContext = require.context('.', true, /\.spec$/)
      testsContext.keys().forEach(testsContext)
      // load 资源文件,及src目录下的除了main.js文件的所有文件
      const srcContext = require.context('../../src', true, /^\.\/(?!(main|index)(\.js)?$)/);
      srcContext.keys().forEach(srcContext)
    

     2. .babelrc文件

    {
      "presets": [
     ["env", {
       "modules": false,
       "useBuiltIns": true,
       "targets": {
         "browsers": ["last 3 versions"]
       }
     }]
      ],
      "env": {
     "browse": {
       "plugins": ["istanbul"]
     }
      }
    }
    

     3. 编写并运行测试
      touch src/util.js

      export function util1 (name) {
        if (name) {
          return name
        }
        return 'hello'
      }
    

      touch test/unit/utils.spec.js 测试用例

      import { util1 } from '../../src/util'
      describe('utils.js', () => {
          it('should return name', () => {
            const name = util1('teapot')
            expect(name).to.equal('teapot')
          })
      })
    

    运行:   npm test
    测试覆盖率: coverage/index.html

  4. 编写测试用例 touch test/unit/vuetest.spec.js
        import Vue from 'vue'
      import MainApp from '../../src/App.vue'
      describe('MainApp', () => {
        it('has a created hook', () => {
          expect(typeof MainApp.created).to.equal('function')
        })
        it('sets the correct default data', () => {
          const defaultData = MainApp.data()
          expect(defaultData.msg).to.equal('Welcome')
        })
        it('sets new text when created', () => {
          const vm = new Vue(MainApp).$mount()
          expect(vm.msg).to.equal('Bye Spec')
        })
        it('renders the correct text', () => {
          const Ctor = Vue.extend(MainApp)
          const vm = new Ctor().$mount()
          expect(vm.$el.textContent).to.equal('Bye Spec')
        })
      })
    

集成css组件库 iview

  1. 安装依赖 npm install iview --save
  2. main.js
    import iView from 'iview';
    import 'iview/dist/styles/iview.css';    // 使用 CSS
    Vue.use(iView);
    
  3. 按需引用是直接引用的组件库源代码,需要借助 babel 进行编译,以 webpack 为例:
    module: {
     rules: [
         { test: /iview.src.*?js$/, loader: 'babel-loader' },
         { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }
     ]
    }
    
  4. 按需引用 import Checkbox from 'iview/src/components/checkbox';
    新建 touch src/main.vue
    <template>
        <div class="layout" :class="{'layout-hide-text': spanLeft < 5}">
            <Row type="flex">
                <Col :span="spanLeft" class="layout-menu-left">
                <Affix>
                    <Menu active-name="1-2" theme="dark" width="auto" :open-names="['1']">
                        <div class="layout-logo-left"></div>
                        <Submenu name="1">
                            <template slot="title">
                                <Icon type="ios-navigate" :size="iconSize"></Icon>
                                <span class="layout-text">导航一</span>
                            </template>
                            <MenuItem name="1-1">选项 1</MenuItem>
                            <MenuItem name="1-2">选项 2</MenuItem>
                            <MenuItem name="1-3">选项 3</MenuItem>
                        </Submenu>
                        <Submenu name="2">
                            <template slot="title">
                                <Icon type="ios-keypad" :size="iconSize"></Icon>
                                <span class="layout-text">导航二</span>
                            </template>
                            <MenuItem name="2-1">选项 1</MenuItem>
                            <MenuItem name="2-2">选项 2</MenuItem>
                        </Submenu>
                        <Submenu name="3">
                            <template slot="title">
                                <Icon type="ios-analytics" :size="iconSize"></Icon>
                                <span class="layout-text">导航三</span>
                            </template>
                            <MenuItem name="3-1">选项 1</MenuItem>
                            <MenuItem name="3-2">选项 2</MenuItem>
                        </Submenu>
                    </Menu>
                </Affix>
                </Col>
                <Col :span="spanRight">
                <Affix>
                    <div class="layout-header">
                        <Button type="text" @click="toggleClick">
                            <Icon type="navicon" size="32"></Icon>
                        </Button>
                        <div class="layout-ceiling-main">
                            <a href="#">注册登录</a> |
                            <a href="#">帮助中心</a> |
                            <a href="#">安全中心</a> |
                            <a href="#">服务大厅</a>
                        </div>
                    </div>
                    <div class="layout-breadcrumb">
                        <Breadcrumb>
                            <BreadcrumbItem href="#">首页</BreadcrumbItem>
                            <BreadcrumbItem href="#">应用中心</BreadcrumbItem>
                            <BreadcrumbItem>某应用</BreadcrumbItem>
                        </Breadcrumb>
                    </div>
                </Affix>
                <div class="layout-content">
                    <div class="layout-content-main">内容区域</div>
                    <BackTop></BackTop>
                </div>
                <div class="layout-copy">
                    2011-2016 &copy; TalkingData
                </div>
                </Col>
            </Row>
        </div>
    </template>
    <script>
    export default {
        data() {
            return {
                spanLeft: 5,
                spanRight: 19
            }
        },
        computed: {
            iconSize() {
                return this.spanLeft === 5 ? 14 : 24;
            }
        },
        methods: {
            toggleClick() {
                if (this.spanLeft === 5) {
                    this.spanLeft = 2;
                    this.spanRight = 22;
                } else {
                    this.spanLeft = 5;
                    this.spanRight = 19;
                }
            }
        }
    }
    </script>
    <style scoped>
    .layout {
        border: 1px solid #d7dde4;
        background: #f5f7f9;
        position: relative;
        border-radius: 4px;
        overflow: hidden;
    }
       
    .layout-breadcrumb {
        padding: 10px 15px 0;
    }
       
    .layout-content {
        /* max-height: 600px;*/
        min-height: 200px;
        margin: 15px;
        overflow-x: auto;
        background: #fff;
        border-radius: 4px;
    }
       
    .layout-content-main {
        padding: 10px;
    }
       
    .layout-menu-left {
        background: #464c5b;
    }
       
    .layout-logo-left {
        width: 90%;
        height: 30px;
        background: #5b6270;
        border-radius: 3px;
        margin: 15px auto;
    }
       
    .layout-ceiling-main a {
        color: #9ba7b5;
    }
       
    .layout-hide-text .layout-text {
        display: none;
    }
       
    .ivu-col {
        transition: width .2s ease-in-out;
    }
       
    .layout-logo {
        width: 100px;
        height: 30px;
        background: #5b6270;
        border-radius: 3px;
        float: left;
        position: relative;
        top: 15px;
        left: 20px;
    }
       
    .layout-header {
        height: 60px;
        background: #fff;
        box-shadow: 0 1px 1px rgba(0, 0, 0, .1);
    }
       
    .layout-copy {
        text-align: center;
        padding: 10px 0 20px;
        color: #9ea7b4;
    }
       
    .layout-ceiling {
        background: #464c5b;
        padding: 10px 0;
        overflow: hidden;
    }
       
    .layout-ceiling-main {
        float: right;
        margin: 15px;
    }
    </style>
    

    修改main.js

    import App from './main.vue';
    
  5. 常用组件 以下组件,在非 template/render 模式下,需要加前缀 i-:
    Button: i-button
    Col: i-col
    Table: i-table
    Input: i-input
    Form: i-form
    Menu: i-menu
    Select: i-select
    Option: i-option
    Progress: i-progress
    以下组件,在所有模式下,必须加前缀 i-,除非使用 iview-loader:
    Switch: i-switch
    Circle: i-circle
    
  6. 国际化,兼容 vue-i18n main.js
    import VueI18n from 'vue-i18n';
    Vue.use(VueI18n);
    import iView from 'iview';
    import zhLocale from 'iview/dist/locale/zh-CN';
    import enLocale from 'iview/dist/locale/en-US';
    //Vue.use(iView, { zhLocale });
    Vue.use(iView);
    Vue.config.lang = 'zh-CN';
    Vue.locale('zh-CN', zhLocale);
    Vue.locale('en-US', enLocale);
    
  7. 定制主题
    1. 变量覆盖(推荐) # 新建目录(如 my-theme)/index.less
      @import '~iview/src/styles/index.less';
      // 下面是要覆盖的变量:
      @primary-color: #8c0776;
      

      main.js

      import '../my-theme/index.less';
      

      2.安装工具修改 安装工具: npm install iview-theme -g
      新建目录,初始化主题: iview-theme init my-theme
      最后编辑 my-theme/custom.less 文件
      编译:cd my-theme&&iview-theme build -o dist/
      在 main.js :import '../my-theme/dist/iview.css';

  8. iView Loader 统一 iView 标签书写规范
    所有标签都可以使用首字母大写的形式,包括 Vue 限制的两个标签 Switch 和 Circle。 通过 loader 选项配置,可以开启所有标签前缀的写法,比如 i-date-picker。
    使用方法
    安装依赖 npm install iview-loader --save-dev
    module: {
     rules: [  {
                 test: /\.vue$/,
                 use: [{
                         loader: 'vue-loader',
                         options: {
                             loaders: {
                                 'scss': 'vue-style-loader!css-loader!sass-loader',
                                 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'
                             }
                         }
                     },
                     {
                         loader: 'iview-loader',
                         options: {
                             prefix: true // <Switch> 和 <Circle>,设为 true 后, <i-row>、<i-select>
                         }
                     }
                 ]
             }
     ]
    }
    

背靠大树好乘凉

iviewui
iviewAdmin
elementui
elementuiAdmin
vueAdmin


Tags: