搭建高效的前端工作流 -- 附带实战项目

安装CLI

1
$ sudo npm install -g @vue/cli

快速构建原型

1
$ sudo npm install -g @vue/cli-service-global

创建一个文件夹,随意命名:instant-prototyping,在此文件夹下创建App.vue文件,文件内容如下

1
2
3
<template>
<h1>Hello!</h1>
</template>

而后在该文件目录下,执行:

1
vue serve

编译后可通过浏览器地址:http://localhost:8080/预览。
也可以作为生产包部署。使用:

1
$ vue build

将生成一个dist的包。

创建一个项目

1
$ vue create hello-world

弹出一个列表。默认选中(babel, eslint),回车即可,后期组件可根据需要添加。

当然也可以使用GUI通过浏览器的图形界面安装,比如自选插件:Router(ESLint with error prevention only)+Unit Testing(Jest),可以保存一个预置信息,以便下次构建其他项目时直接启用。内建了图形化操作界面:比如添加vue插件、管理项目依赖、进行Vue CLI和ESLint配置、执行运行、打包、代码风格检查等任务。

使用GUI

命令行根目录下执行:

1
$ vue ui

启动GUI界面,点击Create a new project,选择项目安装目录,进行初始化配置,根据步骤一步一步提交即可,等待几分钟即可创建完成。

而后会提示你,启动开发服务器。

1
2
$ cd [ project root folder]
$ npm run serve

开发

静态资源管理

两种方式:

  • 在JavaScript导入或templates/CSS导入相关路径。这种引用被webpack管理
  • 放在public目录和引用绝对路径。这些资源仅仅被复制而不通过webpack管理。

URL翻译规则

  • 绝对路径会保持原样(eg:/images/foo.png);
  • .开始作为一个模块请求解析为基于文件夹结构的文件系统;
  • ~开始,在它之后的任何内容被解释为模块请求。这意味着你能引用node modules内部的资源;
  • 以@开始也被推荐为一个模块请求。这是有用的因为Vue CLI默认的别名@<projectRoot>/src.(仅在templates)。

构建现代、灵活且功能强大的前端工作流

快捷流畅的前端工作站需要几个要素,简洁快速的界面搭建,我们使用一个库Element,管理组件间关系的路由,使用Vue-Router,用于与后端服务器进行数据交换的工具,使用Axios。我们一个一个看。

Vue-Router

首先安装Vue-Router

1
$ vue add router

Vue本身使用组件组合应用。vue + router,我们需要做的就是映射我们的组件到路由,使路由知道渲染他们。如果你在使用CLI创建项目的时候选择安装vue-router,那么项目会为我们生成router的基本使用示例router.jsrouter.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
// router.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'

Vue.use(Router)

export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
}
]
})
  1. 首先导入VueRouter,然和导入需要路由的组件Home.vue
  2. 调用Vue.use(Router)使用路由
  3. 创建路由实例export default new Router({...})
  4. 在路由实例内部定义路由routes:[ {path: '/', name: 'home', component},...]

把该路由导入程序入口文件,并挂载到根实例,保证全局响应router

1
2
3
4
5
6
7
8
9
10
import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

new Vue({
router,
render: h => h(App)
}).$mount('#app')

如上我们导入router.js,使用router选项注入router给Vue的根实例。

1
2
3
4
5
6
7
8
9
10
// App.vue
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>

在HTML中,使用router-link导航,使用to指定链接的页面,<router-link>将被渲染为<a>标签。<router-view>路由的出口,与路由匹配的组件内容将被渲染到这里。

在任何组件内部,我们可以通过两个方式访问路由:this.$routerthis.$route

Element

当然,接下来为了演示路由的其他细节功能,我就结合一个基于Vue的UI库element,由于我们使用@vue/cli version 3.x搭建的项目,elementvue-cli@3提供了一个插件Element plugin,可以快速构建一个基本的element工程,进入项目根目录,在Terminal键入如下命令:

1
$ vue add element

当然这期间会跟随构建过程做一些基本的配置:

  1. 选择导入Element的方式:全局导入即可Fully import(Default)
  2. 你希望重写Element的SCSS变量吗,填写Y(yes)后直接回车;
  3. 选择你想加载的本地化语言,直接回车默认中文(zh-CN(Default)即可。

OK,安装完成后我们看到src下多了一个element-variables.scss文件,这里面引入了element的icon和字体。

1
2
3
4
5
6
7
8
9
10
11
12
// eleemnt-variables.scss
/*
Write your variables here. All available variables can be
found in element-ui/packages/theme-chalk/src/common/var.scss.
For example, to overwrite the theme color:
*/
$--color-primary: teal;

/* icon font path, required */
$--font-path: '~element-ui/lib/theme-chalk/fonts';

@import "~element-ui/packages/theme-chalk/src/index";

src/plugins/element.js,对elementscss文件进行引入并调用:

1
2
3
4
5
6
// element.js
import Vue from 'vue'
import Element from 'element-ui'
import '../element-variables.scss'

Vue.use(Element)

而后在应用的入口程序main.js导入element.js文件:

1
import './plugins/element.js'

项目实战演示

我们会定义一个基本项目的雏形,包含登录页面,输入用户名、密码后展示主页,而后采用侧边栏分为两个栏目切换,切换完成后退出页面的基本流程,ok,我们开始:

首先,我们创建我们用到的三个页面:一个登录页Login.vue,一个主页面Home.vue,和一个About.vue,

因为我们要使用页面导航,所有我们需要在Router里建立页面对应的路由:

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
// router.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'

Vue.use(Router)

export default new Router({
routes: [
{
path:'/',
redirect: '/login',
},
{
path: '/login',
name: 'login',
component: () => import('./views/Login.vue')
},
{
path: '/',
name: 'home',
component: Home,
children:[
{
path: '/helloworld',
name: 'helloworld',
component:() => import('./components/HelloWorld.vue')

},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
}
]
},

]
})

以上我们添加了一级页面登录页Login.vue和主页Home.vue,在Home.vue里面我们添加了用于分栏的两个页面HelloWord.vueAbout.vue

接下来开始写项目代码,写一个简单的登录页面,代码如下:

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
<template>
<div class="login-wrap">
<div class="ms-title">后台管理系统</div>
<div class="ms-login">
<el-form :model="ruleForm">
<el-form-item>
<el-input v-model="ruleForm.username"></el-input>
</el-form-item>
<el-form-item>
<el-input type="password" v-model="ruleForm.password"></el-input>
</el-form-item>
<div class="login-btn">
<el-button type="primary" @click="submitForm()">登录</el-button>
</div>
</el-form>
</div>
</div>
</template>

<script>
export default {
name: "Login",
data() {
return {
ruleForm: {
username: '',
password: ''
}
}
},
methods: {
submitForm() {
if (this.ruleForm.password == '123456' && this.ruleForm.username == 'admin') {
sessionStorage.setItem("username", "admin");
this.$router.push('/helloworld');
this.$message({
message: "登录成功",
type: 'success'
})
} else {
this.$message({
message: "用户名或密码不正确",
type: 'error'
})
}
}
}

}
</script>

<style scoped>

.login-wrap {
position: relative;
width: 100%;
height: 100%;
}

.ms-title {
position: absolute;
top: 50%;
width: 100%;
margin-top: -230px;
text-align: center;
font-size: 30px;
color: #fff;
}

.ms-login {
position: absolute;
left: 50%;
top: 50%;
width: 300px;
height: 160px;
margin: -150px 0 0 -190px;
padding: 40px;
border-radius: 5px;
background: #fff;
}

.login-btn {
text-align: center;
}

.login-btn button {
width: 100%;
height: 36px;
}
</style>

在登录页,我们拟定了一个用户名和密码,输入正确即可登录成功,进入主页,展示主页的一个默认分栏内容HelloWorld页。

注意:这里面有一个class=”login-wrap”,这个我们定义了一个css样式,因为我们的系统样式最好统一,为了方便统一管理,我们定义一个全局样式,在assets/css/theme-green目录下(后两个文件夹需要自己手动创建)创建一个css全局样式文件:color-green.css,定义全局样式:

1
2
3
4
5
6
.header{
background-color: #00d1b2;
}
.login-wrap{
background: rgba(56, 157, 170, 0.82);
}

.login-wrap就是我们登录页的背景颜色,.header一会用于我们主页的顶部栏–浅绿色,一会看到。

我们在assets/css/在创建一个main.css文件,用于创建我们全局的尺寸,字体,内容等样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
*{margin:0;padding:0;}
html,body,#app,.wrapper{
width:100%;
height:100%;
overflow: hidden;
}
body{
font-family:"Helvetica Neue",Helvetica, "microsoft yahei", arial, STHeiTi, sans-serif;
}
a{text-decoration: none}
.content{
background: none repeat scroll 0 0 #fff;
position: absolute;
left: 250px;
right: 0;
top: 70px;
bottom:0;
width: auto;
padding:40px;
box-sizing: border-box;
overflow-y: scroll;
}

main.css里面我们定义了页面的尺寸,body体的文字,定义了标准的文本a{…},为none即无修饰,以及content的样式等信息。

而后,我们将这两个css文件导入到App.vue进行全局引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// App.vue
<template>
<div id="app">
<router-view/>
</div>
</template>

<script>
export default {
name: 'app',
}
</script>

<style>
@import "assets/css/main.css";
@import "assets/css/theme-green/color-green.css";
</style>

我们已经写好的登录页面
Alt text

当然为了更好的理解,我们先来看一下主页的基本结构:

Alt text
Alt text

这样,一目了然,简单明了,我们点击登录按钮进来,首先映入眼帘的是主页的欢迎分栏页内容,主页上包含了顶部的header区域,左边的管理列表侧边栏,和中间的内容区域欢迎分栏(默认的首页–HelloWorld.vue)。点击关于分栏进入该页面,当然目前只有一句话内容:This is an about page。点击admin用户名会弹出带退出按钮下拉菜单,点击退出按钮回到登录页面。

为了让结构更为清晰,我们使用封装思想,在src/components/创建Header.vueSidebar.vue用于书写主页页头和侧边栏。

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
// Header.vue
<template>
<div class="header">
<div class="logo">后台管理系统</div>
<div class="user-info">
<el-dropdown trigger="click" @command="handleCommand">
<span class="el-dropdown-link">
<img class="user-logo" src="../assets/logo.png">
{{ username }}
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="loginout">退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>

<script>
export default {
name: "Header",
data() {
return {
name: 'default',
}
},
computed: {
username() {
let username = sessionStorage.getItem('username')
return username ? username: this.name
}
},
methods: {
handleCommand(command) {
if (command == 'loginout') {
sessionStorage.removeItem('username')
this.$router.push('/login')
}
}
}
}
</script>

<style scoped>
.header {
position: relative;
box-sizing: border-box;
width: 100%;
height: 70px;
font-size: 22px;
line-height: 70px;
color: #fff;
}
.logo{
float: left;
width:250px;
text-align: center;
}
.user-info {
float: right;
padding-right: 50px;
font-size: 16px;
color: #fff;
}
.user-info .el-dropdown-link{
position: relative;
display: inline-block;
padding-left: 50px;
color: #fff;
cursor: pointer;
vertical-align: middle;
}
.user-info .user-logo{
position: absolute;
left:0;
top:15px;
width:40px;
height:40px;
border-radius: 50%;
}
</style>

如上这里面可以显示用户名称和icon,响应退出事件。

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
// Sidebar.vue
<template>
<div class= "sidebar">
<el-row class="tac">
<el-col>
<el-menu default-active="helloworld"
class="el-menu-vertical-demo"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b" router>
<el-submenu index="1" style="width: 200px">
<template slot="title">
<span>管理列表</span>
</template>
<el-menu-item index="helloworld">欢迎</el-menu-item>
<el-menu-item index="about">关于</el-menu-item>
</el-submenu>
</el-menu>
</el-col>
</el-row>
</div>
</template>

<script>
export default {
name: "Sidebar",

}
</script>

<style scoped>
.sidebar{
display: block;
position: absolute;
width: 200px;
left: 0;
top: 70px;
bottom:0;
background-color: rgb(50, 86, 87);
}
</style>

Sidebar.vue主要实现侧边栏的分栏内容设置。
而后,在Home.vue页面引入这两个组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Home.vue
<template>
<div class="warpper">
<my-header></my-header>
<my-sidebar></my-sidebar>
<div class="content">
<transition name="move" mode="out-in"><router-view></router-view></transition>
</div>
</div>
</template>

<script>
// @ is an alias to /src
import MyHeader from '@/components/Header.vue'
import MySidebar from '@/components/Sidebar.vue'

export default {
name: 'home',
components: {
MyHeader,
MySidebar
}
}
</script>

至此,我们完成了后台管理界面基本雏形的搭建,项目目录结构如下:
Alt text

总结一下:
我们使用Vue CLI3搭建了项目,引入了Vue Router做页面路由,引入Element做界面组件,并完成了一个从登录-展示页面结构-退出的完整页面流程。通过这些操作我们学会了如何创建项目,以及路由和Element的基本用法,一个简单高效的前端工作流搭建完成。

其他

自定义主题

我们可以在主题预览,下载完整的主题包,而后导入主题,比如我们将下载的主题命名为element-green,放到项目的assets文件夹下面。直接在main.js导入即可:import './assets/element-green/index.css',默认主题可以注释掉了。

网络请求

很多时候我们都需要请求后台数据来调试我们的界面,Vue官方推荐使用axios

安装axios

1
npm install axios

这个请求会在很多页面使用到,所以我们在程序的入口将它代理给Vue,打开main.js文件

1
2
3
import Axios from 'axios'

Vue.prototype.$http = Axios;

这样的我们在其他组件页面直接使用this.$http.get/this.$http.post的形式即可访问API,无需再导入头文件。

使用参考:
vue.js中使用Axios
使用vue-cli+axios配置代理进行跨域访问数据

跨域问题解决

由于我们的开发服务器和我们请求的API可能不在一台机器上,所以如果你直接使用axios请求API可能会出现跨域问题,即:请求被浏览器拦截。

我们需要在vue.config.js文件下作如下代理访问配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
lintOnSave: false,
devServer: {
port: 8080,
proxy: {
'/v1': {
target: 'https://api.coindesk.com',
ws: true,
changeOrigin: true
}
}
}
}

8080是我们要监听的端口,target后面为我们要请求的API的host地址。我是怎么知道的这个,搜索呗,百度:

关键词:vue axios cli跨域
Alt text

上图打开第二个链接指向这里:
Vue-cli@3版本下使用axios如何设置跨域配置?,而后这个链接提示来这里找,即官方文档有写:
Vue CLI | vue.config.js | devServer.proxy,由于官方提示的信息不全面,让参考 http-proxy-middleware,于是点进去看了看,没有合适的说明,怎么办呢,继续关键词搜索呗:

关键词:axios vue-cli@3跨域 devServer
Alt text
如上图,点开第二个链接:vue-cli3.0 axios跨域请求代理配置及端口修改,
ok,这里有配置完整的答案。在官方的代码段的基础上加一个端口号嘛。到此,解决了这个跨域问题,你看看,有时候正确的关键词搜索能力是高效解决问题的关键,因为你碰到的问题很可能别人也碰到并解决,而且分享出来了,找到即可

注意
配置完代理后要再次重启服务器配置才能生效。控制台运行npm run serve即可。

延伸阅读:
Vue CLI | Config REference: 看更多参数配置。

而后,打开Home.vue页面,在mounted()方法中,作如下请求:

1
2
3
4
mounted() {
this.$http.get('http://localhost:8080/v1/bpi/currentprice.json')
.then(response => (this.info = response))
}

如果使用Google Chrome浏览器,打开后台的network板块即可看到响应数据。

代码版本控制

  1. 创建远程仓库
    码云代码管理平台为例,点击主页+号创建私有仓库,填写仓库配置信息:
1
2
3
4
5
仓库名称:XXX
仓库介绍:XXX
选择语言:JavaScript
添加.gitignore:Node
勾掉:使用Readme文件初始化这个仓库(因为我们创建的项目中会带有Readme文件,不需要用这个)

点击创建按钮即可。

2.将远程仓库克隆到本地,我们使用一个软件名为Sourcetree(Sourcetree具体用法,自行百度)。

打开SourceTree主页,点击New...下拉菜单,选择Clone from Url,把刚刚我们创建的远程仓库克隆到本地。会弹出一个对话框:

Alt text

Source URL:到码云上,找到刚刚我们创建的仓库,点击克隆/下载按钮,在弹出的对话框中点击复制按钮复制HTTPS链接,将复制到剪切版的URL黏贴到此处即可。

Destination Path:选择一个存储到本地项目文件夹,可点击...快捷选择。

Name:会在Destination Path填充后自动生成。

点击Clone即可。

到此,本地仓库已经创建好,将刚刚创建好的项目拖入这个文件夹即可。我们以后就可以用Sourcetree进行版本控制了。

注意
我们刚刚创建项目的时候项目本身有一个文件夹HelloWorld,而刚刚我们创建本地仓库也有一个文件夹,比如名为HelloWorldGit,这样就我们的项目内容路径为HelloWorldGit/HelloWorld/entity files,这样就会导致我们的实体内容路径较深,可以把第三层的实体内容移出来。中间一层的文件夹删掉,变成HelloWorldGit/entity files

使用WebStrom进行版本管理的一些提示

我们开发工具使用的是WebStorm,我们可以将新创建的项目添加到WebStorm的版本控制下:

1
2
CVS | Git | Add (或 Alt+command+A)   添加代码为本地版本控制
⌘K 提交代码到本地仓库

使用WebStorm提交到本地仓库,WebStorm会进行代码检查。常见错误有:

  1. Warning -- Unused default export
    确定代码没有问题,可以去掉此提示,点击WebStrom程序主板右下角的小人头,搜索javasript general unused ,勾掉Unused global symbol

  2. Warning: Unterminated statement
    找到对应位置,添加中断声明,可能是添加一个分号而已。

  3. .idea文件报错:Error:Element profile is not allowed here.
    .idea文件是WebStorm自动生成的配置文件,是隐藏文件,若损坏或删除。可能导致程序的实体文件夹无法在WebStorm的工程目录中显示。忽略此错误即可。

最后在使用WebStorm Commit代码时,如果代码检查依然有误,会提示直接提交还是Review审查代码,可以Review看一遍所有的错误和警告,确认没有问题直接commit提交即可。提交到本地仓库之后,还是建议使用SourceTree推送代码到远程仓库,因为这个界面更简洁更方便友好,尤其是同时管理多个项目时。


以上实战项目的git地址:hello-world