I have written many VUE projects, and I feel that I cannot stay the same. I want to go to the outside world. So I tried react development. ̄ del  ̄O just want to make a webApp and build a scaffold of your own. Then the scaffolding was built and the project was ready to start. Why can’t I package it as an App? I have heard about cordova packaging App before, but this time I decided to use HBuilder H5 + API to develop a multi-page App and SPA single-page App. I was happy to write code after the differences were erased, but after looking at the uni-app documentation, I felt that I had written so primitive. Ah. HBuilder H5 + API is a bit off, but I have a certain understanding of it. I can simulate its interface and function realization in my mind when reading the uni-app document. It also encapsulates functionality to simplify the H5 + API for multiple platforms.

Project address: github.com/wangyaxinon…

Since I was developing the multi-page app and the single-page app at the same time, I considered the following questions before redeveloping:

  1. H5 + API multi-page application and React single-page application redirect problems and parameters required for page redirect.
  2. H5 + API App supports offline applications, how to obtain the last online data in offline state, and offline submission. React webApp does not support offline.
  3. How to package different code on different terminals (e.g. package scan function module on APP but not on H5) and react JSX to render different code for different platforms

Start solving the problems mentioned above

  1. APP page jump is to create a Webview (plus.webview.create) window through H5 + API, and set the display of the created window (plus.webview.show), and set the last Webview hidden in the callback after display. However, the React single-page application uses the React -router. Therefore, I packaged an adaptation mode, which packaged the code for switching app pages on ios and Android platforms, and packaged the code for react-Router redirect page on H5 platform. (Process.env.platform later)
   if(process.env.platform==='ios' || process.env.platform==='Android') {var router = require(`./app.js`)}else{
       var router = require(`./web.js`)
   }
   router.default && (router = router.default)
   export default router;
Copy the code

App side jump code

import allRouter from "@/utils/route.js"
import utils from "@/utils/init.js"
var _openw;

export function push({path,titleViewOptions,AnimationType}){
    if(_openw || ! path){return; }// Prevent quick clicks
    if(path==="/login") {if(isLogin()){
            return; }}if(path==='/' ||path==='/index' ){
        path = `index.html`
    }else if(path[0= = ='/') {var pathArr = path.split('/');
        var newpath = pathArr[pathArr.length- 1]
        path = `/pages/${newpath}.html`
    }
    
    utils.changePage(path)
    .then((a)= >{
        _openw=null; })}export function go(num){
    utils.go()
}
function isLogin() {
    var userDetail = utils.getItem("userDetail");
    if(userDetail &&userDetail.token){
        return true;
    }else{
        return false; }}Copy the code

Jump code on the Web side

import { createHashHistory } from 'history'
var history = createHashHistory();
var push = function (data){
    console.log(arguments);
    return history.push(data.path)
}
var go = (num) = >{
    return history.go(num)
}
export {
    push,
    go
}  
Copy the code

2. H5 + API App supports offline applications, and how to obtain the last online data and submit it offline in offline state. React webApp does not support offline. Solution: Some static resources such as HTML, CSS, JS, IMG font are packaged in the APP and can be directly accessed offline, but the data of a list of goods, for example, is requested from the background. You can’t get data offline. But we can use the H5 + API (sqLite local database for this). The principle is to create a table at initialization time, the first request to insert the interface and data into the table, each subsequent request is with the current interface data in the new table.

var qs = require('qs')
import config  from "./config.js"
import SQLite from "@/platform/storage/app.js"

var types = {};
types['0'] = "Unknown";
types['1'] = "Not connected to network";
types['2'] = "Wired Network";
types['3'] = "WiFi network";
types['4'] = "2G Cellular network";
types['5'] = "3G Cellular Network";
types['6'] = "4G Cellular";
  
function get(options){
    if(! options.url){return 
    }
    if(! options.type){ options.type ='get';
    }
    
    if(Object.prototype.toString.call(options.data)! = ="[object String]"){
        options.data = qs.stringify(options.data)
    }
    
    return new Promise((resolve,reject)=>{
        var xhr = new plus.net.XMLHttpRequest();
        xhr.onreadystatechange = function () {
            if(xhr.readyState==4){
                if ( xhr.status == 200 ) {
                    resolve(xhr.responseText );
                } else {
                    reject(xhr.readyState );
                }
            }
        }
        

        xhr.open( options.type,  `${options.url}?${options.data}`); xhr.send(); })}function post(options){
    if(! options.url){return 
    }
    if(Object.prototype.toString.call(options.data)! = ="[object String]"){
        options.data = JSON.stringify(options.data)
    }
    return new Promise((resolve,reject)=>{
        var xhr = new plus.net.XMLHttpRequest();
        xhr.onreadystatechange = function () {
            if(xhr.readyState==4){
                if ( xhr.status == 200 ) {
                    resolve(xhr.responseText );
                } else {
                    reject(xhr.readyState );
                }
            }
        }
        xhr.open( options.type, `${options.url}?${options.data}`); xhr.setRequestHeader('Content-Type'.'application/x-www-form-urlencoded'); xhr.send(); })}export default function (options){
    options.url = config.baseUrl+options.url;
    var CurrentType = types[plus.networkinfo.getCurrentType()];
    options.cache = options.cache || true; // If there is no network or cache, read the data of the last successful request from the databaseif(CurrentType==='unknown' || CurrentType==='Not connected' && options.cache){
        return SQLite.selectSQL(`select * from database WHERE key = '${options.url}'`)
        .then((data)=>{
            var nowData;
            if(data && data.length){

                nowData = data[0].data;
            }
            try{
                nowData = JSON.parse(nowData)
            }catch{
            }
            returnnowData || {}; })}else{
        if(options.type==='get'| |! options.type){return Promise.race([
                get(options),
                new Promise((resolve, reject) => {
                    setTimeout(() => reject('request timeout'), config.timeout ? config.timeout : 30 * 1000);
                })
            ]).then((res)=>{
                try {
                    res = JSON.parse(res);
                }
                catch(err) {
                }
                setsqLite(`UPDATE database SET data = '${JSON.stringify(res)}', date = '${new Date()/1} WHERE key = '${options.url}'`) return res; }) }else{ return Promise.race([ post(options), new Promise((resolve, reject)=>{ setTimeout(() => reject('request timeout'), config.timeout ? config.timeout : 30 * 1000); }) ]).then((res)=>{ try { res = JSON.parse(res); } catch(err) { } setsqLite({ res, options }) return res; }) } } function setsqLite({options,res}) { SQLite.selectSQL(`select key from database WHERE key = '${options.url}Sqlite. executeSql(' UPDATE database SET data = '). Then ((data)=>{if(data && data.length){// UPDATE database SET data = '${JSON.stringify(res)}', time = '${new Date()/1}' WHERE key = '${options.url}ExecuteSql (' insert into database values(')${options.url}', '${JSON.stringify(res)}', '${new Date()/1}') ')}})}}Copy the code

3. Distinguish different platforms, I use Webpack to achieve, In the package. The json scripts into a parameter – ios – wx – android – web, at the same time in the root directory of the config/webpack config. Base. The js file to obtain these parameters, Set global variables in webpack.definePlugink

"scripts": {
    "build-ios": "cross-env NODE_ENV=production webpack --ios --config configMulti/webpack.config.js"."build-Android": "cross-env NODE_ENV=production webpack --Android --config configMulti/webpack.config.js"."build-web": "cross-env NODE_ENV=production webpack --web --config configSPA/webpack.config.js"."build-wx": "cross-env NODE_ENV=production webpack --wx --config configSPA/webpack.config.js"."dev-web": "Cross-env NODE_ENV=development webpack-dev-server --web --inline --host 0.0.0.0 --config configSPA/webpack.config.dev.js"."dev-ios": "Cross - the env NODE_ENV = development webpack - w - ios - the inline - host 0.0.0.0 - config configMulti/webpack config. Dev. Js." "."dev-Android": "Cross-env NODE_ENV=development webpack-dev-server --Android --inline --host 0.0.0.0 --config configMulti/webpack.config.dev.js"."dev-wx": "Cross-env NODE_ENV=development webpack-dev-server --wx --inline --host 0.0.0.0 --config configSPA/webpack.config.dev.js"
  },
Copy the code
Var parms = process.argv; var parms = process.argv; var DefinePlugin = nullif(parms.includes('--ios')){
    DefinePlugin = {
        'process.env': {
            platform: '"ios"'}}}if(parms.includes('--Android')){
    DefinePlugin = {
        'process.env': {
            platform: '"Android"'}}}if(parms.includes('--wx')){
    DefinePlugin = {
        'process.env': {
            platform: '"wx"'}}}if(parms.includes('--web')){
    DefinePlugin = {
        'process.env': {
            platform: '"web"'
        }
    }
} 
// DefinePlugin.NODE_ENV = '"development"'
config.plugins.push(
    new webpack.DefinePlugin(DefinePlugin),
)
module.exports = config
Copy the code

Here comes the core of the project:

Single page is fine, multi-page view switching, navigation at the bottom, creation of titleNView subviews at the top all invoke native functions. So I make a configuration, and instead of having to change the source code every time, I follow the rules and the configuration view changes along with it. These configurations are created during initialization.

For example, initialize the page

initSubPages() {
        if(routeConfig){
            for(var key in routeConfig){
                var children = routeConfig[key].children;
                var parentConfig = routeConfig[key];
                if(children && children.length){
                    // The first home page opened by default
                    if(key==='index') {var self = plus.webview.currentWebview();
                        var titleNView = self.getTitleNView();
                        console.log('titleNView')
                        console.log(JSON.stringify(titleNView))
                        children.forEach((item,idx) = >{
                            var page = item.MultiPath;
                            var meta = item.meta || {};
                            if(! plus.webview.getWebviewById(page)){// Initialize the first child page
                                if(idx ==0 ){
                                    utils.setStatusBar(item);
                                    var sub = plus.webview.create( page, page, item.WebviewStyles,meta);
                                    // Append to the parent webView
                                    self.append(sub);
                                    // Add the first subpage to the stack
                                    utils.setItem('pagesList',[page])
                                }
                            }
                        })
                    }else{
                        // Others are created as needed
                        // var parentPage = routeConfig[key].MultiPath;
                        // var parent = plus.webview.create( parentPage, parentPage);
                        // children.forEach((item)=>{
                        // var page = item.MultiPath;
                        // var meta = item.meta
                        // if(! plus.webview.getWebviewById(page)){
                        // var sub = plus.webview.create( page, page, utils.subPageStyle,meta);
                        // // append to the parent WebView
                        // parent.append(sub);
                        // // Initialize hide
                        // sub.hide();
                               
                        / /}
                        // })}}else{
                    // Others are created as needed
                    // var parentPage = routeConfig[key].MultiPath;
                    // var parent = plus.webview.create( parentPage, parentPage);
                    // parent.hide();}}}},Copy the code

Initializes the bottom button of all routing page configurations

// Recursive routing configuration, create native bottom navigation
initAllTabBar() {
        if(routeConfig){
            drawAllNative(routeConfig);
        }
        function drawAllNative(routeConfig) {
            if(Object.prototype.toString.call(routeConfig)==="[object Object]") {for(var key in routeConfig){
                    var View = routeConfig[key].View;
                    if(View && View.length){
                        View.forEach((item,idx) = >{
                            var nowView = new plus.nativeObj.View(item.id, item.styles, item.tags);
                            var parentWebview = plus.webview.getWebviewById(routeConfig[key].MultiPath==='/index.html'? utils.indexId:routeConfig[key].MultiPath);if(parentWebview){
                                parentWebview.append(nowView)
                            }else{
                                // Load the View when switching uncreated pages}})}var children = routeConfig[key].children;
                    if(children && children.length){ drawAllNative(children); }}}else if(Object.prototype.toString.call(routeConfig)==="[object Array]"){
                routeConfig.forEach((item,idx) = >{
                    var View = item.View;
                    if(View && View.length){
                        View.forEach((item,idx) = >{
                            var nowView = new plus.nativeObj.View(item.id, item.styles, item.tags);
                            var parentWebview = plus.webview.getWebviewById(item.MultiPath);
                            if(parentWebview){
                                parentWebview.append(nowView)
                            }else{
                                // Load the View when switching uncreated pages}})}var children = item.children;
                    if(children && children.length){ drawAllNative(children); }})}}},Copy the code

H5 + API switch page

// Switch pages
    changePage(targetPage) {
        
        return new Promise((resolve,reject) = >{
            var pagesList = utils.getItem('pagesList')
           
            var activePage =  pagesList[pagesList.length- 1];
            if(targetPage===activePage){
                return;
            }
            
            if($.isEmptyObject(utils.MuLti)){
                utils.MuLti = getMuLtiConfig(routeConfig)
            }else{}var targetPageWebview = plus.webview.getWebviewById(targetPage)
            if(targetPageWebview){
                plus.webview.show(targetPage , (utils.MuLti[targetPage].AnimationTypeShow || 'auto'), 300,()=>{
                    hidePage()
                });
                console.log('pre-existing');
            }else{
                // plus.webview.open(targetPage, targetPage, {}, 'slide-in-right', 200);
                var nowConfig = utils.MuLti[targetPage];
                var meta = nowConfig.meta || {};
                console.log('parentPath : '+nowConfig.parentPath)
                if(nowConfig.parentPath){
                    var parentView = plus.webview.getWebviewById(nowConfig.parentPath=="/index.html"? utils.indexId:nowConfig.parentPath);var sub = plus.webview.create( nowConfig.MultiPath, nowConfig.MultiPath, nowConfig.WebviewStyles,meta);
                    // Append to the parent webView
                    parentView.append(sub);
                    addNowPageView();
                   
                    plus.webview.show(sub, (nowConfig.AnimationTypeShow || 'auto'), 300,()=>{
                        hidePage()
                    });
                }else{
                   
                    var ws = plus.webview.create( targetPage, targetPage, nowConfig.WebviewStyles ,meta);
                    addNowPageView();
                    
                    plus.webview.show(ws, (nowConfig.AnimationTypeShow || 'auto'), 300,()=>{
                        hidePage()
                    });
                }
                
                console.log('First creation');
            }
            utils.setStatusBar(utils.MuLti[targetPage]);
            function addNowPageView(){
                var nowConfig = utils.MuLti[targetPage];
                if(nowConfig.View && nowConfig.View.length){
                    nowConfig.View.forEach((item) = >{
                        var nowView = new plus.nativeObj.View(item.id, item.styles, item.tags);
                        var parentWebview = plus.webview.getWebviewById(nowConfig.MultiPath);
                        if(parentWebview){
                            parentWebview.append(nowView)
                        }
                    })
                }
            }
            
            // Hide the current window except for the first parent
            function hidePage() {
                
                resolve('success')
                var pagesList = utils.getItem('pagesList')
                if(utils.MuLti[targetPage] && utils.MuLti[targetPage].meta && utils.MuLti[targetPage].meta.ignore){
                    //activePage = pagesList[pageslist.length-1] //activePage = Last opened page
                }else{

                }
                pagesList.push(targetPage)
                utils.setItem('pagesList',pagesList)
                activePage = pagesList[pagesList.length2 -] //activePage = Last opened page
                if(activePage ! == plus.webview.getLaunchWebview().id) {var AnimationTypeClose = utils.MuLti[activePage] ? utils.MuLti[activePage].AnimationTypeClose :null
                    if(utils.MuLti[activePage] && utils.MuLti[activePage].meta && utils.MuLti[activePage].meta.leaveClose) {
                        plus.webview.close(activePage,AnimationTypeClose || 'auto');
                    }else{
                        plus.webview.hide(activePage,AnimationTypeClose || 'auto'); }}}})},Copy the code

It’s a little too much crap.