因公司项目需要,要修改一个手机端上传图片的一个功能,原本的项目用的是input 的file控件上传的,虽然标注了可以多选,但是在实际运用当中只有iOS手机可以实现多选,Android手机并不支持多选,甚至部分Android手机因为input file 上multiple="multiple"属性连图片选择框都打开不了。而这次的需求需要用户上传多张图片,显然让用户一张图片一张图片上传满足不了需求,我们需要多选上传。
因为我们的项目是挂在微信企业号里面的,所以我想到用微信企业号的JS-SDK的方法来选择图片,可以直接绕开file控件的缺陷。直接上代码:
wx.chooseImage({
count: , // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function (res) {
$(".previewimg").html("");//每次选择图片完成后都清空之前已经添加的html节点
var images = {
localId: [],//微信返回的本地id列表
serverId: [],//微信返回的服务器id列表
};
ioslocId = [];//用于兼容ios的本地id列表 图片是base64格式的
rows = "";//声明一个空字符串用于保存循环出来的html
images.localId = images.localId.concat(res.localIds); // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
localId = images.localId;
try { for (var i = ; i < images.localId.length; i++) {
//alert(res.localIds[i]);
wx.getLocalImgData({ //循环调用 getLocalImgData
localId: res.localIds[i], // 图片的localID
success: function (res) {
var localData = res.localData; // localData是图片的base64数据,可以用img标签显示
//alert(localData);
if (window.__wxjs_is_wkwebview) { //ios
localData = localData.replace('jgp', 'jpeg');//iOS 系统里面得到的数据,类型为 image/jgp,因此需要替换一下
}
else {
localData = "data:image/jpeg;base64," + localData; //android
}
ioslocId.push(localData) //把base64格式的图片添加到ioslocId数组里 这样该数组里的元素都是base64格式的
rows = "";
for (var j = ; j < ioslocId.length; j++) {
rows += '<img style="margin: 5px;" class="up_p" src="' + ioslocId[j] + '"> <input type="hidden" name="upimg' + j + '" value="' + ioslocId[j] + '" />';
}
$(".previewimg").html(rows);
}, fail: function (e) {
alert(e);
}
});
} }
catch (err) {
alert(err) // 可执行
} }
});
这里注意到:微信企业号api官网上:https://work.weixin.qq.com/api/doc#10029/获取本地图片接口
说:wx.getLocalImgData,此接口仅在企业微信iOS支持,要求版本为2.4.6及以上,但是我们很显然的发现,Android也可以用只是返回的res.localData做处理的不同而已。
一开始我也不知道安卓手机获取的图片可以直接用getLocalImgData的返回值转base64,所以我想了很多其他办法,也走了不少冤枉路,比如我想用前端显示的图片转base64,用canvas ,如下:
function getBase64Image(img) {
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, , , img.width, img.height);
var dataURL = canvas.toDataURL("image/png");
return dataURL
// return dataURL.replace("data:image/png;base64,", "");
}
但是很遗憾,这里的img必须是同一域里的图片或者是允许跨域访问的图片地址,而微信返回的是本地ID,即使安卓手机上这个id可以直接放在img 的src上显示图片,但不知是因为id问题还是因为这个“地址”不允许跨域访问导致用canvas转的base64是错的,传到服务器端后不能保存成我们选择的图片。所以这个方法肯定不行。
于是经过他人的指点我想到用wx.uploadImage接口先上传到微信服务器,然后再用微信提供的接口在服务器端将微信服务器的图片下载到我们自己的服务器。
wx.uploadImage({
localId: '', // 需要上传的图片的本地ID,由chooseImage接口获得
isShowProgressTips: , // 默认为1,显示进度提示
success: function (res) {
var serverId = res.serverId; // 返回图片的服务器端ID
}
});
服务器端请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID
此处获得的 media_id即serverId 。
这里上传到微信服务器必须一提的是:上传图片只能一张一张图片上传,上传下一张图片必须等上一张图片上传完成,再进行下一张图片,所以我立即想到写个递归上传即可,如下:
//递归上传到微信服务器 add by torres cooooooooooooooool
localId: [];
function uploadImg(index) {
if (index < ) {
return;
}
wx.uploadImage({
localId: localId[index], // 需要上传的图片的本地ID,由chooseImage接口获得
isShowProgressTips: , // 默认为1,显示进度提示
success: function (res) {
//res.serverId 返回图片的服务器端ID
try {
var rows = '<input type="hidden" name="serverIdArr' + index + '" value="' + res.serverId + '" />';
$(".previewimg").append(rows);
} catch (e) {
alert(e)
}
uploadImg(index - );
}, fail: function (res) {
alert("上传失败,msg:" + JSON.stringify(res));
}
});
}
这样的写法可能有点low,我在网上看到另一种
var syncUpload = function (localIds, index) {
var localId = localIds.pop();
wx.uploadImage({
localId: localId,
isShowProgressTips: ,
success: function (res) {
//其他对serverId做处理的代码
try {
//alert("上传下一张");
var serverId = res.serverId; // 返回图片的服务器端ID
var rows = '<input type="hidden" name="serverIdArr' + index + '" value="' + res.serverId + '" />';
$(".previewimg").append(rows);
if (localIds.length > ) {
syncUpload(localIds, index - );
}
} catch (e) {
alert(e)
}
}, fail: function (res) {
alert("上传失败,msg:" + JSON.stringify(res));
}
});
};
两种方法差不多,都能实现我想要的,但是仅仅是在iOS。很奇怪,在Android机器上,上传多张图片时并不能上传全部图片并返回sercerId并打印出input隐藏域在页面上(或者说并不是所有图片上传都走了success方法,但也没有走fail方法),单张图片上传完全OJBK。这就很让人郁闷了,选择了三张图片上传微信服务器,但是只返回了一个或两个serverId,十分不稳定,有时候能全部上传,有时候只能上传部分图片。
这里也顺便贴出C#语言后台下载微信服务器端图片代码:
string stUrl = string.Format("https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token={0}&media_id={1}", access_token, serverIdArr[i]);
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(stUrl);
HttpWebResponse myResponse = (HttpWebResponse)req.GetResponse();
string contentType = myResponse.ContentType;
if (myResponse.ContentLength == || (contentType != "image/jpeg" && contentType != "image/pjpeg" && contentType != "image/bmp" && contentType != "image/gif" && contentType != "image/png" && contentType != "image/x-png"))
{
htInfo["error"] += "第" + (i + ) + "个文件上传的附件格式不对,请重新上传!<br/>";
continue;
}
int picUploadSize = Convert.ToInt32(ConfigurationManager.AppSettings["picUploadSize"]);
if (myResponse.ContentLength > picUploadSize * )
{
htInfo["error"] += string.Format("第" + (i + ) + "个图片不能超过{0}KB!<br/>", picUploadSize);
continue;
}
req.Method = "GET";
//req.ProtocolVersion = HttpVersion.Version10;
string strpath = myResponse.ResponseUri.ToString();
WebClient mywebclient = new WebClient();
string extension = GetExtensionName(contentType); //获取扩展名
string imgName = DateTime.Now.ToString("yyyyMMddHHmmssffff") + extension;
//下载原图
mywebclient.DownloadFile(strpath, savepath);
最终因为Android上传图片的问题这个方法还是行不通。没有办法之下我只能在 wx.chooseImage的success方法里显示选择图片的下面再加一个上传按钮,让用户手动一个一个点击上传到微信服务器,这样保证了每张都可以返回serverId,这么low的方法只能先用着了,正当我要放弃继续探索其他方法的时候,我在微信开发群里面问了一下我这个问题,然后有好心的群友给了我建议说直接转base64,不用上传微信服务器时我就告诉了他我的疑问,他给了我他的代码,最终有了
localData = "data:image/jpeg;base64," + localData; //android
上面这行代码 ,苦苦追寻,在埋头研究两天后终于实现了手机端选择多图上传的功能,在用户操作上也不用区分iOS和Android。
以上的故事告诉我们:不要轻言放弃,这种方法不行就换一种再试试,你会发现,哪一种方式都TM不行啊!!!我屮艸芔茻。略略略略略 ,才不是呢!你会发现总有一种适合你。
有什么建议或意见可以请大家多多指点,谢谢!