开发问题记录

开发问题记录——持续更新...

一、开发遇到的问题

1、OSS的下载链接,直接复制在新标签页能打开下载,但是通过a标签下载,就会跳转到报错页面403 (Forbidden) You are denied by bucket referer policy. 原因: OSS 中开了 refer 校验,通过在 a 标签的 rel 属性配置禁止携带 refer 就能正常跳转下载。

1
2
3
4
5
const link = document.createElement('a');
link.style.display = 'none';
link.href = res.data;
link.rel = 'noreferrer';
link.click();

2、https协议 的网站不能下载 http协议 的链接资源 浏览器报错:Mixed Content: The page at ‘https://xxx’ was loaded over HTTPS, but requested an insecure test ‘http://xxx’. This request has been blocked; the content must be served over HTTPS.


3、new Date()的异常,Date()中参数的时间字符串,用‘,’‘/’拼接和用‘-’拼接,返回结果不同

1
2
3
new Date('2022/01/01') //结果:Sat Jan 01 2022 00:00:00 GMT+0800 (中国标准时间)
new Date('2022,01,01') //结果:Sat Jan 01 2022 00:00:00 GMT+0800 (中国标准时间)
new Date('2022-01-01') //结果:Sat Jan 01 2022 08:00:00 GMT+0800 (中国标准时间)

4、在vscode编辑器,打开刚拉取新代码,vue文件保存时出现代码自动格式化或者缩进的情况,导致引起多处git更改。可以在settings.json增加如下设置:

1
2
3
4
5
6
7
8
"[vue]": {
    "editor.defaultFormatter": "octref.vetur",
    "editor.formatOnSave": false
}

"editor.codeActionsOnSave": {
    "editor.formatOnSave": false
}

5、Safari浏览器不兼容new Date(YYYY-MM-DD) 这样的格式,只能new Date(YYYY/MM/DD)。


6、浏览器打开链接为什么有时候是预览有时候是下载?

浏览器打开链接是直接预览还是下载取决于链接的响应头 Content-Disposition 的属性。 Content-Disposition 属性是作为对浏览器对下载文件的一个标识字段。

Content-Disposition 属性有两种类型: inline(直接在浏览器上预览)、attachment(弹出对话框让用户下载)

如果Content-Disposition没有设置,则默认是inline效果。

参考链接


7、Javascript 中<%=%>是做什么的?

<%= %>是客户端用来获取服务端变量的代码,在<% %> 之间的是服务器端代码,外面的是客户端代码。 若前面有个=,则是直接引用服务器代码中的值。

1
2
3
4
5
6
7
8
//.env.production文件中代码如下
VUE_APP_VUEJS = 'vue.min.js'

//那么可以通过<%= %>引用
<script src="//static.vip.qiyi.domain/js/vue/2.5.16/<%= VUE_APP_VUEJS %>"></script>

//得到的结果是这样
<script src="//static.vip.qiyi.domain/js/vue/2.5.16/vue.min.js"></script>

8、引入AMapUI时TypeScript校验报错:“找不到名称“AMapUI” 由于高德官网引入 AMapUI 示例都是通过 script 标签,且不能通过npm方式引入。因此项目中的 AMap 是通过npm方式引入,可以使用 AMapLoader 加载 AMapUI

1
2
3
4
5
6
7
8
9
AMapLoader.load({
  key: 'xxx',
  version: "2.0",
  plugins: [],
  AMapUI: {
    version: '1.1',
    plugins: ['overlay/SimpleMarker']
  },
}).then(() => {}).catch(() => {});

但是在npm安装 AMap 模块时,高德官网没有将 AMapUI 的声明一同打包输出,导致在使用 AMapUI.loadUI() 时,TypeScript语法校验缺少 AMapUI 相关接口声明等问题,因此需要在项目types.d.ts文件上声明。

1
2
3
declare namespace AMapUI {
  export const loadUI: (plugins: string | string[], cb: any) => void;
}

9、遇到报错node:internal/crypto/hash:69 this[kHandle] = new _Hash(algorithm, xofLen);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--报错信息-->
node:internal/crypto/hash:69
  this[kHandle] = new _Hash(algorithm, xofLen);
                  ^
Error: error:0308010C:digital envelope routines::unsupported
    at new Hash (node:internal/crypto/hash:69:19)
    at Object.createHash (node:crypto:133:10)
    at module.exports.__webpack_modules__.57442.module.exports (H:\QDM_Projects\store-location\store-location-pc\node_modules\@umijs\deps\compiled\webpack\4\bundle4.js:135907:62)
    at NormalModule._initBuildHash (H:\QDM_Projects\store-location\store-location-pc\node_modules\@umijs\deps\compiled\webpack\4\bundle4.js:109317:16)
    at H:\QDM_Projects\store-location\store-location-pc\node_modules\@umijs\deps\compiled\webpack\4\bundle4.js:109352:10
    at H:\QDM_Projects\store-location\store-location-pc\node_modules\@umijs\deps\compiled\webpack\4\bundle4.js:109223:13
    at H:\QDM_Projects\store-location\store-location-pc\node_modules\@umijs\deps\compiled\webpack\4\bundle4.js:61151:11
    at H:\QDM_Projects\store-location\store-location-pc\node_modules\@umijs\deps\compiled\webpack\4\bundle4.js:61017:18
    at context.callback (H:\QDM_Projects\store-location\store-location-pc\node_modules\@umijs\deps\compiled\webpack\4\bundle4.js:60895:13)
    at H:\QDM_Projects\store-location\store-location-pc\node_modules\@umijs\deps\compiled\babel-loader\index.js:1:130029 {
  opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ],
  library: 'digital envelope routines',
  reason: 'unsupported',
  code: 'ERR_OSSL_EVP_UNSUPPORTED'
}

Node.js v18.19.0

解决办法:降低 node 版本到v16以下

10、移动端UC浏览器等下载文件异常问题 https://blog.csdn.net/BiangBaing/article/details/133169147

11、微信/企微移动端内置浏览器不能下载文件问题 跳转下载链接只能预览文件,不能下载,只能通过复制下载链接在其他浏览器打开下载

12、项目开发或生产环境服务的公共基础路径的配置注意

  • 绝对 URL 路径名,例如 /foo/
  • 完整的 URL,例如https://bar.com/foo/ (域名部分在开发环境中不会被使用,因此该值与 /foo/ 相同)
  • 空字符串或 ./(不推荐,慎重使用:可能会偶然性导致打包后的静态资源如js、css文件加载不出来404,原因是获取静态文件的路径会莫名其妙拼接上当前的路由地址,比如https://www.bar.com/home/assest/index.js(正常应该是https://www.bar.com/assest/index.js))

13、在网页中通过img标签直接查看微信公众号的图片链接异常问题 原因:浏览器根据图片的链接去请求图片所在的服务器,在请求头中,有个Referer字段标记当前请求图片的网页地址。当微信服务器发现图片请求中携带的Referer不是来自微信的域名时,就会直接返回一张“此图片来自微信公众平台,未经允许不可引用”的图片。 解决:通过img标签的referrerpolicy规定在获取图像时要使用的引用信息。no-referrer:不发送引用者信息。 <img referrerpolicy="no-referrer" src="ttps://mmbiz.qpic.cn/xxxxxx" alt="">

14、iOS系统下window.open()失效问题 原因:iOS的Safari浏览器为了防止弹出广告和恶意窗口,对window.open()方法进行了限制:如果window.open()不是由用户直接交互触发的(例如点击事件),而是由代码自动执行的,就会被拦截。 解决:

  • 方法1:确保用户交互触发‌:确保window.open()是由用户的直接交互(如点击事件)触发的,而不是由代码自动执行的
  • 方法2:使用setTimeout延迟执行‌:将window.open()放在setTimeout中执行,因为setTimeout是在主线程运行的,不会被浏览器认定为代码操作,从而避免被拦截
  • 方法3:使用window.location.href替代‌:在iOS环境下,可以使用window.location.href来进行页面跳转,这种方法不会受到iOS安全机制的限制

二、插件、组件相关问题

1、element UIel-tale组件,设置fixed,表格滚动到最后一行无法对齐的情况。 原因:设置了::-webkit-scrollbar属性,其中widthheight的值不一致导致的 解决:通过修改表格样式

1
2
3
.el-table__fixed-body-wrapper .el-table__body {
    padding-bottom: 8px; // 滚动条高度
}

2、el-form表单验证,打开编辑页面时总是在刚进入页面显示触发表单验证结果,由于此时并未提交,影响用户体验感 解决: 使用 clearValidate 方法移除校验结果,此处应注意 clearValidate 是对 DOM 的操作,需要在 nextTick 内实现,以避免不生效的问题。

1
2
3
4
//  使用clearValidate移除表单验证结果
this.$nextTick(() => {
    this.$refs["dateForm"].clearValidate();
});

3、element UItree组件,default-checked-keys的坑。 描述:对接口返回已勾选的数据defaultKeys。进行回显,使用default-checked-keys存在坑。如果defaultKeys包含父节点,且只包含其部分子节点,那么它所有的子节点都会被选中。 解决:通过tree组件的setChecked方法,

设置节点是否被选中, 使用此方法必须设置 node-key属性,(key/data, checked, deep) 接收三个参数:

  1. 要选中的节点的 key 或者数据;
  2. 一个布尔类型参数表明是否选中;
  3. 一个布尔类型参数表明是否递归选中子节点遍历defaultKeys
1
2
3
defaultKeys.forEach((item: string) => {
    treeRef.value.setChecked(item,true,false);
});

4、element plusForm表单组件,表单校验的坑。 问题:在el-form组件内写入以下代码,并进行表单验证,有时会在输入框下方出现xxx is required红色提示

1
2
3
<el-form-item prop="name" label="名称" required>
    <el-input v-model="name" placeholder="请输入名称"></el-input>
</el-form-item>

原因:是el-form-item元素上的required属性引起的,自定义校验规则和required属性同时出现,然后有两个验证规则,导致会触发两种校验,所以只需删除required属性即可。 拓展:删除required属性后,表单项的必填的*号又会消失,只需要在el-form-item元素上添加class="is-required"类型即可。

1
2
3
<el-form-item prop="name" label="名称" class="is-required">
    <el-input v-model="name" placeholder="请输入名称"></el-input>
</el-form-item>

还有一个方法就是把required属性用在自定义规则里

1
2
3
4
5
rules: {
    name: [
        { required: true, validator: checkName, trigger: 'blur' }
    ]
}

5、在移动端使用echarts时,其tooltip组件悬浮显示时,点击页面时悬浮会偶发不消失,导致遮挡页面其他元素问题 解决:监听触摸屏幕时touchstarttouchend事件,通过echartsdispatchAction函数,手动设置tooltip组件的显示(showTip)或者隐藏(hideTip)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--在react中使用-->
  useEffect(() => {
    const startFn = () => {
      chartRef.current?.dispatchAction({type: 'showTip'})
    };
    const endFn = () => {
      setTimeout(() => {
        chartRef.current?.dispatchAction({type: 'hideTip'})
      }, 1000)
    };
    document.addEventListener('touchstart', startFn);
    document.addEventListener('touchend', endFn);
    return () => {
      document.removeEventListener('touchstart', startFn);
      document.removeEventListener('touchend', endFn);
    };
  }, []);

三、功能实现

1、【文件操作】把bolb流转成file流,并添加文件名

1
2
3
4
let fileStream = new File([bolbStream], 文件名,{type: "text/plain", lastModified: date});
//参数1:是一个字符串数组。数组中的每一个元素对应着文件中一行的内容
//参数2:是文件名字符串
//参数3:设定一些文件的属性,比如文件的MIME,最后更新时间等

2、【文件操作】使用vue-json-excel导出Excel表格。 安装:npm i -S vue-json-excel 引入:import JsonExcel from 'vue-json-excel' 常用属性:

data fields name type
需要导出的数据 导出数据的字段 导出excel的文件名 导出excel的文件类型(xls,csv),默认是xls
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  json_fields: {
    年龄: "age", //常规字段
    姓名: "info.name", //支持嵌套属性
    密码: {
      field: "info.phone",
      //自定义回调函数
      callback: value => {
        return `+86 ${value}`;
      }
    }
  }
  json_data: [
    {
      age: 22,
      info: {
        name: "张三",
        phone: 12222222222
      },
      sex: ""
    },
    {
      age: 23,
      info: {
        name: "李四",
        phone: 13333333333
      },
      sex: ""
    }
  ]

存在的坑:(可以修改源码解决) 1、较长的数字字符串,会在导出后被自动转成科学计数法; 2、时间字段精确到秒时,导出来的数据显示不全。 参考链接 Github链接


3、【文件操作】使用xlsx导入/导出Excel表格。 安装:npm install xlsx --save 引入:import * as XLSX from 'xlsx' 导入(读取文件数据) 1)创建FileReader实例;

1
const reader = new FileReader();

2)通过FileReader.readAsBinaryString()读取指定的 Blob 或 File 对象,当读取完成的时候,readyState 会变成DONE(已完成),并触发 loadend 事件,同时 result 属性将包含所读取文件原始二进制格式;

1
2
3
4
5
//当读取操作成功完成时调用
fileReader.onload = (e) => {
    console.log(e.target.result);
}
fileReader.readAsBinaryString(文件对象); //将文件读取为二进制字符串

3)通过xlsx获取workbook

1
2
3
4
5
6
7
8
9
10
const workbook = XLSX.read(e.target.result, { type: type }); //返回一个叫WordBook的对象
//其中,这里type的类型要与处理文件(读取文件)时读的data一致,FileReader方法对应的type取值如下
/**
    base64:以base64方法读取
    binary:BinatyString格式(byte n is data.charCodeAt (n))
    string:UTF-8编码的字符串
    buffer:nodejs Buffer
    array:Uint8Array,8位无符号数组
    file:文件的路径(仅nodejs下支持)
*/

4)获取获取Sheets中Sheet的名字,并获取数据。

1
2
const wsname = workbook.SheetNames[0]; //获取Sheets中第一个Sheet的名字,多个sheet时可以遍历读取
const result = XLSX.utils.sheet_to_json(workbook.Sheets[wsname]);

完整代码

1
2
3
4
5
6
7
const fileReader = new FileReader();
fileReader.onload = (e) => {
    const workbook = read(e.target.result, { type: "binary" });
    const wsname = workbook.SheetNames[0];
    const result = utils.sheet_to_json(workbook.Sheets[wsname]);
}
fileReader.readAsBinaryString(文件对象);

导出Excel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const EXPORT_LIST = [
    {
        姓名: 'Amy',
        年龄: 24,
        性别: ''
    },
    {
        姓名: 'Mike',
        年龄: 25,
        性别: ''
    },
];
// 创建sheet
const data = XLSX.utils.json_to_sheet(EXPORT_LIST);
// 创建workbook
const workbook = XLSX.utils.book_new();
// 把sheet放入workbook
XLSX.utils.book_append_sheet(workbook, data, sheet的名字);
// 写入文件(通过文件名控制导出的类型)
XLSX.writeFile(workbook, 文件名);

//生成sheet的方法
/**
    json_to_sheet:将由对象组成的数组转化成sheet
    aoa_to_sheet:将一个二维数组转成sheet
    table_to_sheet:将table的dom直接转成sheet
    sheet_add_aoa:将二维数组添加到现有工作表中
    sheet_add_json:将js对象数组添加到现有工作表中
*/

4、回显通过富文本编辑的html时,如何放大查看其中的图片

在渲染html的节点上绑定点击事件,默认获取当前的事件对象e,判断当前点击的元素是否是图片(e.target.nodeName === IMG

1
2
3
4
5
6
7
8
9
<!--html-->
<div v-html="content" onclick="handleClick"></div>
<!--js-->
const handleClick = (e) => {
    if (e.target.nodeName.toUpperCase() === 'IMG') {
      const img = event.target.currentSrc; // 图片路径
      console.log(img);
    }
}

5、前端在线展示PPT内容

把ppt的链接拼接到该地址的src后面 https://view.officeapps.live.com/op/view.aspx?src=https://jifen-web.oss-cn-beijing.aliyuncs.com/temp/ppt.pptx


6、获取两点经纬度坐标之间的距离

1
2
3
4
5
6
7
8
9
10
11
const getDistances = (lat1, lng1, lat2, lng2) => {
	let EARTH_RADIUS = 6378.137;// 地球半径
	let radLat1 = lat1 * Math.PI / 180.0; //lat1 * Math.PI / 180.0=>弧度计算
	let radLat2 = lat2 * Math.PI / 180.0;
	let a = radLat1 - radLat2;
	let b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;
	let s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
	s = s * EARTH_RADIUS; 
	s = Math.round(s * 10000) / 10000;// 输出为公里
	return { m: s * 1000, km: Number(s.toFixed(2)) }
}

7、浅析FormData的使用 get(key):获取键为key的第一个值。

getAll(key):返回一个数组,获取键为key的所有值。

has(key):判断是否存在键为key,返回true/false。

delete(key):删除对应的键值

append(key, value, filename):添加一个新值到FormData对象内的已存在的键中,如果键不存在则会添加该键。

参数1:键值名称key; 参数2:值,可以是String或者Blob类型等等,但是不支持对象,会转成这种形式:[object, file]、[object, object]。 参数3(可选):传给服务器的文件名称(当第二个参数为Blob或File时,该参数作为默认文件名)。

set(key, value, filename):如果指定的键已存在,则会覆盖已有的值。

1
2
3
4
5
6
7
8
9
10
11
const formData = new FormData()
formData.append('key1', 'value1')
formData.append('key1', 'value2')
formData.get('key1') // 'value1'
formData.getAll('key1') // ['value1', 'value2']
formData.get('key1') === formData.getAll('key1')[0] // true

formData.set('key2', 'value1')
formData.set('key2', 'value2')
formData.get('key2') // 'value2'
formData.getAll('key2') // ['value2']
1
2
3
4
5
6
// 多个文件上传例子
const filesArr = [bold1, bold2, bold3, ...]
const formData = new FormData()
formData.append('file', filesArr) // 【错误方法】,控制台Network看到发送file参数的值变成[object, file]形式
// 应该要遍历文件数组append进去
filesArr.map(item) => formData.append('file', item) // 【正确方法】

8、捕获所有不匹配路由文件的路由

1
2
3
4
5
6
7
8
{
    path: '/:pathMatch(.*)*',
    name: '404',
    meta: {
      title: '404',
    }
    redirect: '/404', // 进行路由重定向
}

path: /:pathMatch(.*)*:可以匹配任意路径,包括根路径和子路径。

-:pathMatch(.*)*是一个动态片段,它使用了路由参数(以冒号 : 开头),其中 pathMatch 是参数的名称,而 (.) 是参数的正则表达式模式

  • (.*)是一个正则表达式,它匹配任意字符(零次或多次)。这意味着它可以捕获任何路径片段
  • ` * `表示捕获的路径片段可以重复零次或多次。这允许我们捕获整个路径


-->