工作中遇到的问题

368 阅读3分钟

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表的导出功能

  1. 首先安装下面两个依赖
cnpm/npm install -S file-saver xlsx
cnpm/npm install -D script-loader
  1. 在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不能检测以下数组的变动:

  1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = new Value
  2. 当你修改数组的长度时,例如: 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)"}

操作:路由重复点击

解决方案:

  1. 更新vue-router的版本(3.0版本以上就不会出现这个问题)
cnpm i vue-router@3.0 -S
  1. 在你引了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,因为dom还未完全加载,所以你是拿不到的,update阶段则是完成了数据更新到DOM的阶段(对加载的数据进行处理),此时就可以使用this.refs,因为dom还未完全加载,所以你是拿不到的,update阶段则是完成了数据更新到DOM的阶段(对加载的数据进行处理),此时就可以使用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);
    },