基于vue-cli搭建vue2.0项目

准备

  • node & npm

    1
    $ brew install node
  • 检查 node & npm

    1
    2
    $ node -v
    $ npm -v
  • Vue-cli

    1
    $ npm install -g vue-cli

初始化

1
$ vue init <template-name> <project-name>

举例

1
vue init webpack vue-step-by-step

根据提示依次输入相关信息↓

最后出现finished安装完成↓

在终端中运行↓

1
2
cd vue-step-by-step
npm run dev

即可查看初始化完成的效果

添加依赖

项目初始化完成后添加项目常用依赖包

1
2
npm install --save vuex axios qs
npm install --save-dev node-sass sass-loader pug pug-loader

包含vuexaxiosqssasspug等,其他依赖包根据项目需求自己选择
vue-router在脚手架init的时候会提示是否选择安装

完善项目结构

添加views文件夹

src 下添加 views 文件夹主要存放页面级的vue组件
src 下的 components 文件夹主要用于存放通用的组件

在views文件夹中创建Home.vue作为主页

删除App.vue中无用的内容,只保留router-view

1
2
3
4
5
<template>
<div id="app">
<router-view/>
</div>
</template>

如果是移动端项目用 rem 作为单位,可以在src/main.js中添加如下代码做自适应 ↓

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(function (win, doc) {
if (!win.addEventListener) return;
var html = document.documentElement;
function setFont() {
var html = document.documentElement;
var k = 750;
html.style.fontSize = html.clientWidth / k * 100 + "px";
}
setFont();
setTimeout(function () {
setFont();
}, 300);
doc.addEventListener('DOMContentLoaded', setFont, false);
win.addEventListener('resize', setFont, false);
win.addEventListener('load', setFont, false);
})(window, document);

调整router配置

更多路由相关使用方法请访问:https://router.vuejs.org/zh-cn/

目录结构↓

1
2
3
4
5
6
router
├── index.js # 我们组装模块并导出 store 的地方
└── modules
├── home.js # 首页模块
├── cart.js # 购物车模块
└── products.js # 产品模块

修改路由主文件router/index.js

使用require.context实现路由去中心化

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
29
30
31
32
33
34
35
36
37
38
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
export default new Router({
base: '/', // 应用的基路径
mode: 'hash', // "hash" (URL hash 模式) | "history"(HTML5 History 模式) | "abstract" (Node.js 环境)
scrollBehavior(to, from, savedPosition) {
// 路由切换的滚动行为,只在 HTML5 history 模式下可用
if (savedPosition) {
return savedPosition;
} else {
return { x: 0, y: 0 };
}
},
beforeEach(to, from, next) {
// 全局路由切换前执行
// 是否有用户信息,并且用户ID是否存在
// if (window.localStorage.getItem("loginInfo") && JSON.stringify(window.localStorage.getItem("loginInfo")).userId) {
// next({path: '/login'})//重定向到登录页面
// } else {
// next()//正常跳转
// }
},
routes: (r => {
// 去中心化
// console.log('r', r); // __webpack_require__
let sourceMap = [];
let res = r.keys().map(key => {
let rKey = r(key);
sourceMap.push(...rKey.default);
// console.log('key', key, rKey); // ./modules/home/route.js // {default: Array(3), __esModule: true}
return rKey;
});
return sourceMap;
})(require.context('./', true, /^\.\/modules\/\w+\.js$/))
});

在 router 文件夹下添加 modules 文件夹

在 modules 文件夹下添加 home.js ,这个 home.js 对应首页业务模块,首页相关的路由页面都可以写到 home.js 文件里。

如果以后添加其他业务模块,只需要在 modules 文件夹添加相对应的业务模块文件,并在其中添加业务相关的路由页面。这样所有不同业务线的开发人员就可以互不干扰 ↓

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Home from '../../views/Home';
const routes = [
{
path: '/',
name: 'index',
redirect: '/home'
},
{
path: '/home',
name: 'home',
component: Home
}
];
export default routes;

对于不需要即时加载的非一级页面可以使用异步路由组件

1
2
3
4
5
6
7
8
9
10
11
12
// region 异步组件 - 路由地址demo
// ES 提案的 import(推荐)
{ name: 'index', path: '/', component: () => import('../views/index')},
// ES 提案的 import,带分组,指定webpackChunkName,相同的name打包到一个js文件
{ name: 'index', path: '/', component: () => import(webpackChunkName:'viewsIndex','../views/index')},
// Webpack 风格的异步组件
{ name: 'index', path: '/', component: resolve => require.ensure(['views/Foo.vue'], () => resolve(require('views/Foo.vue')))},
// Webpack 风格的异步组件,带分组
{ name: 'index', path: '/', component: resolve => require.ensure([], () => resolve(require('views/index.vue')), 'group-index')},
// AMD 风格的异步组件
{ name: 'index', path: '/', component: resolve => require(['views/index.vue'], resolve)},
// endregion

添加store文件夹

src 下的 store 文件夹主要是存放 vuex 相关信息的
更多vuex相关使用方法请访问:https://vuex.vuejs.org/zh-cn/

在 store 文件夹下创建目录结构↓

1
2
3
4
5
6
7
8
9
10
store
├── index.js # 我们组装模块并导出 store 的地方
├── getters.js # 根级别的 getter
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
├── mutation-types.js # 定义链接 action 和 mutation 的方法名常量
└── modules
├── base.js # 首页模块
├── cart.js # 购物车模块
└── products.js # 产品模块

下面开始改造store文件夹↓

  1. mutation-types.js中添加一个常量

    1
    2
    3
    export const BASE = {
    SET_USER_INFO: 'SET_USER_INFO'
    }
  2. 修改modules/base.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
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    import Vue from 'vue';
    import { base } from '../mutation-types';
    import axios from 'axios';
    import qs from 'qs';
    const state = {
    version: '',
    token: null,
    user: {
    userID: '',
    userName: '',
    name: '',
    tel: '',
    email: '',
    head: ''
    }
    };
    const getters = {
    versionGetter(state, getters) {
    return state.version;
    }
    };
    const mutations = {
    [BASE.SET_USER_INFO](state, userInfo) {
    userInfo.userID && (state.user.userID = userInfo.userID);
    userInfo.USERNAME && (state.user.userName = userInfo.USERNAME);
    userInfo.NAME && (state.user.name = userInfo.NAME);
    userInfo.TEL && (state.user.tel = userInfo.TEL);
    userInfo.EMAIL && (state.user.email = userInfo.EMAIL);
    userInfo.HEAD && (state.user.head = userInfo.HEAD);
    }
    };
    const actions = {
    async login({ commit, dispatch, state }, { userName, password }) {
    let userInfo = await axios.post('/api/login', qs.stringify({ userName, password }));
    commit(BASE.SET_USER_INFO, userInfo);
    }
    };
    export default {
    state,
    mutations,
    actions,
    getters
    };
  3. 修改vuex主文件index.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
    25
    26
    27
    28
    29
    30
    import Vue from 'vue';
    import Vuex from 'vuex';
    import getters from './getters';
    import actions from './actions';
    import mutations from './mutations';
    import base from './modules/base';
    import cart from './modules/cart';
    import products from './modules/products';
    // import createLogger from 'vuex/dist/logger' //vuex内置的Logger日志插件
    const debug = process.env.NODE_ENV !== 'production'; // 发布品种时需要用 Webpack 的 DefinePlugin 来转换 process.env.NODE_ENV !== 'production' 的值为 false
    Vue.use(Vuex);
    const state = {};
    export default new Vuex.Store({
    state,
    getters,
    mutations,
    actions,
    modules: {
    base,
    cart,
    products
    },
    strict: debug // 开发阶段使用
    // plugins: debug ? [createLogger()] : []//vuex插件
    });

添加mixins文件夹

目录结构↓

1
2
mixins
├── index.js # 全局mixin

添加filters文件夹

目录结构↓

1
2
filters
├── index.js # 全局过滤器

添加utils文件夹

目录结构↓

1
2
3
4
utils
├── fetch.js # axios
├── filters.js # 全局filter
└── mixin.js # 全局mixin

src/main.js中添加全局引用↓

1
2
3
4
5
6
7
8
9
10
import * as filters from './utils/filters';
import fetch from './utils/fetch';
/* 全局注册fetch */
Vue.prototype.$fetch = fetch;
/* 注册全局过滤器 */
Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key]);
});

封装axios

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import Vue from 'vue';
import router from '../router';
import axios from 'axios';
import qs from 'qs';
import Toast from '../components/toast';
// #region config
// 每页条数
export const ROW = 10;
// 加载最小时间
export const MINI_TIME = 300;
// 超时时间(超时时间)
export const TIME_OUT_MAX = 8000;
// 环境value
export const _env = process.env.NODE_ENV;
// 请求组(判断当前请求数)
export const _requests = [];
// #endregion
// #region 实例化axios
const _instance = axios.create({
timeout: TIME_OUT_MAX
});
// #endregion
// region request统一处理操作
_instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
// POST传参序列化
_instance.interceptors.request.use(
config => {
if (config.method === 'post') {
config.data = qs.stringify(config.data);
}
return config;
},
error => {
Toast('错误的传参');
return Promise.reject(error);
}
);
// endregion
// region response统一处理操作
_instance.interceptors.response.use(
res => {
let _message = null;
if (res.status !== 200) {
console.error(res);
switch (res.status) {
case 404:
_message = '404,错误请求';
break;
case 401:
router.push({ path: '/login', query: { redirect: router.currentRoute.fullPath } });
_message = '未授权';
break;
case 403:
_message = '禁止访问';
break;
case 408:
_message = '请求超时';
break;
case 500:
_message = '服务器内部错误';
break;
case 501:
_message = '功能未实现';
break;
case 503:
_message = '服务不可用';
break;
case 504:
_message = '网关错误';
break;
default:
_message = '未知错误';
}
Toast(_message);
return Promise.reject(_message);
} else {
return res.data
}
},
error => {
console.error(error);
Toast(error || '服务器繁忙,请稍后重试');
return Promise.reject(error || '服务器繁忙,请稍后重试');
}
);
// endregion
export default _instance;

config配置

build 生成的文件路径使用相对路径

修改config/index.js文件中build节点的assetsPublicPath

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
dev: {
// ...
},
build: {
// ...
assetsPublicPath: './',
// ...
}
};

开发的的时候需要使用代理(proxy)跨域访问服务器接口

修改config/index.js文件中dev节点的proxyTable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = {
dev: {
// ...
proxyTable: {
'/api': {
target: 'http://123.57.89.97:8081',
changeOrigin: true,
// pathRewrite: {
// '^/api': '/api'
// }
}
}
// ...
}
};

分离线上环境和本地环境的配置信息

修改config/dev.env.jsconfig/prod.env.js,为不同的环境配置文件添加与NODE_ENV同级的环境变量

1
2
3
4
module.exports = {
NODE_ENV: '"development"',
API: '"http://123.57.89.97:8081"'
};

通用样式(SCSS)

目录结构↓

1
2
3
4
5
6
7
assets
└── scss
├── base.scss # 基础样式
├── common.scss # 通用样式
├── fun.scss # 函数
├── mixin.scss # 混合
└── variable.js # 变量

base.scss

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@CHARSET "utf-8";
@import "variable";
@import "fun";
@import "mixin";
@import "common";
/*基础样式*/
html, body, #app {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
width: 100%;
height: 100%;
font-family: Arial, "Microsoft YaHei", "微软雅黑", Verdana, sans-serif;
}
ul, li {
padding: 0;
margin: 0;
list-style: none;
}
* > img {
max-width: 100%;
max-height: 100%;
}
button {
position: relative;
display: block;
margin-left: auto;
margin-right: auto;
padding-left: 14px;
padding-right: 14px;
box-sizing: border-box;
font-size: 18px;
text-align: center;
text-decoration: none;
line-height: 2.55555556;
border-radius: 5px;
-webkit-tap-highlight-color: transparent;
overflow: hidden;
color: #000000;
background-color: #F8F8F8;
&::after {
content: " ";
width: 200%;
height: 200%;
position: absolute;
top: 0;
left: 0;
border: 1px solid rgba(0, 0, 0, 0.2);
-webkit-transform: scale(0.5);
transform: scale(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
box-sizing: border-box;
border-radius: 10px;
}
}
//页面切换动画
.slide {
&-enter, &-leave-to {
-webkit-transform: translate(100%, 0);
transform: translate(100%, 0);
}
&-enter-active, &-leave-active {
transition: all .5s cubic-bezier(.55, 0, .1, 1);
}
&-enter-to, &-leave {
-webkit-transform: translate(0, 0);
transform: translate(0, 0);
}
}

common.scss

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
29
30
31
32
33
@charset "UTF-8";
@import "fun";
@import "mixin";
@import "variable";
/*通用样式*/
* {
box-sizing: border-box;
}
.clear {
display: block !important;
clear: both !important;
float: none !important;
margin: 0 !important;
padding: 0 !important;
height: 0;
line-height: 0;
font-size: 0;
overflow: hidden;
}
.clearfix {
zoom: 1;
}
.clearfix:after {
content: "";
display: block;
clear: both;
height: 0;
}

fun.scss

1
2
3
4
5
6
7
@charset "UTF-8";
/*函数*/
@function rem($pixels) {
@return $pixels / 100px * 1rem;
}

mixin.scss

1
2
3
4
5
6
7
8
9
10
11
@charset "UTF-8";
/*混合*/
@mixin fullpage {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
}

variable.scss

1
2
3
4
5
6
7
@charset "UTF-8";
@import "fun";
/*变量*/
$headerHeight: rem(50px);

demo地址https://github.com/MrLeo/wedive

查缺补漏

我用了 axios , 为什么 IE 浏览器不识别(IE9+)

那是因为 IE 整个家族都不支持 promise, 解决方案:

1
npm install es6-promise

1
2
3
// 在 main.js 引入即可
// ES6的polyfill
require("es6-promise").polyfill();
坚持原创技术分享,您的支持将鼓励我继续创作!