H5頁面對接React Native App

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頁面通訊

通訊總共包含如下三個步驟

  1. H5頁面在組件掛載之後,註冊監聽程序,以便接收App發送的數據
  2. 通過window.postMessage方法給App發送請求
  3. App在接受到H5的請求之後,解析命令,返回H5所需要的數據
  4. 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還未解決

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章