共计 5448 个字符,预计需要花费 14 分钟才能阅读完成。
项目中前端开发问题经验总结
ie 下 websocket 的安全限制问题
问题描述:数据看板中的数据大部分都是实时数据或前一天统计的历史数据,因此这边后端考虑采用 websocket 来实时和定时推送数据来保证数据的实时性和有效性。而前端开发这边为了提高前端开发的复用性,采用了在各个组件中开发成一个个的小部件,然后在门户通过 vue 异步动态加载的方式来实现,小部件的组装拼接。因此在组件中开发的小部件都是单 vue 页面。因此出现了有几个小部件就有几个 websocket, 目前我这边就出现了 7 个 websocket。问题现象:在 ie 浏览器下,打开时就会发现报错。IE 控制台会报 SecurityError 错误。解决方法:造成上述现象的原因是 ie 下 websocket 连接做了安全限制,如果 websocket 连接超过 6 个时,那么就会连接失败。默认最大连接数为 6 个。那么如何避免报错呢?当然 ie 下的限制我们是不好修改的,如果真的强制修改,那么可以通过修改注册表来修改最大连接数(当然这边是不建议的,不可能让客户区修改浏览器注册表的)。那么我们需要的就是避免多个 websocket 的连接。2 个思路:使用 websocket 之前先思考,是否真的有必要使用 wesocket??
如果是实时推送,定时推送这些场景,那么完全没有必要使用 websocket,前端可通过定时器来实现相同的功能。因此能避免使用就避免使用。
如果是报警等未知的推送,那么我们就是必须要使用 websocket 的,而且如果正好应用在了看板小部件上。那么还是会出现 7 个 websocket 的情况。所以这种情况下就需要和后端沟通,一个项目采用一个 websocket 服务,通过 type 来区分。那么看板就只有一个 websocket 了。可是现在看板的小部件都是独立,如何去实现一个 websocket?那么需要借助一下事件通信。如下:
a.vue
created () {
this.$root.eventBus = new Vue()
this.init()
},
methods: {
init (i) {
let ws = new WebSocket()
ws.onmessage = (data) => {
this.$root.eventBus.$emit(‘websocket’, data)
}
}
}
b.vue
created () {
this.$root.eventBus.$on(‘websocket’, () => {
// 处理推送的数据
})
}
我们知道我们的小部件都是通过在门户,通过 vue 动态加载组件的方式来形成看板的,那么所以小部件就都会在门户这个 vue 实例对象下。所以可以采用 this.$root 下挂在一个 vue 实例来实现事件的传递
备注:另外如果你一个页面中只有 4 个 websocket,而认为 ie 下就不会报错,请不要这要处理,也请使用 type 的形式来处理。因为 ie 下刷新页面销毁 websocket 是时间延迟的。第一次进入页面 websocket 连接是正常的,而舒心页面后,可能就会造成 2 个 websocket 连接失败。
hui 多语言使用问题
问题描述:在使用 hui 控件的时候,会出现某些 bug, 然后 bug 修改后,项目中应用的 hui 版本也对应的升级。但是这种情况下,可能会出现 hui 内置多语言增加了一些字段,导致项目中会出现有未翻译的字段。解决方法:
在我们各个组件框架下的 i18n 下面有一个 hui.js 文件,这个文件内部就是 hui 的多语言,这个多语言版本是在脚手架完成的时候就已经创建了,它是不会随着 hui 的升级而变化,因此我们就需要从 hui 那边去拿到最新的包(node_modules/hui/lib/locale/lang/zh-CN.js),然后再替换更新。
当然我们除了手动这样替换之外,我们也可以直接引用这个文件,那么之后就不需要再替换了。(当然翻译的文件还是需要更具 index.json 来翻译最新的)
把 hui.js 替换成以下代码那么中文状态就可以随着 hui 的升级而变化了
hui.js
修改前:
let hui = {
colorpicker: {
confirm: ‘ 确定 ’,
clear: ‘ 清空 ’
},
…. 等所以 hui 的 key 值
}
export default hui
修改后:
import hui from ‘hui/lib/locale/lang/zh-CN.js’
export default hui.el // 这边是因为 hui 内部包了一层 el,所以直接抛出 hui.el 的对象
多语言问题的拓展:在组件中开发中怎么使用多语言呢?之前组件开发我都是把变量抛到外面,通过调用者传递参数进来,那么外面肯定都是已经转过多语言的了,那么这种肯定是没问题的,当然这不是特别好的。因此这边把 hui-pro 如何使用多语言的方式来说明一下,以后开发组件中遇到多语言问题都可以这样操作,向 hui 那样把语言放到项目工程中。
首先在工程中需要创建对应的语言 js 文件如 zh_CN.js 然后在创建一个调用的方式:
import defaultLang from ‘hui-pro/src/locale/lang/zh-CN’;
import Vue from ‘vue’;
import deepmerge from ‘deepmerge’;
import Format from ‘./format’;
const format = Format(Vue);
let lang = defaultLang;
let merged = false;
let i18nHandler = function() {
const vuei18n = Object.getPrototypeOf(this || Vue).$t;
if (typeof vuei18n === ‘function’ && !!Vue.locale) {
if (!merged) {
merged = true;
Vue.locale(
Vue.config.lang,
deepmerge(lang, Vue.locale(Vue.config.lang) || {}, { clone: true})
);
}
return vuei18n.apply(this, arguments);
}
};
export const t = function(path, options) {
let value = i18nHandler.apply(this, arguments);
if (value !== null && value !== undefined) return value;
const array = path.split(‘.’);
let current = lang;
for (let i = 0, j = array.length; i < j; i++) {
const property = array[i];
value = current[property];
if (i === j – 1) return format(value, options);
if (!value) return ”;
current = value;
}
return ”;
};
export const use = function(l) {
lang = l || lang;
};
export const i18n = function(fn) {
i18nHandler = fn || i18nHandler;
};
export default {use, t, i18n};
这个 js 文件是用于合并工程中的多语言或自己翻译 t 函数就是对外组件使用多语言的方法。这边通过做一个 mixins
import {t} from ‘hui-pro/src/locale’;
export default {
methods: {
t(…args) {
return t.apply(this, args);
}
}
};
然后直接在组件中使用该 mixins 即可
<template>
{{t(`h.common.add`) }}
</template>
import Locale from ‘hui-pro/src/mixins/locale’;
export default {
mixins: [Locale]
}
require 的使用问题
问题描述:目前这边有那么一种场景,前端有一些列的城市的 json 文件,而前端需要根据后端的返回值来调用相应的城市 json 文件。对于这种情况下:我就使用了 require 加载动态文件的方式来加载,因为 require 是同步加载的,所以比较方便。使用方式如下
let city 从后端获取
let cityMap = require(`static/city/${city}.json`);
// 后续根据 cityMap 再处理
就以上那么一段代码在打包的时候会将 city 下的所以 json 文件都打包的 js 里面。(require 是提前把所有的文件都打包进来,才使得可以动态的加载)。造成了 js 比原来臃肿了很多。(臃肿程度是跟 city 下 json 文件大小有关)。然后进入对应的页面也会相对要慢一些(js 比原先大了一些),这样用户体验不好。因为对于动态加载的方式尽量避免(如果文件小的话,那影响不大)解决方法:动态获取的文件(这边的 city.js,多语言,皮肤包等等)尽量都通过 ajax 来获取,这样打包的 js 文件会少很多。为了保证仍是同步的,那么就采用 es7 的 async、await 来操作吧
async get () {
let city = xxx
try {
let cityMap = await xxx.get(‘xxxx’)
// 在根据 cityMap 出咯
} catch {}
}
门户看板小部件打包的一些问题
问题描述:之前讲述了一篇关于如何打包小部件的,但是那篇并没有使用复杂的页面,引用第三方插件等。就是单纯几个简单的页面的测试。这一次实际打包之后发现仍然有不少问题需要优化:
打包的文件会比较大。这是由于每个单 vue 文件打包,将所有依赖都打包进来了,那么就造成文件过于臃肿,相比于单 vue 实例的效果会差很多,会重复打包 vue,hui,echarts 等一些插件。
因此这边需要剔除依赖进行打包,方法如下:
// webpack 配置中增加如下配置项,如还有其他第三方插件都可以配置在如下
externals: {
echarts: ‘echarts’,
hui: ‘hui’,
vue: ‘vue’
},
通过以上过滤,可以讲一个文件从几 M 缩小到 100KB 以内。
小部件中无法获取到自己组建内部的多语言。
a. 这个是由于小部件内部是通过 this.$t 的形式去调用 i18n 来翻译的。可小部件的环境发生了变化,通过门户动态调用组件的方式加载,那么小部件所在的环境就是门户的 vue 实例对象,那么 i18n 也就是门户的,所以小部件就无法得到翻译。b. http 的实例对象内部也不可以通过 i18n 以及 {message} from ‘hui’ 这些。原因是已经剔除了这些依赖,那么打包后就会报错,i18n 和 hui 不存在。解决方法:通过一个配置文件里面存放自己组件中的 i18n 的 json 文件路径(/oams/static/i18n/zh-CN/index.json), 以及一个 keys 字段。将看板的部件多语言文件给让门户下载,并跟门户自己的多语言合并(因此多语言 key 一定要加上自己的上下文或其他来和门户区分,不要字段重叠)。这些组件内部通过 this.$t 也都能正常翻译。
hui 上传组件的问题
问题描述:目前前端都使用了统一的前端请求封装,http 都做了一些处理如登录过期跳登陆页,可上传组件是组件内部自己 ajax 请求,因此是不会做这些特殊处理。因此在组件内部需要自己做一下解决方法:如果当前已经登录过期,那么后端单点登录针对 Content-Type 为 application/json 都是后端做了一层处理,返回 errorCode 为 pleaseRefreshByHeader,那么前端根据这个值来跳登录页。可是上传组件的 Content-Type:multipart/form-data,这种类型的单点登录是直接进行拦截而不会经过后端,直接返回错误页面。那么我们就需要针对返回的页面做特殊处理
uploadSuccess (res, file) {
if (res.code === ‘0’) {
this.$message.success(this.$t(`oams_common.addSuccess`))
this.addMapDialog = false
this.$emit(‘add-map-success’)
} else {
// 页面过期处理
if (res.includes && res.includes(‘html’)) {
let refreshUrl = ‘/isecure/cas/login?service=’ + location.protocol + ‘//’ + location.host + location.pathname
location.href = refreshUrl
} else {
// 错误码处理
this.$message.error(this.$t(`oams_errorcode.${res.code}`))
this.$refs.mapUpload.clearFiles()
this.$nextTick(() => {
this.mapForm.filename = ”
})
}
}
}
除了上面的方法还可以在上传组件前,先自己发送一个接口验证当前页面是否已经过期,如果已经过期,那么它就会自动跳转首页了(自己的接口都是经过处理的),而且这也不单单处理了单点登录,网络超时也做了相应了处理。(上传组件是没有做超时处理的,因为不知道文件上传需要多久,如果设置了,网络差的情况下可能上传文件或图片就失败了)