一、开发遇到的问题
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 UI
的el-tale
组件,设置fixed,表格滚动到最后一行无法对齐的情况。
原因:设置了::-webkit-scrollbar
属性,其中width
和height
的值不一致导致的
解决:通过修改表格样式
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 UI
的tree
组件,default-checked-keys
的坑。
描述:对接口返回已勾选的数据defaultKeys
。进行回显,使用default-checked-keys
存在坑。如果defaultKeys
包含父节点,且只包含其部分子节点,那么它所有的子节点都会被选中。
解决:通过tree
组件的setChecked
方法,
设置节点是否被选中, 使用此方法必须设置
node-key
属性,(key/data, checked, deep)
接收三个参数:
- 要选中的节点的
key
或者数据;- 一个布尔类型参数表明是否选中;
- 一个布尔类型参数表明是否递归选中子节点遍历
defaultKeys
。
1
2
3
defaultKeys.forEach((item: string) => {
treeRef.value.setChecked(item,true,false);
});
4、element plus
的Form
表单组件,表单校验的坑。
问题:在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组件悬浮显示时,点击页面时悬浮会偶发不消失,导致遮挡页面其他元素问题
解决:监听触摸屏幕时touchstart
和touchend
事件,通过echarts
的dispatchAction
函数,手动设置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 是参数的名称,而 (.) 是参数的正则表达式模式
(.*)
是一个正则表达式,它匹配任意字符(零次或多次)。这意味着它可以捕获任何路径片段- ` * `表示捕获的路径片段可以重复零次或多次。这允许我们捕获整个路径