美文网首页JS 与 iOSiOS调试技巧前端学习
iOS-Cordova集成开发,已有项目集成cordova

iOS-Cordova集成开发,已有项目集成cordova

作者: afyylong | 来源:发表于2018-02-02 10:01 被阅读511次

    项目组准备开发一个APP,要求Android和iOS端页面完全一致,除了一个页面跟业务相关的不同,其他界面基本一致,因此,萌生一个想法,关于webAPP的想法。于是乎苦逼的我们开始调研可行性以及整体的方案流程。为了达到除了业务数据页面用web,其他页面全是原生APP的作用。其中,关于业务的web页面需要调用原生的相机,相册,地理位置,麦克风,扬声器,扫描二维码等一系列功能,这就涉及到js与原生交互的问题了。
    我们iOS端提出的方案是直接用WebView或者用WKWebView嵌套在实现web与原生的交互就可以了,不过安卓同事说安卓因为其平台多样性和特殊性这个就不兼容而且可行性交差,列出了集中方案,最终确定双方都用Cordova实现该功能。然后,开始了iOS端关于Cordova的学习之路(如果已经集成,请下翻到5节)

    1 Cordova简介

    Cordova前身是phonegap,而PhoneGap是Nitobi软件公司2008年推出的一个框架,旨在弥补web和iOS之间的不足,使得web和iPhone SDK之间的交互更容易。后来又加入了Android SDK 和BlackBerry SDK,再然后又陆续加入了更多的平台。但是在2011年,Nitobi公司被Adobe收购,PhoneGap也被提交到Apache Incubator。由于Adobe现在拥有PhoneGap商标,PhoneGap v2.0版产品就更名为Apache Cordova。

    据说Cordova是Nitobi团队当时坐落的街道名称,用此名来纪念Nitobi团队的贡献。Apache Cordova是从PhoneGap中抽出的核心代码,是驱动PhoneGap的核心引擎。

    1.2 Cordova的工程结构

    有下图可以看出,关于Cordova的工程结构以及与Native API之间的关系:


    cordova机构图.png

    由上图可以看出,其实Cordova的使用分为上面几个框架结构,在Native与web之间交互。

    2 Cordova安装

    2.1 准备工作

    因为本人从事iOS工作,所以只阐述在Mac端安装Cordova的步骤及解释,至于windows下安装Cordova的步骤,这里不再赘述。
    (1)首先,你要有一台Mac电脑,如果没有,那么,你就把这篇文章关了吧,看了也没什么用。
    (2)然后你需要安装xcode,当然,你要开发,这一点必不可少。
    (3)你需要申请证书,然后在xcode中设置配置文件开发者账号...(可后面再做)。
    (4)好了,你可以开始安装Cordova了。

    2.1 安装Node.js

    要安装Cordova,需要先安装Node.js在Node.js官网,上下载并安装,下载好以后,一步一步点击下去就好。如果你实在不会,可以参考文章Mac下安装Node.js

    nodejs.png

    2.2 安装git

    git一般不用安装,osx和linux都自带git,可以在命令行输入git --version检查一下。如果没有git,需要到git官网下载安装一个git客户端。

    git.png

    2.3 使用node.js的依赖包管理工具npm来进行cordova安装

    接下来,在终端输入命令:

     sudo npm install -g cordova
    
    过程可能稍微有点枯燥和漫长,请耐心等待,其实需要输入的安装命令只有npm install -g cordova,之所以输入sodu,是因为有点Mac直接输入前一句代码是安装不起的,安装效果如下图: install.png

    为了写文章所以第二次安装,第一次安装的效果不记得了,哈哈~

    3 创建你的第一个Cordova工程

    3.1 新建项目

    继续,打开终端,cmd切换目录到工作目录下,输入以下命令,同样,可能需要点时间来完成

     cordova create Demo com.cordova.demo.hello HelloWorld 
    

    参数描述:
    Demo(参数是必填):将为你的项目生成一个Demo目录 www子目录是应用程序的主页,以及各种资源(css,js,img),遵循共同的web开发文件命名规范。这些资源将存储在设备上的本地文件系统,而不是远程服务。config.xml文件包含重要的需要生成和分发应用程序的元数据。
    com.cordova.demo.hello(参数可选):App ID,如果不填写这个参数,第三个参数就要省略,默认值是 io.cordova.hellocordova,但建议你填写一个适当的值。
    HelloWorld(参数可选):应用程序的项目名 这个参数的默认值是 HelloCordova,但建议你填写一个适当的值。

    3.2 添加平台支持

    所有后续命令在项目的目录中进行,可在该项目任何子目录中,cmd切换到项目目录下:

      cd Demo
    

    在构建项目之前,你需要指定一组目标平台。你能够运行这些命令取决于您的机器是否支持每一个SDK,合理是否已安装SDK。在MAC上运行命令:

     cordova platform add ios
    

    如果需要查看Cordova支持平台以及已经添加的平台,终端输入命令:

      cordova platforms ls
    

    3.3 添加插件

    根据项目具体功能需要,可添加插件以简单方便调用原生接口,如需添加插件,可以去Cordova插件库 搜索需要的插件:

     cordova plugin add com.phonegap.plugins.barcodescanner
     cordova plugin add org.apache.cordova.file-transfer
     cordova plugin ls
    

    并非所有的插件都是全平台支持的,有些可能只支持安卓,也有的可能同时支持安卓和iOS,所以在多平台开发时,请慎重选择插件。个人建议,可自定义插件类,可参考官网Cordova自定义插件

    3.4 迭代项目

     // 在工程目录下运行下面的命令来构建项目:
      cordova build  
     // 或者,指定生成iOS平台代码项目:
      cordova platform add iosiOS_Cordova
    

    4 运行项目

    4.1 打开工程

    步骤走完第三章,那么,一个简单的Cordova项目就已经搭建完成了,现在,我们运行下我们工程。打开目录下

    /Users/****/Desktop/测试cordova/Demo/platforms/ios
    

    为了避免引起混淆,建议移除掉下图中两个文件/文件夹引用(应用哦):

    config.png

    4.2 Events Cordova生命周期事件

    生命周期函数 释义
    deviceready 当Cordova加载完成会触发
    pause 当应用程序进入到后台时触发
    resume 应用程序从后台进入到前台会触发

    4.3 Plugin APIs 自定义Plugin方法

    这里看Api就可以的。

    到此cordova环境的搭建集成基本完成了。有一些项目已经完成了突然要集成cordova,该怎么办呢?往下看。

    5 将Cordova本地相关文件copy到已存的项目中

    5.1 找到相关路径,复制文件到工程目录

    首先,进入到上一篇文章中创建的Cordova项目的路径中/Users/****/Desktop/Demo/platforms/ios,找到下面需要copy的4个文件夹( CordovaLib ,cordova, www, platform_www

    )copy到工程项目的根路径中,如下 copy.png

    5.2 拷贝对应的config.xml文件到对应的工程目录

    然后将下面图中所示的config.xml文件copy到需要的iOS项目路径文件夹内,如图:


    config

    6 配置相关参数

    6.1 打开工程,进入工程TARGETS -> Build Phases 下,然后入下图所示,点击①中的按键,创建New Run Script Phase,②为创建的Run Script:
    image
    6.2 将下图示意图中,①原名称Run Script修改为Copy www directory;然后讲②中的选项去掉,最后将下面的代码字段复制到③中:
    NODEJS_PATH=/usr/local/bin; NVM_NODE_PATH=~/.nvm/versions/node/`nvm version 2>/dev/null`/bin; N_NODE_PATH=`find /usr/local/n/versions/node/* -maxdepth 0 -type d 2>/dev/null | tail -1`/bin; XCODE_NODE_PATH=`xcode-select --print-path`/usr/share/xcs/Node/bin; PATH=$NODEJS_PATH:$NVM_NODE_PATH:$N_NODE_PATH:$XCODE_NODE_PATH:$PATH && node cordova/lib/copy-www-build-step.js
    
    如图: image
    6.3 在工程 Build Settings -> Other Linker Flags 中添加-ObjC -all_load
    image
    6.4 Add Files to ... -> CordovaLib.xcodeproj
    image
    6.5 Add Files to ... -> config.xml
    image
    6.6 Add Files to ... -> www文件夹
    image
    6.7 Schemes按照下图配置,然后编译一次程序(command+R),最后导入下面两项

    Build Phases -> Target Dependencies -> CordovaLib

    Build Phases -> Link Binary With Libraries -> libCordova.a image
    6.8 Schemes按照下图配置,将"ViewController.h" 文件改为:
    #import <Cordova/CDVViewController.h>
    #import <Cordova/CDVCommandDelegateImpl.h>
    #import <Cordova/CDVCommandQueue.h>
    @interface ViewController : CDVViewController
    @end
    

    至此,Cordova嵌入已存的开发项目就已经完成了,运行程序就可看到工程中wwww文件目录下,index.html文件中的网页信息了。只需要将该文件内容,改为公司需要的网页内容即可。具体交互以及自定义插件,将在后面文章中介绍。

    7 自定义插件

    7.1 在config.xml文件中加入下面代码:

    <feature name="YourPluginName"> 
        <param name="ios-package" value="Plugin" /> 
        <param name="onload" value="true" /> 
    </feature>
    
    config.png
    3.1 创建一个Plugin类,实现下面的方法

    下面介绍一个简单的web端调用原生手机相机以及选择相册图片并返回web显示的自定义插件调用:

    • (1)YanSYPlugin.h 的实现
    #import <Cordova/CDVPlugin.h>
    @interface Plugin : CDVPlugin
    {
        UIImagePickerController *_imagePickerController;//相机
    }
    - (void)myMethod:(CDVInvokedUrlCommand *)command;
    @end
    
    • (2)YanSYPlugin.m 的实现
    #import "Plugin.h"
    
    typedef void(^imgBlock)(NSString *data);
    @interface Plugin() <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
    @property (copy, nonatomic) imgBlock myBlock;
    @end
    
    @implementation Plugin
    
    - (void)myMethod:(CDVInvokedUrlCommand *)command {
        [self.commandDelegate runInBackground:^{
            NSString* myarg = [command.arguments objectAtIndex:0];
            if (myarg !=nil) {
                [self switchMethodWithName:myarg andCommand:(CDVInvokedUrlCommand*)command];
            }else{
                return ;
            }
        }];
    }
    
    // 根据方法选择判断调用具体内容
    - (void)switchMethodWithName:(NSString *)name andCommand:(CDVInvokedUrlCommand*)command{
        _imagePickerController = [[UIImagePickerController alloc] init];
        _imagePickerController.delegate = self;
        _imagePickerController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
        _imagePickerController.allowsEditing = YES;
        __block CDVPluginResult* pluginResult = nil;
        NSString * standbyJSStr = [NSString stringWithFormat:@"%@",@"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAVCAYAAACt4nWrAAAEDWlDQ1BJQ0MgUHJvZmlsZQAAOI2NVV1oHFUUPrtzZyMkzlNsNIV0qD8NJQ2TVjShtLp/3d02bpZJNtoi6GT27s6Yyc44M7v9oU9FUHwx6psUxL+3gCAo9Q/bPrQvlQol2tQgKD60+INQ6Ium65k7M5lpurHeZe58853vnnvuuWfvBei5qliWkRQBFpquLRcy4nOHj4g9K5CEh6AXBqFXUR0rXalMAjZPC3e1W99Dwntf2dXd/p+tt0YdFSBxH2Kz5qgLiI8B8KdVy3YBevqRHz/qWh72Yui3MUDEL3q44WPXw3M+fo1pZuQs4tOIBVVTaoiXEI/MxfhGDPsxsNZfoE1q66ro5aJim3XdoLFw72H+n23BaIXzbcOnz5mfPoTvYVz7KzUl5+FRxEuqkp9G/Ajia219thzg25abkRE/BpDc3pqvphHvRFys2weqvp+krbWKIX7nhDbzLOItiM8358pTwdirqpPFnMF2xLc1WvLyOwTAibpbmvHHcvttU57y5+XqNZrLe3lE/Pq8eUj2fXKfOe3pfOjzhJYtB/yll5SDFcSDiH+hRkH25+L+sdxKEAMZahrlSX8ukqMOWy/jXW2m6M9LDBc31B9LFuv6gVKg/0Szi3KAr1kGq1GMjU/aLbnq6/lRxc4XfJ98hTargX++DbMJBSiYMIe9Ck1YAxFkKEAG3xbYaKmDDgYyFK0UGYpfoWYXG+fAPPI6tJnNwb7ClP7IyF+D+bjOtCpkhz6CFrIa/I6sFtNl8auFXGMTP34sNwI/JhkgEtmDz14ySfaRcTIBInmKPE32kxyyE2Tv+thKbEVePDfW/byMM1Kmm0XdObS7oGD/MypMXFPXrCwOtoYjyyn7BV29/MZfsVzpLDdRtuIZnbpXzvlf+ev8MvYr/Gqk4H/kV/G3csdazLuyTMPsbFhzd1UabQbjFvDRmcWJxR3zcfHkVw9GfpbJmeev9F08WW8uDkaslwX6avlWGU6NRKz0g/SHtCy9J30o/ca9zX3Kfc19zn3BXQKRO8ud477hLnAfc1/G9mrzGlrfexZ5GLdn6ZZrrEohI2wVHhZywjbhUWEy8icMCGNCUdiBlq3r+xafL549HQ5jH+an+1y+LlYBifuxAvRN/lVVVOlwlCkdVm9NOL5BE4wkQ2SMlDZU97hX86EilU/lUmkQUztTE6mx1EEPh7OmdqBtAvv8HdWpbrJS6tJj3n0CWdM6busNzRV3S9KTYhqvNiqWmuroiKgYhshMjmhTh9ptWhsF7970j/SbMrsPE1suR5z7DMC+P/Hs+y7ijrQAlhyAgccjbhjPygfeBTjzhNqy28EdkUh8C+DU9+z2v/oyeH791OncxHOs5y2AtTc7nb/f73TWPkD/qwBnjX8BoJ98VVBg/m8AAAEFSURBVDgRY/z969//GO9HDAd3f2agNmDKjX9KE4NBDmXasPwDTgcX14sxPP+vg4JBYsQCJmIVkqNu1HCsoUbTYGEJiBBgCE8SZLCw5WTg4CDeLkt7bgYrB24UFx878JXh+MGvcDGW6ctl4BxSGCCDSxpQk2VPwysUw4l3Kik2Q9XS1HAWkCV//vxn2LL2M8Od6z9Q3AfyOnq4oiggwGF5++YvQ4TzA4Yrl75jUSpGkeFMM3re4DAYi10kCjHt3kb9ohbmBqZHN3/B2FSnmdg4GaluKMxAJitn1FwGk6AGzZRXKcrAykId16Obw2RgwsmwYJMcg4omBwOlVniH8jFISbPBPQ0Ay3NHXHSKRhIAAAAASUVORK5CYII="];
    
        if ([name isEqualToString:@"调用相机"]) {
            [self selectImageFromCameraWithBlock:^(NSString *data) {
                // 因当前转码格式为iOS原生转码,web端无法解析 ,所以百度了一波图片转码成功后复用,若转码格式正确 可直接上传字符串
                // NSString *jsStr = [NSString stringWithFormat:@"data:image/png;base64,%@",data];
                // 使用备用字符串上传
                pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:standbyJSStr];
                [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
            }];
        }else if ([name isEqualToString:@"查看相册"]){
            [self selectImageFromAlbumWithBlock:^(NSString *data) {
                // 因当前转码格式为iOS原生转码,web端无法解析 ,所以百度了一波图片转码成功后复用,若转码格式正确 可直接上传字符串
                // NSString *jsStr = [NSString stringWithFormat:@"data:image/png;base64,%@",data];
                // 使用备用字符串上传
                pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:standbyJSStr];
                [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
            }];
        }else {
            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"You Are Error"];
            [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
        }
    }
    
    #pragma mark 从摄像头获取图片或视频
    - (void)selectImageFromCameraWithBlock:(imgBlock)block{
        self.myBlock = block;
        _imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
        //相机类型(拍照、录像...)字符串需要做相应的类型转换
        //视频上传质量
        //UIImagePickerControllerQualityTypeHigh高清
        //UIImagePickerControllerQualityTypeMedium中等质量
        //UIImagePickerControllerQualityTypeLow低质量
        //UIImagePickerControllerQualityType640x480
        _imagePickerController.videoQuality = UIImagePickerControllerQualityTypeHigh;
        //设置摄像头模式(拍照,录制视频)为录像模式
        _imagePickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
        [self.viewController presentViewController:_imagePickerController animated:YES completion:nil];
    }
    
    #pragma mark 从相册获取图片或视频
    - (void)selectImageFromAlbumWithBlock:(imgBlock)block{
        self.myBlock = block;
        _imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
        [self.viewController presentViewController:_imagePickerController animated:YES completion:nil];
    }
    
    #pragma mark UIImagePickerControllerDelegate
    //该代理方法仅适用于只选取图片时
    - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(nullable NSDictionary<NSString *,id> *)editingInfo {
        
        NSString *encodedImageStr = [self imageProcessing:image];
        self.myBlock(encodedImageStr);
    }
    
    //适用获取所有媒体资源,只需判断资源类型
    - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
        NSString *encodedImageStr = [self imageProcessing:info[UIImagePickerControllerEditedImage]];
        self.myBlock(encodedImageStr);
        [self.viewController dismissViewControllerAnimated:YES completion:nil];
    }
    
    // 图像处理 若未下载base64者 可不用查看
    - (NSString *)imageProcessing:(UIImage *)proImage{
        // 判断传过来照片的大小进行裁剪
        CGFloat width  = proImage.size.width;
        CGFloat height = proImage.size.height;
        CGSize size;
        if (width>height) {
            size = CGSizeMake(800, 450);
        }else {
            size = CGSizeMake(450, 800);
        }
        // 创建一个bitmap的context
        // 并把它设置成为当前正在使用的context
        UIGraphicsBeginImageContext(size);
        // 绘制改变大小的图片
        [proImage drawInRect:CGRectMake(0,0, size.width, size.height)];
        // 从当前context中创建一个改变大小后的图片
        UIImage * tailoringImage =UIGraphicsGetImageFromCurrentImageContext();
        // 使当前的context出堆栈
        UIGraphicsEndImageContext();
        // 判断图片大小进行压缩
        NSData *imageData = UIImageJPEGRepresentation(tailoringImage,1.0);
        
        NSLog(@"imagedata == %lud,size.width = %f==%f",(unsigned long)imageData.length,tailoringImage.size.width,tailoringImage.size.height);
        
        if (imageData.length>100*1024) {
            if (imageData.length>1024*1024) {     //1M以及以上
                imageData=UIImageJPEGRepresentation(tailoringImage, 0.1);
            }else if (imageData.length>512*1024) {//0.5M-1M
                imageData=UIImageJPEGRepresentation(tailoringImage, 0.3);
            }else if (imageData.length>200*1024) {//0.25M-0.5M
                imageData=UIImageJPEGRepresentation(tailoringImage, 0.7);
            }
        }
        NSLog(@"imagedata == %lud,size.width = %f==%f",(unsigned long)imageData.length,tailoringImage.size.width,tailoringImage.size.height);
       
        // 最后进行 base64 转码
        // NSString * encodedImageStr = [GTMBase64 stringByEncodingData:imageData];
          // 原生转码方法
        NSString * encodedImageStr = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
        return encodedImageStr;
    }
    @end
    
    • (3)编写index.html文件如下
    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title></title>
            <script type="text/javascript" src="cordova.js"></script>
            <script type="text/javascript" src="cordova_plugins.js"></script>
            <script type="text/javascript" src="jquery.min.js"></script>
            <script type="text/javascript">
                document.addEventListener("deviceready", yourCallbackFunction, false);
                
                function cameraBTClick(){
                    Cordova.exec(successFunction, failFunction, "YanSYPlugin", "myMethod", ["调用相机"]);
                }
            function PhotoAlbumBTClick(){
                Cordova.exec(failFunction, failFunction, "YanSYPlugin", "myMethod", ["查看相册"]);
            }
            
            function failFunction(error){
                alert("error");
                document.getElementById("returnValue").value = img;
            }
            function aaaa(){
                document.getElementById("2").innerHTML = "<img src='data:image/png;base64,"
            }
            
            function successFunction(img){
                document.getElementById("returnValue").value = img;
                document.getElementById("2").innerHTML = "![]("+img+")"
            }
            
                </script>
            
        </head>
        
        <body>
            <p>点击下面按钮调用相机拍照</p>
            <button onclick="cameraBTClick()">调用相机</button>
        </body>
        
        <body>
            <p>点击下面按钮选择相册照片</p>
            <button onclick="PhotoAlbumBTClick()">查看相册</button>
            <button onclick="aaaa()">测试</button>
            <h1>这是回调结果展示区</h1>
            <textarea id ="returnValue" type="value" rows="5" cols="40">
            </textarea>
            
        </body>
        
        <body>/Users/YanSY/Desktop/代码运行示意11.gif
            <p>
            <img id="1" src=“”/>
            </p>
            <p id="2" > </p>
            
        </body>
        
     
    </html>
    
    将以上代码,按步骤写入程序中,可测试调用原生相机,注意,需要在plist文件中添加隐私权限。运行效果示例如下只做到调用原生相机相册。 result.gif

    到这里基本结束了,需要Demo的点这里.

    这样集成Cordova需要依赖cordova的一个工程,下篇文章介绍一种不需要依赖关系的方式,直接导入相关文件使用。

    相关文章

      网友评论

      • xdw_208a:我如果想使用 Cordova项目中的插件 要做什么特殊配置吗
      • 沃小沃:请问下我们要做ISO的sdk给客户用,客户只会h5,我应该怎么提供接口怎么才能和他联通?也是需要在像你一样写个demo搭建cordova环境吗
        沃小沃:@afyylong 不太清楚他们是怎么上架,只知道使用我们提供的iOS SDK 集成好那边直接打包
        afyylong:@沃小沃 这要看你们用什么混编模式了,用react native也可以的,cordova是不能上架的
      • 敲代码的小矮人:还有,怎么按照Shemes下图配置,那个viewcontroller.h就变成下图显示的了,就是目录下6.8的位置,不是很懂
      • 敲代码的小矮人:Xcode9工程里面Build Settings没有Other Linker Flages怎么办
        敲代码的小矮人:@afyylong 恩 是的的 我找到了 需要在all的情况下找
        afyylong:Other Linker Flags 有的啊 我Xcode9.4有的,你拼错了小姐姐:smile:
      • 木叉叉木大手:安装cordova插件会报错的吧
        (node:12108) UnhandledPromiseRejectionWarning: CordovaError: Current working directory is not a Cordova-based project.
        木叉叉木大手:@afyylong 哈哈,用cocoapods安装cordova,插件也只能用cocoapods安装,不过官网并没写
        afyylong:@木叉叉木大手 我没试过用pod安装cordova:smile:
      • 木叉叉木大手:用cocoapods怎么弄到已有项目中呢?
        木叉叉木大手:@afyylong 恩,就把lib手动copy那一步省了
        afyylong:cocoapods 安装好之后,可以先搜索一下pod search Cordova, 在podfile中加上 pod 'Cordova'
        应该就可以了,你可以尝试一下
      • Ilovecoding822:楼主为何不选择rn呢?
        afyylong:@hippo_baby 666
        Ilovecoding822:@afyylong 哈哈,也还好吧,遇坑填坑啊,不然怎么成长?:sunglasses: 今年我司所有App全部转RN:smiley:
        afyylong:@hippo_baby rn坑太多,老大拍板了
      • writeSpace:六六六
      • 玉思盈蝶:好厉害好厉害啊
        afyylong:@玉思 求我啊:stuck_out_tongue_winking_eye:
        玉思盈蝶:@乆号代码 快写快写
        afyylong:@玉思盈蝶 准备写下一篇呢,结果搞电脑搞的头疼

      本文标题:iOS-Cordova集成开发,已有项目集成cordova

      本文链接:https://www.haomeiwen.com/subject/ncsdzxtx.html