问题记录
1、换行符带来的签名问题
  • 1、手机端换行 \n
  • 2、pc换行 ↵
  • 3、签名计算md5加密时 \n 签名校验失败
  • 4、解决思路
    • 换行符全部转化为 ↵ 或者特定字符(eg:$huanhang$) 进行签名计算
    • 渲染时再转化为 \n 进行展示
  • 5、需要在文本域(textarea)内使用
    replaceMobileEnterToPc (str) {
      return str.replace(/\n/g, "↵");
    },
    replacePcEnterToMobile (str) {
      return str.replace(/↵/g, "\n");
    }
2、图片上传,列表更新不同步问题(缓存)
    <img :src="item.img ? item.img + '&t=' + (new Date().getTime()) : 'static/images/user/morenyonghu.png'" >
3、加载默认图片(失效、异常),img 陷入 onerror 事件死循环
  • 1、设置了默认图片,但是默认图片失效,触发 onerror 事件
  • 2、解决思路
    • onerror 事件只触发一次 this.onerror=null
<img :src="item.img ? item.img  + '&t=' + (new Date().getTime()) : 'static/images/device/default.png'" onerror="javascript:this.src='static/images/device/default.png';this.onerror=null;"
4、图片上传(原生)
  • 1、手机拍照
  • 2、预览
  • 3、ajax 参数
  • 4、解决思路
    • 选择本地图片(文件)或者手机拍照存储为 [{id: 'file_' + 时间戳, file: file文件}]
    • 通过 readAsDataURL(file) 方法存储 base64 格式图片作为预览缩略图
// html
<input type="file" accept="image/*" @change="uploadImgFile" ref="input" multiple />

// js
data () {
    return {
        fileData: [], // 存储上传原文件
        imgArr: [], // 存储缩略图
        previewImgs: [], // 存储预览大图
        delImgArr: [], // 删除的图片id集合
        delImgId: '' // 删除的图片id字符串 1,2,3,4
    }
}
..........

uploadImgFile () {
    let files = this.$refs.input.files;
    for (let i = 0; i < files.length; i++) {
      let tempObj = {};
      let tempId = 'file_' + new Date().getTime() + Math.floor(Math.random() * 10000);
      tempObj.id = tempId;
      tempObj.file = files[i];
      this.fileData.push(tempObj);
      readAndPreview(files[i], tempId);
    }
    this.$refs.input.value = ''; // 避免上传同一个照片不触发 change 事件
    console.log(this.fileData);
},
function readAndPreview (file, tempId) {
    if (file !== undefined) {
        if (/\.(jpe?g|png|gif)$/i.test(file.name)) {
            let that = this;
            let reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = function (e) {
              that.imgArr.push({ id: tempId, url: e.target.result }); // 缩略图集合
              that.previewImgs.push(e.target.result); // 预览图集合
              }
           }
    }
},
deleteImg (id) {
    MessageBox.confirm('确定删除该照片?').then(action => {
        if (/^file/.test(id)) { // 删除新增图片
          let idx = this.fileData.findIndex(item => item.id === id);
          this.fileData.splice(idx, 1);
        } else { // 删除原有接口图片
          this.delImgArr.push(id);
          this.delImgId = this.delImgArr.join(',');
        }
        let index = this.imgArr.findIndex(item => item.id === id);
        this.imgArr.splice(index, 1); // 删除缩略图
        this.previewImgs.splice(index, 1); // 删除预览图
    }).catch(res => {
      console.log('点击了取消按钮');
    });
},

// 接口参数处理
let formData = new FormData();
let param = {};
param.id = this.id;
param.describe = this.replaceMobileEnterToPc(this.describe); // 保证手机端换行符 \n 签名能够通过
if (this.delImgId !== '') param.file_id = this.delImgId;
let data = getDataAndSign(BASE, DETAIL_URI, param);
formData.append('v', data.v);
formData.append('t', data.t);
formData.append('token', data.token);
formData.append('s', data.s);
formData.append('id', data.id);
formData.append('describe', data.describe);
if (this.delImgId !== '') formData.append('file_id', this.delImgId);

// image1, image2, image3, ...... , image100
if (this.fileData.length > 0) {
    for (let i = 0; i < this.fileData.length; i++) {
        formData.append('image' + (i + 1), this.fileData[i].file);
    }
}

// image[] 写法,(后端体现为数组)
if (this.fileData.length > 0) {
    for (let i = 0; i < this.fileData.length; i++) {
        formData.append('image[]', this.fileData[i].file);
    }
}


// axios 请求接口(主要设置 header中Content-Type)
let options = {
    method: 'post',
    url: BASE + DETAIL_URI,
    data: data,
    headers: {
        'Content-Type': 'multipart/form-data',
        'Cache-Control': 'no-cache'
    }
};
axios(options).then(response => {
    // ...
})
5、数据导出为 Excel 格式
var aoa = [
    ['主要信息', null, null, '其它信息'], // 特别注意合并的地方后面预留2个null
    ['姓名', '性别', '年龄', '注册时间'],
    ['张三', '男', 18, new Date()],
    ['李四', '女', 22, new Date()]
];
var sheet = XLSX.utils.aoa_to_sheet(aoa);
sheet['!merges'] = [
    // 设置A1-C1的单元格合并
    {s: {r: 0, c: 0}, e: {r: 0, c: 2}}
];
openDownloadDialog(sheet2blob(sheet), '单元格合并示例.xlsx');

// 将一个sheet转成最终的excel文件的blob对象,然后利用URL.createObjectURL下载
function sheet2blob(sheet, sheetName) {
    sheetName = sheetName || 'sheet1';
    var workbook = {
        SheetNames: [sheetName],
        Sheets: {}
    };
    workbook.Sheets[sheetName] = sheet;
    // 生成excel的配置项
    var wopts = {
        bookType: 'xlsx', // 要生成的文件类型
        bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
        type: 'binary'
    };
    var wbout = XLSX.write(workbook, wopts);
    var blob = new Blob([s2ab(wbout)], {type:"application/octet-stream"});
    // 字符串转ArrayBuffer
    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;
    }
    return blob;
}

/**
 * 通用的打开下载对话框方法,没有测试过具体兼容性
 * @param url 下载地址,也可以是一个blob对象,必选
 * @param saveName 保存文件名,可选
 */
function openDownloadDialog(url, saveName)
{
    if(typeof url == 'object' && url instanceof Blob)
    {
        url = URL.createObjectURL(url); // 创建blob地址
    }
    var aLink = document.createElement('a');
    aLink.href = url;
    aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
    var event;
    if(window.MouseEvent) event = new MouseEvent('click');
    else
    {
        event = document.createEvent('MouseEvents');
        event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    }
    aLink.dispatchEvent(event);
}
6、导出数据为 csv 格式
// 定义必须参数并赋予测试数据
    let data = [
      // 真实数据需要与此测试数据格式保持一致。
      { name: "张三", url: "https://www.baidu.com" },
      { name: "zhangsan", url: "https://www.baidu.com" },
      { name: "zhangsan", url: "https://www.baidu.com" },
      { name: "zhangsan", url: "https://www.baidu.com" },
    ]
    let header = { name: "张三", url: "路径" } // 表头

    //数据处理, 这一部分可以封装为函数
    data.unshift(header) // 添加表头
    // 处理数据为csv的格式
    let csvString = ""
    data.map((item) => {
      Object.keys(header).map((key) => {
        let value = item[key]
        csvString += value + ","
      })
      csvString += "\r\n"
    })
    // 保存为csv文件并添加下载按钮
    csvString = "data:application/csv," + encodeURIComponent(csvString)
    let btn = document.createElement("a")
    btn.setAttribute("href", csvString)
    btn.setAttribute("download", "data.csv")
    btn.innerText = "下载"
    // 定义一个修改元素样式的函数
    function setStyle(dom, styles = {}) {
      Object.keys(styles).map((key) => {
        dom.style[key] = styles[key]
      })
      return dom
    }
    // 设置按钮的样式
    btn = setStyle(btn, {
      position: "fixed",
      top: "10px",
      left: "10px",
      zIndex: "9999999999",
      backgroundColor: "yellow", // 注意的是,样式名不能有下划线,要合并在一起改为首字母大写列如:  background-color  =>  backgroundColor
    })
    document.body.appendChild(btn)
7、禁止浏览器密码输入框点击显示保存的密码
  • 1、场景:拥有记住账号密码功能之后,由于此前浏览器提醒记住了密码,再次点击密码框触发了浏览器管理账号密码机制,将对应账号密码列表显示出来了,要求禁止显示该列表
  • 2、问题原因
    • 浏览器是根据输入框的类型是password来判断你在保存密码
    • 一旦登录成功之后提示你是否保存,而你点击了保存,账号密码就会保存在管理列表中
  • 3、解决方案
    • 核心就是不能设置type为password
    • css有专门设置input文字改成密文的属性
 -webkit-text-security:disc

 // 指定要使用的形状来代替文字的显示
 none 无。
 circle 圆圈。
 disc 圆形。
 square 正方形。
日常
JSRUN前端笔记, 是针对前端工程师开放的一个笔记分享平台,是前端工程师记录重点、分享经验的一个笔记本。JSRUN前端采用的 MarkDown 语法 (极客专用语法), 这里属于IT工程师。