脚手架搭建

在github上找到了基于Vue.js 2.x系列 + Element UI 的后台管理系统解决方案,项目包括常用的管理系统组件,webpack等自动化工作全部拿来即用,具体项目地址如下:

https://github.com/lin-xin/vue-manage-system

登录状态

前端基于vue的单页面应用,前后端完全分离,并且管理系统供内部人员使用,对浏览器版本要求不高,所以为了简便高效,前后端分工更加明晰,采用后端专注提供数据,登录状态由前端维系。想到两种方案,cookie&& localStorage,前者对浏览器兼容性较好,但是数据会写进http头中,每次发送请求都会携带,后端并不需要这些数据;后者对浏览器要求较高,但完全由前端维护,使得前后端职能明确区分。最终选择使用localStorage来维系登录状态。

还有一点,就是登录状态是有时效的,直接使用Html5的localStorage并未提供时间设置功能,于是在github上找到好用的扩展库(webStorageCache)https://github.com/WQTeam/web-storage-cache,其添加了超时时间,序列化方法,可以直接存储json对象,同时可以简单的进行超时时间的设置。

webStorageCache使用代码如下:

import wsCache from 'web-storage-cache';
Vue.prototype.$wsCache = new wsCache();                             
this.$wsCache.set("username", userName, { exp: 60 * 60 });

最后,检验登录状态是否过期,初期是通过页面路由变化来触发,当页面地址有变,首先判断是否可以从localStorage中拿到登录状态,若不能直接跳转到登录页重新登录,若可以则继续跳转。该方案引入vue-router的一个钩子函数

import Router from 'vue-router';
var router = new Router({.....});
router.beforeEach((to, from, next) => {    // beforeEach为router的钩子函数,在路由有变化时调用
    if (judgeStatus(to.path))   // judgeStatus为判断登录状态是否过期的工具function
        next();
})

总结,由于项目初期开发时间紧,并且是前后端完全分离,登录状态的设计更多是从开发的方便高效角度考虑,结合项目的使用场景(供内部初期使用),问题不大。但还是列出需要改善的点,以便后续完善:1. localStorage的安全性,由于前端可见,可以伪造,安全性并未有很好的保障,后续可以考虑引入token验证的方案 2. 登录状态触发,后期可以加上接口请求级别拦截,在请求发送前添加全局拦截方法。

axios的配置

vue2.0之后,官方推荐的网络交互选择使用了axios,其是基于Promise的HTTP请求客户端。

列举下其功能特性:

  1. 在浏览器中发送XMLHttpRequests 请求

  2. 支持Promise API

  3. 拦截请求和响应

  4. 转换请求和响应数据

  5. 自动转换json数据

  6. 客户端支持保护安全免受XSRF攻击

在开发中,“享用”到 功能 2,3,4,5的便利

支持Promise API

代码举例如下:有关ajax的串行发送等,代码阅读性会更友好

axios.get('/user?ID=12345')
  .then(function (response) {
    console.log(response);
  })
  .catch(function (response) {
    console.log(response);
  });
拦截请求和响应

拦截请求,系统会针对post的请求进行数据拦截处理再发送(因为默认情况下,axios将JavaScript对象序列化为JSON。 要以应用程序/ x-www-form-urlencoded格式发送数据),post请求需要对数据进行编码。

拦截响应,系统后端接口的返回格式要严格按照前后端协商后的标准进行返回,

成功的返回
{
  "code": 200,
  "data": {}   //返回的数据
}

失败的返回
{
  "code": 500,
  "error": {
    "msg": "\"失败信息\""
  }
}

axios对响应拦截,若成功则继续进行相应处理,失败则输出错误信息,结束该次请求,该响应拦截将失败与成功的情况进行全局处理,节省了大量重复代码

如下为系统的拦截请求和响应配置代码

axios.interceptors.request.use((config) => {
    config.data = qs.stringify(config.data);
    return config;
});
axios.interceptors.response.use((res) => {
    if (res.data.code == '500') {
        Message.error(res.data.error.msg);
        return null;
    }
    return res.data;
}, (error) => {
    Message.error("网络异常");
    return Promise.reject(error);
});
axios全局配置
axios.defaults.timeout = 5000;  // 超时设置
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';  
axios.defaults.baseURL = baseUrl; // 全局的请求url前部

父子组件的通信

组件(Component)是Vue最强大的功能之一,组件可以扩展HTML元素,封装可重用的代码,在较高层面上,组件是自定义元素,编写可重用组件的同时,对于父子组件的通信也很关键

本处以系统中多图片上传组件为例,进行父子组件的通信说明。

父组件引入子组件
  1. import 子组件 import MultiImg from ‘../common/MultiImg.vue’;

  2. 子组件名称注册 components: { ‘MultiImg’: MultiImg }

  3. 在template中加入子组件标签

父组件传值

此处需要父组件传递已经上传过的图片地址以及图片上传的接口地址,写法如下:

1在子组件标签中添加传递的prop

<MultiImg :imgArray="resources.designer_works" :actionUrl="imgUploadUrl"></MultiImg>

imgArray和actionUrl为prop名称,前面加上:代表着数据对应的是变化的data

2.子组件说明prop: props: [‘actionUrl’, ‘imgArray’] 此处对应props的值,子组件可以进行前期的格式类型校验

3.最后,子组件可以直接按照props中的名称使用传递的值

注:若父组件传引用,并且没有使用.sync的情况下,则子组件的修改会同步到父组件中,实现双向绑定的效果

子组件传值

接收到父组件的初始值后,随着子组件的状态改变,父组件需要即时的监听数据变化,因此需要子组件即时的向父组件传值

1.监听需要传递的值的变化 使用vue的watch函数

2.触发事件

watch: {
    imgs: function(val) {     // imgs为变化的图片地址数组
        this.$emit("imgsChange", val);  // $emit触发事件 imgsChange为触发的事件名称,val为传值
    }
}

3.父组件需要绑定监听事件函数,在子组件标签中添加v-on,如下

<MultiImg :imgArray="resources.designer_works" :actionUrl="imgUploadUrl" v-on:imgsChange="worksChange" v-on:handleChange="handleImgChange2"></MultiImg>

当imgsChange被触发后,父组件会调用worksChange方法,并且可以传递参数,因此,子组件传递的值,可以在父组件中worksChange的方法中进行处理

第三方功能sdk

本管理系统需要引用实验室的特色“室内地图SDK”,但是该SDK多年沉淀下来,全部是由“jq套件”开发,完全没有模块化,组件化,vue单页面引入难度很大,因为该地图sdk在很多其他系统中使用,专门为了vue去改写,不现实,所以采用iframe的方式,通过src的方式引用,完全与vue隔离开。

但是仍然存在诸多问题,如下列举:

加载顺序

室内地图SDK在初始化的时候,会需要系统的数据,而该数据是通过vue发送请求获取到的,因此存在vue加载为先,iframe为后的情况,因此使用到vue的生命周期钩子函数,钩子函数列举如下:

采用created进行数据请求,mounted为iframe的src赋值,进行iframe的加载

iframe与vue的通信

在初始化的时候,需要vue请求数据传递给iframe,运行的时候,iframe中地图SDK也会存在数据变化传递给vue进行数据保存,因此它们之间的通信必不可少

本系统的交互方案是采用input隐藏域的形式

1.vue向iframe传值

首先通过vue生命周期的钩子函数created请求初始数据,数据与vue标签中的隐藏域进行绑定;接着通过vue的生命周期钩子函数mounted给iframe的src赋值,加载地图SDK,此时的地图SDK中的“jq套件”的main.js在onload的时候添加代码

var infoText = $(window.parent.document).find("input隐藏域名称").val();

获取到初始数据,进行相应的处理即可

2.iframe向vue传值

首先需要在vue中添加相应的input隐藏域,通过相应的data字段进行绑定,来承接改变的动态值,当地图SDK有数据变化时,添加如下代码

$(window.parent.document).find("input隐藏域名称").val(改变的数据值).focus().blur();

注:此处有一个坑点,就是vue不支持非手工的动态绑定,即如果input的value是通过js代码添加修改的,绑定的data值是不会相应变化的,除非是手工修改,即用户通过在input中修改value值,绑定的data值会相应改变;所以此处使用了一个小技巧,即向input域动态写值后,为隐藏域聚焦,后失焦,然后在vue的input隐藏域上添加 @focus 监听聚焦函数,通过在函数中获取value值来改变data的字段,以此实现动态的绑定

最后,附上实验室特色“地图SDK-教三楼demo”(堪比百度室内地图,尴尬,,,,,,,,)