1.Vue项目中开发环境中的console.log(),如何在生产环境中统一删除
基于webpack打包的Vue项目,我们找到 webpack.prod.conf.js (webpack生产环境配置),按照下面的代码进行修改即可。
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false,
drop_console:true,//打包时去掉console
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
2.基于webpack打包之后的项目,生产环境中的代码如何避免暴露源码
我们可以找到 config 文件夹下面的 index.js 定位到下面的代码:
build: {
......
......
/**
* Source Maps
*/
productionSourceMap: false,
//生产环境资源映射,其改为false,线上代码看不到源码,如果线上出问题了,不好定位(一般来说线上也不需要),此外可以减少文件的体积。
devtool: '#source-map',
......
......
}
3.Vue项目中实现Excel表的导出功能
- 首先安装下面两个依赖
cnpm/npm install -S file-saver xlsx
cnpm/npm install -D script-loader
- 在src文件下面新建一个 vendor 文件夹,下面添加 Export2Excel.js,代码如下:
/* eslint-disable */
require('script-loader!file-saver')
import XLSX from 'xlsx'
function generateArray(table) {
var out = []
var rows = table.querySelectorAll('tr')
var ranges = []
for (var R = 0; R < rows.length; ++R) {
var outRow = []
var row = rows[R]
var columns = row.querySelectorAll('td')
for (var C = 0; C < columns.length; ++C) {
var cell = columns[C]
var colspan = cell.getAttribute('colspan')
var rowspan = cell.getAttribute('rowspan')
var cellValue = cell.innerText
if (cellValue !== '' && cellValue == +cellValue) cellValue = +cellValue
//Skip ranges
ranges.forEach(function(range) {
if (
R >= range.s.r &&
R <= range.e.r &&
outRow.length >= range.s.c &&
outRow.length <= range.e.c
) {
for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null)
}
})
//Handle Row Span
if (rowspan || colspan) {
rowspan = rowspan || 1
colspan = colspan || 1
ranges.push({
s: {
r: R,
c: outRow.length
},
e: {
r: R + rowspan - 1,
c: outRow.length + colspan - 1
}
})
}
//Handle Value
outRow.push(cellValue !== '' ? cellValue : null)
//Handle Colspan
if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null)
}
out.push(outRow)
}
return [out, ranges]
}
function datenum(v, date1904) {
if (date1904) v += 1462
var epoch = Date.parse(v)
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000)
}
function sheet_from_array_of_arrays(data, opts) {
var ws = {}
var range = {
s: {
c: 10000000,
r: 10000000
},
e: {
c: 0,
r: 0
}
}
for (var R = 0; R != data.length; ++R) {
for (var C = 0; C != data[R].length; ++C) {
if (range.s.r > R) range.s.r = R
if (range.s.c > C) range.s.c = C
if (range.e.r < R) range.e.r = R
if (range.e.c < C) range.e.c = C
var cell = {
v: data[R][C]
}
if (cell.v == null) continue
var cell_ref = XLSX.utils.encode_cell({
c: C,
r: R
})
if (typeof cell.v === 'number') cell.t = 'n'
else if (typeof cell.v === 'boolean') cell.t = 'b'
else if (cell.v instanceof Date) {
cell.t = 'n'
cell.z = XLSX.SSF._table[14]
cell.v = datenum(cell.v)
} else cell.t = 's'
ws[cell_ref] = cell
}
}
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range)
return ws
}
function Workbook() {
if (!(this instanceof Workbook)) return new Workbook()
this.SheetNames = []
this.Sheets = {}
}
function s2ab(s) {
var buf = new ArrayBuffer(s.length)
var view = new Uint8Array(buf)
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
return buf
}
export function export_table_to_excel(id) {
var theTable = document.getElementById(id)
var oo = generateArray(theTable)
var ranges = oo[1]
/* original data */
var data = oo[0]
var ws_name = 'SheetJS'
var wb = new Workbook(),
ws = sheet_from_array_of_arrays(data)
/* add ranges to worksheet */
// ws['!cols'] = ['apple', 'banan'];
ws['!merges'] = ranges
/* add worksheet to workbook */
wb.SheetNames.push(ws_name)
wb.Sheets[ws_name] = ws
var wbout = XLSX.write(wb, {
bookType: 'xlsx',
bookSST: false,
type: 'binary'
})
saveAs(
new Blob([s2ab(wbout)], {
type: 'application/octet-stream'
}),
'test.xlsx'
)
}
export function export_json_to_excel({
multiHeader = [],
header,
data,
filename,
merges = [],
autoWidth = true,
bookType = 'xlsx'
} = {}) {
/* original data */
filename = filename || 'excel-list'
data = [...data]
data.unshift(header)
for (let i = multiHeader.length - 1; i > -1; i--) {
data.unshift(multiHeader[i])
}
var ws_name = 'SheetJS'
var wb = new Workbook(),
ws = sheet_from_array_of_arrays(data)
if (merges.length > 0) {
if (!ws['!merges']) ws['!merges'] = []
merges.forEach(item => {
ws['!merges'].push(XLSX.utils.decode_range(item))
})
}
if (autoWidth) {
/*设置worksheet每列的最大宽度*/
const colWidth = data.map(row =>
row.map(val => {
/*先判断是否为null/undefined*/
if (val == null) {
return {
wch: 10
}
} else if (val.toString().charCodeAt(0) > 255) {
/*再判断是否为中文*/
return {
wch: val.toString().length * 2
}
} else {
return {
wch: val.toString().length
}
}
})
)
/*以第一行为初始值*/
let result = colWidth[0]
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]['wch'] < colWidth[i][j]['wch']) {
result[j]['wch'] = colWidth[i][j]['wch']
}
}
}
ws['!cols'] = result
}
/* add worksheet to workbook */
wb.SheetNames.push(ws_name)
wb.Sheets[ws_name] = ws
var wbout = XLSX.write(wb, {
bookType: bookType,
bookSST: false,
type: 'binary'
})
saveAs(
new Blob([s2ab(wbout)], {
type: 'application/octet-stream'
}),
`${filename}.${bookType}`
)
}
3.在需要导出的.vue文件夹中添加下面两个方法
data(){
tableData:[],//Excel表的数据
fileName:"",//导出的Eecel表名称
autoWidth:"",//表头自适应
bookType:"xlsx",导出的文件类型
}
methods:{
excelDow() {
import('@/vendor/Export2Excel.js').then(moudle => {
// Excel表对应的表头
const tHeader = ['租户名称', '账套', '系统地址', '联系人', '联系的电话', '租用开始日期','租用终止日期','是否封存']
// 表头对应的后台字段
const filterVal = ['name', 'accountCode', 'sysUrl', 'linkman', 'telephone', 'startDate','endDate','sealed']
// 整个列表的数据
const list = this.tableData
const data = this.formatJson(filterVal, list)
moudle.export_json_to_excel({
header: tHeader,
data,
filename: this.fileName === '' ? 'filename' : this.fileName,//导出的文件名称
autoWidth: this.autoWidth,//宽度自适应
bookType: this.bookType,//导出的文件类型
})
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => v[j]))
},
}
4.Vue项目中数组结构的input表单不能编辑(涉及到编辑赋予默认值)?
这个就是Vue的数据没有双向绑定造成的,查阅官方文档可以看到下面的解释
注意事项: 由于JavaScript的限制,Vue不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = new Value
- 当你修改数组的长度时,例如: vm.items.length = newLength
举个例子:
var vm = new Vue({
data:{
items:["a","b","c"]
}
})
vm.items[1] = 'x' //不是响应式的
vm.items.length = 2 //不是响应式的
为了解决第一类问题,以下两种方式都可以实现 vm.items[indexOfItem] = new Value 相同的效果,同时也将在响应式系统内触发状态更新:
Vue.$set(vm.items,indexOfItmes,newValue)
为了解决第二类问题,你可以使用 splice
vm.items.splice(newLength)
//splice方法改变原数组,传递一个参数表示从当前索引开始,截取到末尾
let ary = [1,2,3,4];
ary.splice(2) //[3,4]
console.log(ary); //[1,2]
实则:newLength 就想当于数组的长度了
5.项目中调试接口时遇到的一种新的格式
后台字段要求的格式如下:
depts[0].deptId //部门的id
depts[0].deptName //部门的名称
说实话,第一看到这个格式的时候懵逼了半天,怎么尝试都不可以,最终在后台同学的帮助下面问题才得到了解决,这里记录一下:
// 接口需要上传文件,所以这里使用的是FormData来进行传递的
save() {
let uploadForm = new FormData();
for (var i = 0; i < this.deptsList.length; i++) {
uploadForm.append(
"dept[" + i + "].deptId",
this.deptsList[i].deptId
);
uploadForm.append(
"dept[" + i + "].deptName",
this.deptsList[i].deptName
);
}
}
}
// 如果是多文件的话,就这样循环append
for (let i = 0; i < this.files.length; i++) {
const element = this.files[i];
uploadForm.append("file", element);
}
6.页面加载时需要让文本框获取焦点
<input v-model="name" ref="inp">
created(){
this.$nextTick(()=>{
this.$refs.inp.focus();//通过resf获取到dom元素并绑定focus方法
})
}
- 在Vue生命周期的 created()钩子函数进行的dom操作一定要放在 Vue.nextClick 的回调函数中
在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。
- 在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。
具体原因在Vue的官方文档中详细解释:
Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0)代替。
7.复杂的跨域请求中,包括着预请求方案(请求方式为OPTIONS)
问题场景: 在vue项目中通过 axios 进行的请求时,突然发现请求会发两次, 当时大哥对我说这个是预请求,不用管,后来看到了这个知识点:
在非同源的请求情况下,浏览器首先会进行OPTIONS请求,所谓的预请求就是试探性请求,向服务端请求的时候,发现此接口设置了允许对应的请求方法或者是请求头,会再次发送真正的请求,分别一共会向后台发送两次请求,拿自己想要的数据。在OPTIONS请求的时候,服务端也会返回数据,但在浏览器层被做了屏蔽,如果没有检测到对应的跨域设置则会报出对应的错误
大致说明一下,有三种方式会导致这种现象:
-
的方法不是GET/HEAD/POST
-
POST请求的Content-Type并非application/x-www-form-urlencoded, multipart/form-data, 或text/plain
-
请求设置了自定义的header字段
比如我的我的Content-Type设置为“application/json;charset=utf-8”并且自定义了header选项导致了这种情况。
在 jQuery的ajax请求 中是没有进行预请求,这个可能就是简单的跨域请求吧!
8.Vue项目中路由重复点击时报错
错误信息
NavigationDuplicated {_name: "NavigationDuplicated", name: "NavigationDuplicated", message: "Navigating to current location ("/staffmange") is not allowed", stack: "Error↵ at new NavigationDuplicated (webpack-int…e_modules/element-ui/lib/mixins/emitter.js:29:22)"}
操作:路由重复点击
解决方案:
- 更新vue-router的版本(3.0版本以上就不会出现这个问题)
cnpm i vue-router@3.0 -S
- 在你引了vue-router的 js文件里加上如下代码即可
import Vue from 'vue' //如果已引用,不需要重复引用
import Router from 'vue-router'; //如果已引用,不需要重复引用
Vue.use(Router) //如果已引用,不需要重复引用
const VueRouterPush = Router.prototype.push
Router.prototype.push = function push (to) {
return VueRouterPush.call(this, to).catch(err => err)
}
9.B端业务新增编辑时的优化处理
情景:一个弹窗中有很多的字段需要新增、以及编辑
最初做法:
data(){
return{
name:"",
age:"",
sex:"",
tel:"",
phone:"",
...
}
}
优化:
data(){
return{
personInfo:{
name:"",
age:"",
sex:"",
tel:"",
...
}
}
}
这种方式有下面的好处:
- 避免常用的变量名重复(比如两个地方都有name、age等字段)
- 清除缓存时候比较容易
this.personInfo = { name:"", age:"", sex:"", tel:"", .... }省去了一个个的置为初始值 - 编辑的时候
this.presonInfo = {...rowData},直接解构赋值,省去了赋值时间 - 必要字段不会漏传
10. elementUI级联选择器根据末级id进行回显
/**
key:末级id
treeData:级联选择器数据
**/
getCascaderIdList (key,treeData){
let arr = []; // 在递归时操作的数组
let returnArr = []; // 存放结果的数组
let depth = 0; // 定义全局层级
// 定义递归函数
function childrenEach(childrenData, depthN) {
for (var j = 0; j < childrenData.length; j++) {
depth = depthN; // 将执行的层级赋值 到 全局层级
arr[depthN] = (childrenData[j].id);
if (childrenData[j].id == key) {
returnArr = arr.slice(0, depthN+1); //将目前匹配的数组,截断并保存到结果数组,
break
} else {
if (childrenData[j].child) {
depth ++;
childrenEach(childrenData[j].child, depth);
}
}
}
return returnArr;
}
return childrenEach(treeData, depth);
},
11. this.$refs.xxx 获取到为undefined
情景复现:
用ref注册子组件,父组件可以通过this.$refs.xx.fn()来调用子组件里的函数,但是有时会出现fn未定义的情况,这个问题遇到
了好多次了,在这里记录一下。
查漏补缺:
ref被用来被用来给元素或者子组件注册引用信息的。引用信息将会注册在父组件的$refs对象上。
- 如果
在普通的DOM元素上面使用,引用的指向就是DOM元素 - 如果
在子组件上面使用,引用的就指向组件的实例(父组件就可以调用子组件实例上面的方法了)
<!-- `vm.$refs.p` will be the DOM node -->
<p ref="p">hello</p>
<!-- `vm.$refs.child` will be the child component instance -->
<child-component ref="child"></child-component>
当
v-for用于元素或者组件的时候,引用信息将是包含DOM节点或者组件实例的数组
关于ref注册时间的重要说明(这里发现官网的每一句话都很重要):因为ref本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它,它们还不存在! $refs也不是响应式的,因此你不应该试图用它在模板中做数据绑定
refs 只会在组件渲染完成之后生效,并且他们不是响应式的。这仅作用于一个直接操作子组件的“逃生仓” --你应该避免在模板或计算属性中访问
$refs
解决方法:
- 在 mounted 阶段里面获取this.refs
- 如果是写在method中,那么可以使用
this.$nextTick(()=>{this.$refs.xxx.fn()})等页面渲染好了再使用(符合refs注册时间说明),这样就可以了 - 使用定时器
setTimeOut(()=>{this.$refs.xxx.fn()})
12.对ElementUI做出一些优化性调整
在main.js 文件中修改
import Vue from "vue";
import ElementUI from "element-ui";
//弹窗点击遮罩层关闭设置为false(不小心点击到遮罩层弹窗会关闭,体验不好)
ElementUI.Dialog.props.closeOnClickModal.default = false
Vue.use(ElementUI);
// 全局修改message消息提示关闭时间(默认时间为3000,太长了),这里要写在`Vue.use(ElementUI);`后面才会生效
let duration = 1200;
Vue.prototype.$message = function(msg) {
return ElementUI.Message({ ...msg, duration: duration })
}
Vue.prototype.$message.error = function(msg) {
return ElementUI.Message.error({
message: msg,
duration: duration
})
}
Vue.prototype.$message.info = function(msg) {
return ElementUI.Message.error({
message: msg,
duration: duration
})
}
Vue.prototype.$message.success = function() {
return ElementUI.Message.success({
message: msg,
duration: duration
})
}
Vue.prototype.$message.warning = function() {
return ElementUI.Message.success({
message: msg,
duration: duration
})
}
13文件预览
handlePreview(item) {
//创建一个form表单
var form = document.createElement("form");
//设置其提交地址为文件的地址
form.setAttribute("action", item.path);
form.setAttribute("method", "get");
form.setAttribute("target", "_blank");
form.setAttribute("style", "display:none");
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
},