公众号:程序员波波
之前已经写过“[React组件封装][实例]文件选择按钮组件封装”这篇文章。目的是封装一个文件选择按钮。
但是在真正运用的时候发现,还是存在问题的。主要是安卓端的微信浏览器input标签不触发onChange函数。
在经过一些查阅后,发现可能原因是安卓端的WebView不支持input标签为file,不会触发onChange,而微信安卓端使用了WebView,所以会有这样的问题。
该如何解决呢?
好在微信提供了专门的图片选择接口,这样至少保证FileButton在安卓微信端能够实现图片的选择。
使用微信的jssdk可以在微信浏览器内实现图片选择功能。
使用接口前需要先加载jweixin-1.4.0.js这个文件,然后还需要到服务器端去签名。(服务端具体的签名规则在微信的官方文档中可以查阅到)
然后我这里封装了一个WeixinTools,来完成上面的步骤。
WeixinTools.js:
import Tools from "../Tools";
import Strings from "../StringUtil";
import { UserOperation } from "../UserUtil";
import Http from "../Http";
class WeixinTools {
static configJsSdk() {
let arg = {
cmd: UserOperation.GET_JS_SDK_CONFIG,
data: {
url: window.location.href,
}
}
Http.post(Tools.getUrl('/user-operation/'), arg, (data)=>{
console.log(data)
if (data.result_id == 0) {
let ans = data.message
let js_sdk_config = ans.js_sdk_config
WeixinTools.jssdk.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: Strings.APP_ID, // 必填,公众号的唯一标识
timestamp: js_sdk_config.timestamp, // 必填,生成签名的时间戳
nonceStr: js_sdk_config.noncestr, // 必填,生成签名的随机串
signature: js_sdk_config.signature,// 必填,签名
jsApiList: [
'chooseImage',
"previewImage",
"uploadImage",
"downloadImage",
"getLocalImgData"
] // 必填,需要使用的JS接口列表
})
WeixinTools.jssdk.ready(function(){
console.log('wx js sdk ready')
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});
WeixinTools.jssdk.error(function(res){
console.log('wx js sdk error')
console.log(res)
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});
}
})
}
}
var _jssdkDependScripts = [
"/static/Weixin/jweixin-1.4.0.js",
]
if (Tools.isWeiXin()) {
Tools.asyncLoadScripts(_jssdkDependScripts, ()=>{
WeixinTools.jssdk = jWeixin
})
}
export default WeixinTools
调用 WeixinTools.configJsSdk()这个方法,就可以实现jssdk的初始化。
实际使用:
然后将之前FileButton组件中的代码进行改造,对应安卓微信端使用chooseImage这个接口来实现选择图片。
但是微信选择完图片后回调接口只会返回一个localIds,这个并不是实际的文件,而是一个文件的标识符一样的东西。
通过getLocalImgData这个接口可以使用localId来获取图片的base64数据,不过在安卓端需要手动加上'data:image/jpeg;base64,'前缀。然后通过Tools.jpegBase64ToBlob将base64转化成blob类型,就能统一成FileButton原来的接口了。
Tools.jpegBase64ToBlob方法:
static jpegBase64ToBlob(urlData) {
try {
var arr = urlData.split(',')
var mime = arr[0].match(/:(.*?);/)[1] || 'image/jpeg';
// 去掉url的头,并转化为byte
var bytes = window.atob(arr[1]);
// 处理异常,将ascii码小于0的转换为大于0
var ab = new ArrayBuffer(bytes.length);
// 生成视图(直接针对内存):8位无符号整数,长度1个字节
var ia = new Uint8Array(ab);
for (var i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob([ab], {
type: mime,
});
}
catch (e) {
var ab = new ArrayBuffer(0);
return new Blob([ab], {
type: 'image/jpeg',
});
}
}
最终版FileButton:
import React, { Component } from 'react';
import PropTypes from 'prop-types'
import CSSModules from 'react-css-modules';
import Button from '@material-ui/core/Button';
import Tools from '../../../common_js/Tools'
import styles from './FileButton.css'
import WeixinTools from '../../../common_js/weixin/WeixinTools';
class FileButton extends Component {
constructor(props) {
super(props);
this.button = React.createRef()
this.getFiles = this.getFiles.bind(this)
this.onChange = this.onChange.bind(this)
this.onClick = this.onClick.bind(this)
}
click() {
this.button.current.click()
}
onClick() {
const { onChange } = this.props
WeixinTools.jssdk.chooseImage({
count: 1, // 默认9
sizeType: ['original'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function (res) {
let localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
WeixinTools.jssdk.getLocalImgData({
localId: localIds[0],
success: function (res) {
let localData = 'data:image/jpeg;base64,' + res.localData
// let localData = 'data:image/jpeg;base64,' + res.localData.replace(/\r|\n/g, '');
if (onChange) {
onChange([Tools.jpegBase64ToBlob(localData)])
}
}
});
}
});
}
getFiles() {
let ans = []
let files = this.refs.fileLoader.files
for (let i = 0; i < files.length; i++) {
ans.push(files[i])
}
return ans
}
onChange(e) {
if (!Tools.isNone(this.props.onChange)) {
this.props.onChange(this.getFiles())
}
this.refs.fileLoader.value = ''
}
componentDidMount() {
if (Tools.isWeiXin() && Tools.isAndroid()) {
WeixinTools.configJsSdk()
}
}
render() {
let { accept, onChange, children, multiple, ...other } = this.props
if (Tools.isNone(accept)) {
accept = 'image/*'
if (Tools.isChrome()) {
accept = 'image/jpeg,image/gif,image/png,image/bmp'
}
}
if (Tools.isWeiXin() && Tools.isAndroid()) {
return (
<Button {...other} onClick={this.onClick} >
{this.props.children}
</Button>
);
}
return (
<Button component='label' {...other} ref={this.button}>
{
this.props.multiple == true ?
<input styleName='input-area' ref='fileLoader' onChange={this.onChange} type="file" accept={accept} multiple />
:
<input styleName='input-area' ref='fileLoader' onChange={this.onChange} type="file" accept={accept} />
}
{this.props.children}
</Button>
);
}
}
FileButton.propTypes = {
onChange: PropTypes.func,
accept: PropTypes.string,
}
export default CSSModules(FileButton, styles);