一次失败的尝试,h5+Api 结合 react,webpack,同时生成android 、ios、h5端代码

栏目: 编程工具 · 发布时间: 6年前

内容简介:vue 项目写多了,觉得不能一成不变,想去外面的世界看看。所以尝试了一把react开发,嗯~ o(项目地址:开始解决上面提到的问题

vue 项目写多了,觉得不能一成不变,想去外面的世界看看。所以尝试了一把react开发,嗯~ o(  ̄▽ ̄ )o 就在想做一个webApp吧,脚手架也自己搭一个吧。然后脚手架搭建完,项目可以正式开始了,自己又出幺蛾子,为什么不能打包成App呢,之前接触过cordova平台打包App,这次决定用HBuilder h5+api 开发一个同时打包多页面App应用 和 SPA单页面应用。(小程序,哎 野心太大,但是实力不允许),在抹平平台差异后,可以愉快的写代码了,但是在我看了uni-app文档后,觉得自己写的好原始。哎。虽然写的喽了点,但是对HBuilder h5+api 有了一定的了解,在看uni-app 文档时可以在脑海里模拟它接口,功能的实现了。还有他封装的功能为了实现多平台对h5+api的简化。

项目地址: github.com/wangyaxinon…

由于是同时开发多页面应用和单页面应用,所以我再开发之前考虑到了如下问题:

  1. h5+api App多页面应用 和 react 单页面应用跳转问题,以及跳转页面所需的参数。
  2. h5+api App 支持离线应用,在离线状态如何获取上次有网的数据,以及离线提交。react webApp 不支持离线。
  3. 如何在不同的终端打包不同的代码(例如:在APP端打包扫码功能模块,在h5端不打包此模块代码),以及在react jsx中根据不同的平台,渲染不同的代码
一次失败的尝试,h5+Api 结合 react,webpack,同时生成android 、ios、h5端代码

开始解决上面提到的问题

  1. APP端页面跳转是通h5+api过创建一个Webview ( plus.webview.create )窗口,并且设置创建的窗口显示 ( plus.webview.show ),在显示后的回调中设置上一个webview隐藏。但是在react 单页面应用中是使用react-router 。所以我封装了一个适配模式,在ios、android 平台打包app页面切换代码,在h5平台打包 react-router跳转页面封装的代码。 (process.env.platform 后面再说)
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;
复制代码

app端跳转代码

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

export function push({path,titleViewOptions,AnimationType}){
    if(_openw || !path){return;}  // 防止快速点击
    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(()=>{
        _openw=null;
    })
   
}
export function go(num){
    utils.go()
}
function isLogin() {
    var userDetail = utils.getItem("userDetail");
    if(userDetail &&userDetail.token){
        return true;
    }else{
        return false;
    }
}


复制代码

web端跳转代码

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
}  
复制代码

2.h5+api App 支持离线应用,在离线状态如何获取上次有网的数据,以及离线提交。react webApp 不支持离线。解决方案: APP端离线的一些静态资源如 html css js img font 都是打包在应用内的可以直接离线访问,但是比如一个商品列表的数据是从后台请求过来的。在离线的情况下是肯定拿不到数据的。但是我们可以借助h5+api (sqlite本地数据库实现此功能 )。原理是在初始化的时候创建一个表,第一次请求的时候将请求接口和数据插入表中,以后的每次请求都是跟新表中当前接口的数据。

import config  from "./config.js"
import SQLite from "@/platform/storage/app.js"

var types = {};
types['0'] = "未知";
types['1'] = "未连接网络";
types['2'] = "有线网络";
types['3'] = "WiFi网络";
types['4'] = "2G蜂窝网络";
types['5'] = "3G蜂窝网络";
types['6'] = "4G蜂窝网络";
  
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;
    //无网络时或者cache,读取数据库中上一次请求成功的数据
    if(CurrentType==='未知' || CurrentType==='未连接网络' && 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{
            }
            return nowData || {};
        })
    }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}'`)
        .then((data)=>{
            if(data && data.length){
                //跟新表中数据
                SQLite.executeSql(`UPDATE database SET data = '${JSON.stringify(res)}', time = '${new Date()/1}' WHERE key = '${options.url}'`)
            }else{
                //第一次请求数据
                SQLite.executeSql(`insert into database values('${options.url}','${JSON.stringify(res)}','${new Date()/1}')`)
            }
        })
       

    }
}

复制代码

3.区分不同的平台,我是借助webpack 实现的,在package.json scripts 传入一个参数 --ios --wx --android --web,同时在根目录下的 config/webpack.config.base.js 文件中获取这些参数,在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-env NODE_ENV=development webpack --w  --ios --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"
  },
复制代码
//读取命令行传入的参数
var parms = process.argv;
var DefinePlugin = null
if(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
复制代码

项目的核心来了:

单页面还好,多页面视图的切换,底部的导航,顶部的titleNView 子视图的创建 这些调用的都是原生功能。所以我做了一个配置,不必每次都该源代码,按规则修改配置视图也跟着去变化。这些配置在初始化的时候去创建。

一次失败的尝试,h5+Api 结合 react,webpack,同时生成android 、ios、h5端代码
一次失败的尝试,h5+Api 结合 react,webpack,同时生成android 、ios、h5端代码

比如初始化页面

initSubPages() {
        if(routeConfig){
            for(var key in routeConfig){
                var children = routeConfig[key].children;
                var parentConfig = routeConfig[key];
                if(children && children.length){
                    //默认打开的第一个首页
                    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)){
                               
                                // 初始化第一个子页面
                                if(idx ==0 ){
                                    utils.setStatusBar(item);
                                    var sub = plus.webview.create( page, page, item.WebviewStyles,meta);
                                    // append到当前父webview
                                    self.append(sub);
                                    //添加第一个子页面进入栈
                                    utils.setItem('pagesList',[page])
                                }
                            }
                        })
                    }else{
                        //其他在需要显示的时候创建
                        // 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到父webview
                        //         parent.append(sub);
                        //         // 初始化隐藏
                        //         sub.hide();
                               
                        //     }
                        // })
                    }
                }else{
                    //其他在需要显示的时候创建
                    // var parentPage = routeConfig[key].MultiPath;
                    // var parent = plus.webview.create( parentPage, parentPage);
                    // parent.hide();
                }
            }
        }
        
    },
复制代码

初始化所有路由页面配置的底部按钮

//递归路由配置,创建原生底部导航
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{
                                //未创建页面在切换时加载View
                            }
                        })
                    }
                    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{
                                //未创建页面在切换时加载View
                            }
                        })
                    }
                    var children = item.children;
                    if(children && children.length){
                        drawAllNative(children);
                    }
                })
            }    
        }
    },
复制代码

h5+api切换页面

//切换页面
    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('已存在');
            }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到当前父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('初次创建');
            }
            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)
                        }
                    })
                }
            }
            
            //隐藏当前 除了第一个父窗口
            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 = 上一次打开的页面
                }else{

                }
                pagesList.push(targetPage)
                utils.setItem('pagesList',pagesList)
                activePage = pagesList[pagesList.length-2] //activePage = 上一次打开的页面
                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');
                    }
                }
            }
        })
    },
复制代码

写的废话有点多。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Web Application Hacker's Handbook

The Web Application Hacker's Handbook

Dafydd Stuttard、Marcus Pinto / Wiley / 2011-9-27 / USD 50.00

The highly successful security book returns with a new edition, completely updated Web applications are the front door to most organizations, exposing them to attacks that may disclose personal infor......一起来看看 《The Web Application Hacker's Handbook》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具