这是我参与更文挑战的第4天,活动详情查看: 更文挑战
封装企业微信sdk
实现多图片上传
iframe重定向+生成器解决。
为什么要用iframe呢,是因为要解决跨域问题,企业微信接口要求post方法发送消息,所以就直接用了iframe+post来实现跨域(后面讲)。
为什么用生成器,因为生成器不想for循环一样局限于一块作用域,生成器是可以想从哪里取就从哪里取的那种。具体场景:
由于我用form来做跨域,那么当我一个请求发完,form所在的iframe会跳转到请求结果页面,那么我要把它重定向回来当前域,(不然无法发送下一个请求)。那么就一定要等他发完了,页面跳转了,再去重定向回来当前页面。(为啥不能直接写在submit后面,我猜测是因为运行到iframe.src="https://juejin.cn/post/\"
这句话的时候还没发生跳转,所以无效,要写在onload里面才行。)既然要重定向,就要上一个锁,否则重定向回来之后就还会经过onload,就还会继续重定向,造成死循环。
以上是背景,那么当我页面重定向回来之后,我还想发送下一个post请求,是不是要判断一下当前发送到哪一个数据了,后面还有没有,如果还有的话就要把锁给打开。
具体流程如下:
-
先发送一个form
-
页面重定向到请求结果页面
-
经过onload,开始判断是否有上锁
发现没上锁,然后重定向到
iframe.src="https://juejin.cn/"
上锁防止再次重定向
-
判断是否有下一个数据
如果有的话,就继续用form发送
-
form发送的过程中,要判断是否发送到最后一个了
不管是发送前面的数据还是发送到最后一个,还是要把锁打开,允许页面再重定向回来一次
最后一个发送完之后,才能把锁关上
-
…(重复以上步骤直到数据发送完毕)
也就是说,不仅是iframe内部onload的时候需要判断数据有没有发送完毕,在form发送数据的过程中也需要判断是否发送完毕,如果要用for循环的话,就得把他们两个写到一起,或者用参数传递的方式来判断数据是否发送完毕,有点多余。
先搞一个生成器:
private* getMsg(msg) {
let i = 0
while (i < msg.length) {
yield msg[i];
i++
}
//最后一次发送需要重定向页面
return "redirect";
}
复制代码
跨域问题解决
利用form+iframe
先封装一个iframe:
//防止iframe重定向递归
let lock = true
// 控制iframe并利用form发送数据
public IframePost(msg, format = ""): Promise<any>{
// 只需要一个iframe
let iframe = window.frames["postFrame"]?.frameElement
// 如果是数组,说明是多图片上传,需要用到生成器
this.genMsg = Array.isArray(msg)?this.getMsg(msg):null
if(!iframe){
iframe = document.createElement('iframe')
iframe.style.display = "none";
iframe.name = "postFrame"
iframe.id = "postFrame"
// 监听onload事件,页面重新加载时触发
iframe.addEventListener('load', ()=>{
// 如果没有上锁就可以重定向页面
if(!lock){
// 是否重定向页面
// 重定向流程 当前页面->提交数据之后跑到后端数据页面->后端页面onload时重定向到当前页面
// 如果不重定向:停留在后端数据页面,可以看到返回数据,但会由于跨域无法再次发送请求
iframe.src = "/"
// 这里重定向回来之后要上一次锁,否则会造成重定向死循环
lock = true
// 如果生成器有值,继续发送下一个信息
if(this.genMsg){
let nextMsg = this.genMsg.next()
// 利用form发送数据
// nextmsg发送到最后一个的时候,这里到底是怎么通过的?
!nextMsg.done&&this.FormPost(nextMsg, format)
}
}
})
document.body.appendChild(iframe)
}
if(this.genMsg){
//如果是数组(图片和文件),就利用生成器获取发送消息
return this.FormPost(this.genMsg.next(), format)
}else{
return this.FormPost(msg, format)
}
}
复制代码
封装form:
// 利用创建form发送数据
private FormPost(msg, format = ""){
const form = document.createElement("form");
form.style.display = "none";
// 把form跳转的页面放到框架里,这样写的好处是可以直接从windows下拿到form而不需要放到iframe里面去
form.target = "postFrame"
form.id = "postForm"
return new Promise((resolve, reject) => {
form.setAttribute("method", "POST");
// 定义form的格式(消息体的格式)
this.getFormatForm(form, msg, format)
document.body.appendChild(form);
//提交表单
form.submit();
resolve(form)
}).then((resolve)=>{
//提交完就删除
form.remove()
//如果还需要一次重定向就不上锁
if(msg.value === "redirect"){
lock = false
}else{
//不上锁,直到发送消息结束的时候上锁
lock = msg.done
}
})
}
复制代码
-
发送application/x-www-form-urlencoded格式数据
默认就是这个格式,所以不需要多设置什么。
form.setAttribute("action", "http://localhost:3000/efoxPayWechatTips"); let bodyKey = [ { name: "rootKey", value: this.key }, { name: "sendMsg", value: this.genMsg?msg.value:msg } ] bodyKey.forEach((el) => { var input = document.createElement("input"); input.name = el.name; input.value = JSON.stringify(el.value); form.appendChild(input); }) 复制代码
-
发送formdata格式数据
// 如果要发送formdata类型就直接把页面上的input赋值到form的input中 form.setAttribute("action", "http://localhost:3000/postFile"); form.setAttribute("enctype","multipart/form-data") let input = document.createElement("input"); // 当使用formdata的时候,msg其实是一个input,直接把该input放到表单中 input = msg; form.appendChild(input); let keyInput = document.createElement("input"); keyInput.name = "rootKey" keyInput.value = this.key form.appendChild(keyInput); 复制代码