《京东E维》基于VUE+Webpack的单页面实践

本篇文章和大家聊一聊我们近期的项目——京东E维平台,这是一个专门为运维工程师设计的工单处理系统。它所要肩负的任务是,针对公司内部所有员工在办公时遇到的有关计算机、网络、软件方面等问题,通过E维平台发起问题上报请求。工程师通过该系统可以根据上报工单的紧急程度进行抢单、解决事件单、流转事件单、退回请求广场、挂起事件单等操作,这个系统一定程度上起到了提高办公效率的作用。

在与产品和研发沟通过后,我们发现,这个项目基本上所有交互部分都放在前端这里,研发只提供接口,可以做到前后端分离。因此经过小组讨论后,决定用 Vue 去实现。基于京东 E 维平台视觉稿的统一性,且页面较少,关联性较强,加上之前在会员PLUS项目中使用 Vue + Webpack 的经验教训,对该项目我们决定尝试着用vue的单页面开发模式进行开发。

初试Vue单文件组件开发页面,我们遇到了一些问题,也总结了一些经验,以下是我们的一些总结 (下图为界面总览):

请求广场页面:

QQ截图20170824103403

我的事件页面:

QQ截图20170824103325

事件单详情页:

QQ截图20170824103336

本篇文章适合具有 Vue 和 webpack 基础的同学,里面的具体配置和vue用法并未写的过于详细。

Vue+Webpack 单页面的文档结构与构建方法

项目构建的目录结构如下:

124

先和大家介绍下我们的思路,进入项目的第一层,是src文件夹以及各种配置文件。

接下来src目录下的文件功能如下:

♦  component 用来存放功能组件

♦  css 存放所有样式文件

♦  image 用于存放图片

♦  view 存放业务上的各个单文件 vue 页面组件

♦  app.js 是入口文件

♦  app.vue 渲染最高级路由匹配到的组件用的出口 vue 文件

♦  index.html 页面模板

♦  router.js 为配置各种跳转信息的路由文件

1、首先是 package.json 配置文件,我们做了一些常规的设置如下(名称、描述、版本、作者):

"name": "jdme_ew",
"description": "jdme_ew",
"version": "1.0.0",
"author": "",

依赖包的列表如下图所示,有处理 SCSS 编译的、有处理 Es6 的、有处理 Vue 的 Ajax 请求的、有处理 CSS 兼容性问题的等等:

"devDependencies": {
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.0.0",
    "babel-loader": "^6.0.0",
    "babel-preset-env": "^1.5.1",
    "clean-webpack-plugin": "^0.1.16",
    "copy-webpack-plugin": "^4.0.1",
    "cross-env": "^3.0.0",
    "css-loader": "^0.25.0",
    "extract-text-webpack-plugin": "^2.1.2",
    "file-loader": "^0.9.0",
    "html-loader": "^0.4.5",
    "html-webpack-plugin": "^2.28.0",
    "node-sass": "^4.5.3",
    "postcss-loader": "^2.0.6",
    "sass-loader": "^6.0.6",
    "style-loader": "^0.18.2",
    "vue-loader": "^12.1.0",
    "vue-resource": "^1.3.4",
    "vue-router": "^2.6.0",
    "vue-style-loader": "^3.0.1",
    "vue-template-compiler": "^2.3.3",
    "webpack": "^2.6.1",
    "webpack-dev-server": "^2.4.5"
  }

自定义 npm 命令如下(npm run dev 在本地启动一个开发用的服务器、npm run build 在 dist 目录下构建出文件):

"scripts": {
    "clean": "rm -rf dist",
    "dev": "npm run clean && webpack-dev-server -d --open --progress",
    "build": "npm run clean && cross-env NODE_ENV=production webpack -p --hide-modules --progress"
  },

2、webpack.config.js配置文件:

webpack 近期真的很火,也有一部分原因是可以通过引用各种插件,完成很多日常项目中的功能。那么打开我们的配置文件,最基本的要素一个都不少:entry、output、module、plugins。以下为配置文件的部分代码:

webpackConfig.entry = {
  app: './src/app.js',
};

webpackConfig.output = {
  path: path.resolve(__dirname, 'dist'),
  filename:'[name].js'
};

webpackConfig.module = {
  rules: [
    {
      test: /\.css$/,
      use: ExtractTextPlugin.extract({
        fallback: "style-loader",
        use: ['css-loader','postcss-loader']
      }),
    },

webpackConfig.plugins = [
  new HtmlWebpackPlugin({
    template: './src/index.html'
  }),

Vue 单文件组件

本项目采用了 Vue 的单文件组件的方式进行开发,那就先来说说 vue.js 的单文件组件。在很多Vue项目中,我们会用 Vue.component 来封装全局组件,然后用 new Vue({ el: ‘#container ‘}) 在每个页面指定一个容器元素。这种方式在更复杂的项目中,有些缺点会变得非常明显,比如强制要求每个 component 中的命名不得重复、字符串模板(String templates) 缺乏语法高亮、不支持CSS、不能使用预编译器等等。

Vue.js的单文件组件可以解决以上问题。简单点来说,Vue单文件组件可以把 Html、CSS、JS 写在一个文件中,并生成一个以 .vue 结尾的文件,相当于把整个组件封装成一个文件,它的代码模板如下:

<template>
    <div class="common-wrapper">
        <div class="problem-desc">
            <h3>问题描述</h3>
        </div>
    </div>
</template>

<script>
import '../css/detail.scss' 
export default {
   data: function() {
        return {
            url: {
                
            },
        }
    },
    mounted: function() {
        this.getDetail();
    },
    methods:{
        getDetail(){
        }
    }
}
</script>

<style>

</style>

从上图可以看出,template 标签包裹的是 html 部分,script 标签里是当前页面的 JavaScript 代码,下边的 style 标签可以把当前页面的 CSS 写在这里,这样就解决了CSS 不能组件化的问题。

细心地同学可能能看到在上图中我们还在JS 里引了 SCSS 文件,这是根据项目需要单独引用的,后期导出时 Webpack 的 Vue-loader 会将所有引入的 SCSS 文件解析成一个 CSS 文件。如果项目没有特殊要求,CSS 可以直接写在页面里的 style 标签中。

总的来说,Vue单文件组件的作用就是,把我们原有的 htm l文件按照 Vue 的方式封装成一个 .vue 文件,在这个项目中每一个 .vue 文件可以看成是一个 html。最终实现单页面的跳转是通过路由去链接各个 .vue 文件。

Vue-router

说到路由 Vue-router,它可谓是本项目的核心组件了,整个项目的运行都离不开它。首先,让我们先来看下这个 router 是个啥东西,如下图:

125

上文说到每一个 .vue 文件是一个组件,也就是一个页面,我们把每个组件汇总到 router 中,路由设定好访问路径,将路径和组件映射好。当用户进入页面时,首页 Vue 初始化调用 router ,渲染第一个路径组件,之后根据用户操作切换页面。这大致是 Vue-router 工作的流程,需要注意的是,这里切换页面不是传统页面上超链接形式的页面跳转,而是路径的切换,也就是不同组件的切换,他们都是在同一个页面上实现的。我们可以看下项目中的实际操作:

1、定义组件

在 .vue 文件中我们可以给特定的文字、图片、按钮设置路径的跳转,方法有很多,但是在本项目中我们只用到了两种方法:

①  <router-link> 创建 a 标签定义导航链接,如下图:

<div class="mi-l">
     <router-link :to="'mydetail/'+item.eventNum+'/'+item.categoryCode"><h3>{{item.description}}</h3></router-link>
     <p>{{item.currentPosition}}</p>
</div>

使用<router-link>相当于在页面中创建了一个a标签,‘:to’后边跟着的是你要跳转的 vue 组件路径,同时还可以根据项目需要带上参数,图中显示的跳转路径为 mydetail 的组件,并向其传了两个参数。

②  router.push(…) 编程式的导航链接,如下图:

ctlResolve(){
     this.$router.push('/resolve/'+this.$route.params.eventNum);
},

router.push等同于<router-link :to=”…”>,但 router-link 是声明式的,router.push 是编程式的。从图中可看出调用 ctlResolve 方法,页面会跳转到路径为 /resolve 的组件页面,并向其传递了参数eventNum 的数据。

2、定义路由策略与路由实例创建

单从Vue文件中看跳转路径,我们不知道这个路径指代的是什么,那么我们接下来看下router.js,如下图。

import Vue from 'vue';
import VueRouter from 'vue-router';

import Square from './view/square.vue';
import Detail from './view/detail.vue';
import myDetail from './view/mydetail.vue';
import Filter from './view/filter.vue';
import Resolve from './view/resolve.vue';
import Suspend from './view/suspend.vue';
import Transfer from './view/transfer.vue';
import me from './view/me.vue';
import Return from './view/return.vue';

Vue.use(VueRouter);

const routes = [ 
  { path: '/', component: Square },
  { path: '/detail/:eventNum', component: Detail },
  { path: '/mydetail/:eventNum/:categoryCode', component: myDetail },
  { path: '/filter', component: Filter },
  { path: '/resolve/:eventNum', component: Resolve },
  { path: '/suspend/:eventNum/:categoryCode', component: Suspend },
  { path: '/transfer', component: Transfer },
  { path: '/me', component: me },
  { path: '/return/:eventNum/:categoryCode', component: Return },
];

const router = new VueRouter({routes});

export default router;

在router.js中,我们可以看出文件上半部分我们引入了 Vue-router 组件和各个 .vue 组件,并对这些组件做了定义。中间我们 use 了一下 VueRouter,使整个应用具有 router 运行的环境。文件下半部分将各个组件的路径设置了一下,并根据需要在路径上添加上参数,实现组件与路径的映射。然后再由 export default 把 router 导出,以便 Vue 初始化的时候调用。

3、挂载路由

最终我们在 app.js 中对 router 进行调用,实现路由的挂载,如下图:

import Vue from 'vue';
import App from './app.vue';
import router from './router.js';
import VueResource from 'vue-resource';

Vue.use(VueResource);
window.erpCode = document.getElementById('erpCode') && document.getElementById('erpCode').value || '';
const app = new Vue({
  router,
  render: h => h(App),
}).$mount('#app');

在最终导出的 app.js 中我们可以看到它引入了 router.js,并在实例化 Vue 时调用了 router,之后根据用户操作输入路径进行页面的切换。这样一来整个单页面的流程就基本跑通了,整个 Vue-router 组件起到了关键作用。

接口与数据问题

1、接口请求问题

关键的路由问题解决了,数据接口问题也是同样困扰着我们。按照常规,我们把页面重构好后准备接入Vue和数据接口。但是由于某种不可抗因素的影响,接口开发没完成!具体完成时间不知道!!难道我们就只能被动的等么?当然不,还好的是研发提供了接口数据结构,按照接口数据结构我们自己模拟了数据进行数据交互部分的开发。我们找了个mock数据的工具,将给定的假数据直接生成一个接口连接,在我们请求这个接口链接的时候可以直接访问到数据。可以说这个小工具节省了我们等待后端调试接口的时间,解决了前端被动开发的窘境,提高了我们的开发效率。

1505982366395

这个工具的具体操作如上图,左侧写入假数据,点击生成,右侧出现代码,点击复制链接,即可得到接口地址,然后就可以直接调用了。右上角的接口记录可以查看所有自己已存的接口数据。在获取接口的时候,我们采用下图调用方法来获取:

getDetail(){
   this.$http.post('//json.diao.li/596db329e6da184a056ce98a',{id:this.$route.params.id}).then(response => {
        this.dtlInfo = response.body;
        console.log(this.dtlInfo);
   }, response => {
        // error callback
   });
}

经过mock数据,我们基本上能跑通整个项目,但在替换研发的真实接口时,又出现了跨域问题。虽然项目最终上线是由研发那边统一部署,不会有跨域问题,但在前期开发环境下,我们本地打开研发提供接口的话,就一定会碰到跨域问题。针对这个问题,考虑到 E 维系统是移动端项目,没有低版本浏览器兼容问题,那新时代的跨域资源共享 CORS 就派了很大用场。以下是 CORS 的 chrome 插件使用截图:

http://jdc.jd.com/wp-content/uploads/2017/09/cors.mp4

在没打开 CORS 时,页面只加载了静态页面,需要js渲染的部分没有显示,控制台报错‘接口请求失败’,但当我们打开 CORS 后,页面就刷新出来了。

2、数据调用

关于数据调用,这里需要给大家一个小提醒,在我们开发时对post类型请求时会遇到数据调不通的情况,这里的 post 的 data 默认不像 jquery 是以 form data 的形式,而是 request payload 。解决起来也很简单,如下图:
this.$http.post('//rtfewm.jd.com/event/jMe/catchEvent',{eventNum:eventNum,erp:erpCode,token:'7cf48cf0495193d0440431844f43a614'},{emulateJSON:true}).then(response => {
                /*console.log(response.body.code)*/
                if(response.body.code == '01'){
                    this.isclose = true;
                }else if(response.body.code == '11'){
                    alert(response.body.msg);
                }; 
                

            }, response => {
                console.error('接口请求失败!');
            });

在vue实例中添加 headers 字段,即添加 {emulateJSON:ture} 参数,Vue-resource 都已经预设好,默认是 false,只要设置成 true 就好。

E维平台实现效果

经过一系列打怪升级工作,E 维副本我们最终还是通关了,以下是整个项目最终呈现的效果:

http://jdc.jd.com/wp-content/uploads/2017/09/ew.mp4

从上图可以看到,我们整个项目有两个部分:

1、“请求广场”,可以查看工单、点击工单可看工单详情、点击抢单按钮可以抢单,另外还有搜索工单和加载更多工单的功能。

2、是“我的事件”部分,在这里可以查看你抢到的订单,并对订单进行解决、退回请求广场、挂起事件单等操作,如果有权限的工程师还可以流转工单。

从图中还可以看到我们在地址栏中更改路径可以切换到那个页面去,实现了组件的切换。整个项目运用vue单页面开发模式后,在前端部分调试成功完成的项目,交给后台研发的代码基本上他们不用再改动,后端只需要维护接口就行,在联调和测试上大大减少了开发成本。

京me E维平台一经上线就得到很好的反响,产品反馈京东在全国范围内的工程师都在京me上处理工单,E维系统的出现相比以往大大提高了他们的工作效率。

总结

通过对京东 E 维平台的开发,我们第一次尝试着用 Vue.js + Webpack + Vue-router 的开发模式,一步步的实现了 Vue 单页面项目的开发。

在这个开发过程中,我们从最开始的页面重构、Vue 模板语法的套用,到数据交互时引用 Vue 单文件核心组件 Vue-router,以及对接口调试时发现了几个好用的小工具,顺利的解决了跨域问题等。

这些都在一定程度上帮助了我们成长,锻炼了我们的能力,加强了我们的业务技能,巩固了我们对 Vue 知识的掌握。虽然 E 维还有很多不足,目前仅仅满足了当下的需求,但是跟着后期业务的增加,我们也会不断的优化完善它。

 

文章来源:

Author:vicky.Y - amen1101
link:https://jdc.jd.com/archives/4622