H5頁面開發筆記
本次開發一個H5頁面,H5頁面用的框架是ant design mobile,對接的是react native 開發的App 程序,其中遇到了一點坑,在此記錄一下。
移動端調試神器vconsole
// 安裝vconsole
npm install vconsole --save
// 引入項目當中
import Vconsole from 'vconsole'
const vConsole = new Vconsole()
// 然後console.log()的日誌就可以通過vconsole 輸出在移動端了
console.log('移動端可見日誌')
// 如果是使用VUE框架,可以在main.js中加入如下代碼
Vue.use(vConsole)
通過postMessage與app頁面通訊
通訊總共包含如下三個步驟
- H5頁面在組件掛載之後,註冊監聽程序,以便接收App發送的數據
- 通過window.postMessage方法給App發送請求
- App在接受到H5的請求之後,解析命令,返回H5所需要的數據
- H5通過receiveMessage 處理接收到的數據
具體樣例代碼如下:
// 判斷是否是IOS操作系統
const isIosDev = () => {
let u = navigator.userAgent
let isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios終端
return isIOS;
}
const isIos = isIosDev()
// 與app約定的構造命令的函數
const createCommand = (command, params) => {
return JSON.stringify({
command: command,
params: params,
});
}
const postMessage = (command, params) => {
// 注意!!!此處需要異步,200ms以後發送請求,app那邊纔可以收到請求
setTimeout(() => {
window.postMessage(createCommand(command, params), "*");
}, 200);
}
const receiveMessage = (event) => {
console.log('-------receiveMessage接受到的信息 -----------')
console.log(event.data)
const data = event.data
if (typeof data === 'string') {
try {
const jsonData = JSON.parse(data)
const { command, params = {} } = jsonData
// 與APP測約定command_accept_info命令爲所需接收的命令
if (command === 'command_accept_info') {
console.log('處理後續接收到的params')
}
} catch (err) {
console.log(err)
}
}
}
componentDidMount() {
// 註冊監聽,接收處理App返回的數據
window.document.addEventListener("message", receiveMessage, false);
postMessage('command_request_info', '')
}
解決直接在html中引入fastclick ,安卓端報錯問題
// 安裝
npm install fastclick -S
// 引入
import FastClick from 'fastclick'
// 使用
componentDidMount() {
FastClick.attach(document.body);
}
解決直接安卓端軟鍵盤遮擋input輸入問題
componentDidMount() {
// android 鍵盤彈起,輸入框被遮擋的解決方案
if (!isIos) {
window.addEventListener("resize", function () {
if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") {
window.setTimeout(function () {
document.activeElement.scrollIntoViewIfNeeded();
}, 0);
}
});
}
}
可以多次疊加上傳文件,自定義樣式解決方案
注意!!! 文件上傳的選擇需要App端的支持,否則安卓端可能存在選擇文件App閃退的現象,accept屬性設置accept=“audio/,video/,image/,application/,text/*”,ios端選不了doc、xls、pdf等文件等問題,此處有些坑~~
// 創建一個input引用
inputRef = React.createRef()
uploadFile = () => {
// 點擊按鈕是,觸發input的點擊事件
this.inputRef.current.click()
}
<div className="listline">
<div className="inputlabel">證據上傳</div>
<div className="uploadWrap">
{/* 原生的input隱藏,不顯示 */}
<input type="file" accept="*" style={{ display: 'none' }} ref={this.inputRef} onChange={this.handleFileChange} multiple />
{/* 此處爲自己設置的按鈕樣式 */}
<Button type="ghost" size="small" inline style={{ marginLeft: '16px' }} onClick={this.uploadFile}>點擊選擇文件</Button>
</div>
</div>
文件大小的計算
此處有一個需求,可以實現多次選擇文件,每次可以多選,並且選擇之後疊加之前的文件,總文件大小不能超過50M,同時需要給與剩餘可選文件大小的提示信息
// 最大可支持的文件總和大小
maxLength = 50 * 1024 * 1024
// 計算文件總大小
totalFilesLength = (files) => {
let size = 0
files.forEach(file => {
size = size + file.size
})
return size
}
// 檢查文件長度
checkFilesLength = (files) => {
const size = this.totalFilesLength(files)
return size <= this.maxLength
}
// 轉換文件單位
sizeConvertUnit = (size) => {
const sizeMap = {
MB: 1024 * 1024,
KB: 1024,
B: 1
}
let unit = 'B'
let convertSize = size
Object.keys(sizeMap).some(item => {
if (size >= sizeMap[item]) {
unit = item
convertSize = parseFloat(size / sizeMap[item]).toFixed(1)
return true
}
return false
})
return `${convertSize}${unit}`
}
// 計算文件剩餘大小
remainFileSize = (files) => {
const size = this.totalFilesLength(files)
const remainSize = this.maxLength - size
return this.sizeConvertUnit(remainSize)
}
handleFileChange = (e) => {
const { files } = this.state
const newfiles = Array.from(this.inputRef.current.files)
const newFilesName = newfiles.map(item => item.name)
const oldFiles = files.filter(item => !newFilesName.includes(item.name))
const mergeFiles = oldFiles.concat(newfiles)
if (this.checkFilesLength(mergeFiles)) {
this.isSelectFile = true
this.setState({
files: mergeFiles
});
} else {
Toast.info('所有文件大小總和不能超過50M', 3, null, false);
}
}
// 刪除文件
deleteFile = (index) => {
const { files } = this.state
files.splice(index, 1);
this.setState({
files
})
}
const { files } = this.state
const fileList = files.map((file, index) => {
return (
<div key={index} style={{ padding: '0 15px', marginTop: '8px' }}>
<span style={{ wordBreak: 'break-all' }}>{file.name}</span>
<span style={{ marginLeft: '16px' }}>{this.sizeConvertUnit(file.size)}</span>
<span style={{ color: '#1890ff', marginLeft: '16px' }} onClick={() => this.deleteFile(index)}>刪除</span>
</div>
)
})
const remainFileSizeTip = <div className='tip' style={{ margin: '8px 0' }}>( 剩餘可選文件大小約{this.remainFileSize(files)} )</div>
文件的壓縮和解析
本來是打算把多個文件壓縮成一個文件傳給後端,但是移動端壓縮報錯,只能放棄,樣例代碼如下:
// 壓縮zip文件
genZipFiles = async () => {
const { files } = this.state
const zip = new JSZip()
files.forEach(file => zip.file(file.name, file))
const content = await zip.generateAsync({ type: "blob" })
return content
}
// 解壓文件
parseZipFiles = async (content) => {
if (isEmpty(content)) return
const zip = new JSZip()
const zipfile = await zip.loadAsync(content)
if (zipfile) {
const { files } = zipfile
const filesList = Object.keys(files).map(fileName => {
const file = files[fileName]
file.size = file._data.uncompressedSize
return file
})
this.setState(
{ files: filesList }
)
}
}
遺留待解決問題
ios端的軟鍵盤擋住input還未解決