浅谈微信小程序转支付宝

项目不大或新开项目,可优先尝试使用AntMove 一键搬家

新项目也可以使用uniapp

本篇是主要针对比较庞大的项目,使用的兼容方式是代码兼容 + 自定义工具转换(篇尾附录使用oc些的转换工具核心源码),下文主要记录一些思路与兼容关键点

1. 文件拓展名转换

微信 支付宝
wxml axml
axss acss
wxs sjs

2. API兼容

2.1 使用AntMove 一键搬家对已有项目进行转换,然后提取__antmove/api中的兼容文件放入项目中,然后在转换为支付宝小程序时将使用了wx.的js文件中引入index.js如:const wx = require('/utils/my/api/index.js')(my)

  • 本篇用到的__antmove /api文件, 对应项目中的utils/my/api
__antmove/api/index.js
__antmove/api/desc.js
__antmove/api/log.js
__antmove/api/my.js
__antmove/api/propsPolyfill.js
__antmove/api/runtimeProcess.js
__antmove/api/utils.js
  • 可根据项目实际情况在api/my.js补充和修改API兼容源码 (建议修改部分进行标记、新增放到最好,方便迭代
    如对getAccountInfoSynccheckSession进行自定义兼容

2.2 支付宝小程序不能在Page({})之外和page.data中访问app全局数据,只能通过在app中定义好的函数访问。 如下:

   // 正确方式
   const iPhoneX = getApp().isIPhoneXOrLater() 
   // 错误方式1
   const iPhoneX = getApp().data.iPhoneX
   // 错误方式2
   Page({
      data: {
         iPhoneX: getApp().data.iPhoneX
      }
   })

2.3 wx.getMenuButtonBoundingClientRect()

  • 由于支付宝不支持获取胶囊按钮属性,所以在项目中禁止使用该方法。有下面方法取代
  • const navBarInfo = getApp().getNavBarInfo() // 尽可能使用getApp()来调用方法,支付宝对使用app调用会有个别情况不兼容
// app.js
// mark: 导航栏信息
/// navBarInfo参数说明 { top: 状态栏高度 width:宽度 height:标题bar高度 H:状态栏+标题栏 }
getNavBarInfo: function () {
   if (this.data.navBarInfo !== null)  return this.data.navBarInfo

   var info = wx.getSystemInfoSync()
   var barInfo = {}
   barInfo.top = info.statusBarHeight || 44
   barInfo.width = info.windowWidth || 375
   if (info.app == 'alipay') { // 支付宝
       barInfo.height = info.titleBarHeight || 48
       barInfo.menuRect = null
   }else { // 微信
       var rect = wx.getMenuButtonBoundingClientRect()
       barInfo.height = rect.bottom + 10 - barInfo.top
       barInfo.menuRect = rect
   }
   barInfo.H = barInfo.top + barInfo.height
   this.data.navBarInfo = barInfo
   return barInfo
},

3. json配置字段转换<主要字段>

将微信小程序json转换为支付宝时只需一些核心字段转换即可,然后再对个别进行单独配置(具体需求根据项目而定
3.1app.json字段转换对应关系

  • pagse : 直接将pages对象赋值给支付宝pages即可
  • subpackages : 直接将subpackages对象赋值给支付宝subpackages即可
  • preloadRule : 直接将preloadRule对象赋值给支付宝preloadRule即可
  • window:在支付宝创一个同名window对象,然后对子元素逐一转换(如下表)
内容 微信条件/字段 支付宝目标
导航栏背景颜色 navigationBarBackgroundColor存在 titleBarColor
导航栏标题颜色 navigationBarTextStyle=white barButtonTheme = light
导航栏标题文字内容 navigationBarTitleText defaultTitle
导航栏样式 navigationStyle =custom transparentTitle=always titlePenetrate=YES
顶部窗口的背景色 backgroundColorTop backgroundImageColor
窗口的背景色 backgroundColor backgroundColor
是否开启当前页面下拉刷新 enablePullDownRefresh pullRefresh
页面上拉触底事件触发时距页面底部距离,单位为px onReachBottomDistance onReachBottomDistance
设置为 true 则页面整体不能上下滚动 disableScroll=True allowsBounceVertical=NO
使用组件集合 usingComponents usingComponents
是否为组件 component component
  • tabBar:在支付宝创一个同名window对象,然后对子元素逐一转换
内容 微信条件/字段 支付宝目标
自定义tabbar custom 不支持
文字颜色 color textColor
文字选中时的颜色 selectedColor selectedColor
背景色 backgroundColor backgroundColor
tab 的列表 list items
list.item:设置页面路径 pagePath pagePath
list.item:按钮文字 text name
list.item:图片路径 iconPath icon
list.item:高亮图标路径 selectedIconPath activeIcon
  • 其它:可根据需求进行定制转换

3.2 page.json页面和组件json: 转换关系同上文window子元素转换一致

3.3 ext.json:三方平台调试文件配置对应关系(非三方平台小程序可忽略)

微信 支付宝
extEnable extEnable 根据实际情况设置
extPages subPackages 根据实际情况设置
ext ext 根据实际情况设置
window window 子元素的转换见上文window
tabBar tabBar 子元素的转换见上文tabBar

3.4 mini.project.json是支付宝的配置管理文件,可以进行单独管理

4. wxmlaxml

4.1 常用元素转换关系
// 文件引用
"wxml" : "axml",

// 条件语句与for循环语句
"wx:" : "a:",

// 事件绑定
"bindtap=" : "onTap=",
"catchtap=" : "catchTap=",
"bindchange=" : "onChange=",
"bindinput=" : "onInput=",
"bindfocus=" : "onFocus=",
"bindblur=" : "onBlur=",
"bindconfirm=" : "onConfirm=",
"bindscroll=" : "onScroll=",
"longpress=" : "onLongTap=",
"touchstart=" : "touchStart=",
"touchmove=" : "touchMove=",
"touchend=" : "touchEnd=",
"touchcancel=" : "touchCancel=",
"bindsubmit=": "onSubmit=",
"bindreset=": "onReset=",

// wxs
"<wxs src=" :  "<import-sjs from=",
"wxs>" : "import-sjs>",
"module=" : "name=",
".wxs" : ".sjs",

// button
"open-type=\"getUserInfo\"" : "open-type=\"getAuthorize\" scope=\"userInfo\"",
"open-type=\"getPhoneNumber\"" : "open-type=\"getAuthorize\" scope=\"phoneNumber\"",
"bindgetuserinfo=" : "onGetAuthorize=",
"bindgetphonenumber=" : "onGetAuthorize=",

// canvas
"canvas-id=" : "id="
4.1 paddingmargin
  • 支付宝中image使用 padding无效果,设置的padding会加到宽高上,所以要避免使用
  • 支付宝中axml中给第一个元素设置margin-top效果会转移到page上去,导致显示异常。最简单的解决办法是在第一个元素前插入一个<view stye="height: 1rpx;"/>,注意高度必须设置(可有用高度替换margin-top),否则无效
  • 支付宝支持自定义标签,如:<food></food> <cell></cell>等
4.3 input

支付宝不支持页内样式,所以不能在input中使用style

4.4 button
  • open-type 兼容适配 (代码兼容,工具转换兼容)
功能 微信 支付宝 工具转换 代码兼容 备注
打开客服会话 contact 不支持 NO YES -
分享 share share / contactShare NO YES 分享到通讯录好友需要兼容
授权设置 openSetting 不支持 NO YES 可以通过openSetting API打开
意见反馈 feedback 不支持 NO YES -
打开APP launchApp 不支持 NO YES -
关注生活号 不支持 lifestyle NO YES -
获取用户信息 getUserInfo getAuthorize + scope YES YES 见ps1
获取手机号 getPhoneNumber getAuthorize + scope YES YES 见ps2

ps1: 获取用户信息

  1. 工具转换说明
    • open-type="getUserInfo" ---> open-type="getAuthorize" scope="userInfo" // 注意用双引号
    • bindgetuserinfo= ---> onGetAuthorize=
  2. 代码适配说明
    • 2.1 由于支付宝通过按钮只能获取授权,我们需要在取得授权后手动调用api获取信息my.getOpenUserInfo(),
      参考代码:pages/main/user/userInfo.js 中的getUserInfo函数
    • 2.2 支付宝授权错误回调兼容(可选操作):onError="onAuthError", 在onAuthError中处理授权失败回调(包括用户拒绝和系统异常)问题
  3. 支付宝文档:https://opendocs.alipay.com/mini/api/ch8chh

ps2: 获取手机号 转换说明

  1. 工具转换说明
    • open-type="getPhoneNumber" ---> open-type="getAuthorize" scope="phoneNumber" // 注意用双引号
    • bindgetphonenumber= ---> onGetAuthorize
  2. 代码适配说明:
    • 2.1 由于支付宝通过按钮只能获取授权,我们需要在取得授权后手动调用api获取信息my.getPhoneNumber(),
      参考代码:pages/main/user/signup.js 中的getPhoneNumberFromWechat函数
    • 2.2 支付宝授权错误回调兼容(可选操作):onError="onAuthError", 在onAuthError中处理授权失败回调(包括用户拒绝和系统异常)问题
  3. 支付宝文档:https://opendocs.alipay.com/mini/api/getphonenumber

5. js文件兼容

5.1 在使用了wx.的js中插入兼容api对象,到js文件的首行

"const wx = require('/utils/my/api/index.js')(my)\n"

5.2 组件js兼容转换(逐一替换)

"/****** alipay begin" : "///****** alipay begin",
"*///****** alipay end" : "///****** alipay end",
"///****** wx begin" : "/****** wx begin",
"///****** wx end" : "*///****** wx end",
"properties" : "props"

5.3 config.js配置文件设置小程序标识符为my

"MINI_APP_TYPE = 'wx' 替换成 MINI_APP_TYPE = 'my'

或使用
const MINI_APP_TYPE = wx.getSystemInfoSync().app == 'alipay' ?  'wx' : 'wx'

6. wxssacss

// 文件引用转换
"wxss" : @"acss"

7. wxssjs

7.1 支付宝不支持页内sjs,所以为了方便将wxs转化为sjs,需要写到一个专门的.wxs文件中
7.2 支付宝不支持"module.exports = ..." 的写法,只支持"export default"。 所以为了方便将wxs转化为sjs需要按如下规定书写:
7.2.1. module.exports = {},要注意空格,然后转换时将"module.exports =" 替换为 @"export default"
7.2.2. 不管输出对象有多少个都只能以module.exports = {}的形式书写,如module.exports.msg = msg;是错误的写法,会导致转换失败

// 正确参考:
module.exports = {
  takeFoodTypeName: takeFoodTypeName,
  stepIconName: stepIconName,
  stateIconName: stateIconName,
  showBtn: showBtn,
  fmtText: text
}

7.3. axml中将wxs(使用)转换为sjs见上文 wxml与axml

8. 自定义组件

8.0 兼容方式:

    1. AntMove组件兼容方式(对组件进行更细致的封装,统一兼容,实测对特俗组件兼容度不够好)
    1. 对自定义组件进行逐一兼容(本篇采用方式,更具针对性),如下:为了兼容支付宝,需要做一些适配、增加一些设定

8.1 支付宝组件中的样式没有区域隔离,会对使用组件的页面生效,导致显示异常,所以需要自行进行区域隔离(如:可以加上__组件名称简写__类似的前缀、 mp__classname等)

8.2 事件绑定:微信是提供triggerEvent方法进行事件传递,而支付宝是提供props中的属性函数。 兼容方式如下:

    1. 微信绑定事件的方法名统一首字母大写: 如 this.triggerEvent('FuncName', data) >>> 对应支付宝 >>> this.props.onFuncName({detail: data})
    1. 支付宝的绑定事件需要写在props中,且以on开始驼峰写法onFuncName, 通过在组件内使用this.props.onFuncName({detail: data})进行事件触发, 参数传递时需要以{detail: data}这样的固定格式,detail是固定key,data是自定义返回参数
    1. 在使用组件时,统一以on开始驼峰写法onFuncName="bindFuncName"进行事件绑定,方便同时兼容支付宝和微信
    1. 绑定事件的实现之固定兼容方式
    // 1. 在methods中实现下面函数
    bindEvent: function (name, detail, options) {
       name = name.substring(0, 1).toUpperCase() + name.substring(1) // 首字转换为母大写
       if (getApp().data.config.app === 'wx') { // wx
          // 固定写法
          this.triggerEvent(name, detail) 
       }else { // my
          // 根据实际情况调用对应的函数
          if (name === 'EventFuncName') { // 注意EventName首字大写
             this.props.onEventFuncName({detail: detail}) // 参数固定写法
          }
       }
    
       // 2. 在事件触发的地方调用bindEvent
       this.bindEvent('EventFuncName', { key: value, ... }); 
    },
    

8.3 固定写法:代码分为微信支付宝公共,并用固定注释标记区域(如下),方便转换工具进行转换(特定格式进行隔离,方便转换是进行切换)
```
///****** wx begin
wx code...
///****** wx end

  /****** alipay begin
     my code...
  *///****** alipay end
  ```  

8.4 支付宝不支持通过setData的方法设置props的属性,所以直接使用如this.properties.curStore = curStore的方法修改properties属性。 如果使用setData方法取修改props属性会导致数据渲染异常(会在data中新建一个)

8.5 在微信自定义组件中不支持直接使用wx调用createSelectorQuery,而需要使用this,但在支付宝中不支持使用this。所以需要兼容,如下

const isWX = getApp().app() == 'wx' 
// 方法1
var query = isWX ? this.createSelectorQuery() : wx.createSelectorQuery()

// 方法2
var query = wx.createSelectorQuery()
if (isWX)    query = query.in(this)

query.select('#marquee').boundingClientRect()
...

附录: object-c实现参考源码

// ======================== .h文件 =============================
@interface wx2my : NSObject
/// from wx minapp folder path
@property(nonatomic, copy) NSString *fromPath;
/// to my minapp folder path, default toPath = fromPath
@property(nonatomic, copy) NSString *toPath;
/// 获取单例
+ (instancetype)shared;

/// 转换:微信小程序转支付宝小程序
/// @param schedule 处理进度回调
- (void)transform:(void(^)(CGFloat progress, NSInteger qty, NSInteger failQty))schedule;

// MARK: - 错误日志
@property(nonatomic, copy) void(^logsInput)(NSMutableArray *logs, NSString *log);
- (void)clearLogs;

@end



// ======================== .m文件 =============================

#import "wx2my.h"

@interface wx2my () {
    /// 总文件数
    NSInteger totalFiles;
}
/// app.json 、page.json 和component.json文件绝对路径集合
@property(nonatomic, strong) NSArray *jsonFiles;
/// js文件绝对路径集合
@property(nonatomic, strong) NSArray *jsFiles;
/// wxml文件绝对路径集合
@property(nonatomic, strong) NSArray *wxmlFiles;
/// wxss文件绝对路径集合
@property(nonatomic, strong) NSArray *wxssFiles;
/// wxs文件绝对路径集合
@property(nonatomic, strong) NSArray *wxsFiles;
/// 其他文件绝对路径集合
@property(nonatomic, strong) NSArray *otherFiles;

// MARK: - 错误日志
@property(nonatomic, strong) NSMutableArray *logs;
@end

@implementation wx2my
static wx2my *instance = nil;
+ (instancetype)shared {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[wx2my alloc] init];
    });
    return instance;
}


/// MARK: - 获取所用文件路径
- (void)initPaths {
    NSFileManager *defaultManager = [NSFileManager defaultManager];
    
    [self addLog:[defaultManager fileExistsAtPath:_fromPath isDirectory:nil] ? @"YES" : @"NO" error:nil actionPrefix:@"文件是否存在"];
    [self addLog:[defaultManager isReadableFileAtPath:_fromPath] ? @"YES" : @"NO" error:nil actionPrefix:@"文件读取权限"];
    [self addLog:[defaultManager isWritableFileAtPath:_fromPath] ? @"YES" : @"NO" error:nil actionPrefix:@"文件修改权限"];
    
    NSArray *subPaths = [defaultManager subpathsAtPath:_fromPath];
    if (subPaths.count == 0) {
        [self addLog:@"" error:nil actionPrefix:@"文件读取权失败"];
        return;
    }else {
        [self addLog:@"" error:nil actionPrefix:@"文件读取权成功"];
    }
    NSMutableArray *jsonFiles = [NSMutableArray array];
    NSMutableArray *jsFiles = [NSMutableArray array];
    NSMutableArray *wxmlFiles = [NSMutableArray array];
    NSMutableArray *wxssFiles = [NSMutableArray array];
    NSMutableArray *wxsFiles = [NSMutableArray array];
    NSMutableArray *otherFiles = [NSMutableArray array];
    for (NSString *path in subPaths) {
        /// 忽略git文件
        if ([path containsString:@".git"])    continue;
        
        // page.json
        if ([path hasSuffix:@".json"]) {
            if ([path hasSuffix:@"app.json"] || [path containsString:@"pages"] || [path containsString:@"components"]) {
                [jsonFiles addObject:path];
                continue;
            }
        }
        if ([path hasSuffix:@".js"]) {
            [jsFiles addObject:path];
            continue;
        }
        if ([path hasSuffix:@".wxml"]) {
            [wxmlFiles addObject:path];
            continue;
        }
        if ([path hasSuffix:@".wxss"]) {
            [wxssFiles addObject:path];
            continue;
        }
        if ([path hasSuffix:@".wxs"]) {
            [wxsFiles addObject:path];
            continue;
        }
        if ([path containsString:@"."] && ![path hasSuffix:@".DS_Store"]) { // 去掉文件夹和.DS_Store文件
            [otherFiles addObject:path];
        }
    }
    totalFiles = jsonFiles.count + jsFiles.count + wxmlFiles.count + wxssFiles.count + wxsFiles.count + otherFiles.count;
    self.jsonFiles = jsonFiles;
    self.jsFiles = jsFiles;
    self.wxmlFiles = wxmlFiles;
    self.wxssFiles = wxssFiles;
    self.wxsFiles = wxsFiles;
    self.otherFiles = otherFiles;
}

// MARK: - 开始转换
- (void)transform:(void(^)(CGFloat progress, NSInteger qty, NSInteger failQty))schedule {
    if (!self.fromPath) {
//        [TLToast showToast:@"请先设置微信小程序路径" inView:nil];
        return;
    }
    [self initPaths];
    NSInteger __block total = totalFiles;       // 已处理数量
    NSInteger __block qty = 0;                  // 已处理数量
    NSInteger __block failQty = 0;              // 失败文件数量
    CGFloat __block progress = 0.00;            // 总进度
    NSArray <NSArray *>*groups = @[_jsonFiles, _jsFiles, _wxmlFiles, _wxssFiles, _wxsFiles, _otherFiles];
    [groups enumerateObjectsUsingBlock:^(NSArray * _Nonnull paths, NSUInteger idx, BOOL * _Nonnull stop) {
        [paths enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            [self fileTransform:obj complate:^(BOOL success) {
                qty++;
                if (!success) failQty++;
                progress = qty * 1.00 / total;
                if (schedule) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        schedule(progress, qty, failQty);
                    });
                }
                   
                if (idx == paths.count - 1) {
                    [self addLog:@"" error:nil actionPrefix:[NSString stringWithFormat:@"%@文件转换完成", [obj pathExtension]]];
                }
                
                if (qty >= total || idx == paths.count-1) {
                    NSString *msg = nil;
                    if (qty >= total) {
                        msg = [NSString stringWithFormat:@"转换完成 失败%zi个文件", failQty];
                    }else {
                        msg = [NSString stringWithFormat:@"完成进度%.2f%%", progress * 100.f];
                    }
//                    [TLToast showToast:msg inView:nil];
                    NSLog(@"%@", msg);
                }
            }];
        }];
    }];
}

// MARK: - 文件转换与导出
- (void)fileTransform:(NSString *)wxFile complate:(void (^)(BOOL success))complate {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 文件读取
        NSError *err;
        NSString *wxFullPath = [NSString stringWithFormat:@"%@/%@", self.fromPath, wxFile];
        BOOL icCopy = [wxFile hasSuffix:@".png"] || [wxFile hasSuffix:@".jpg"];
        BOOL needRename = NO;
        NSData *data = nil;
        if (!icCopy) {
            NSString *dataString = [NSString stringWithContentsOfFile:wxFullPath
                                                             encoding:NSUTF8StringEncoding
                                                                error:&err];
//            if (err) {
//                err = nil;
//                dataString = [NSString stringWithContentsOfFile:wxFullPath
//                                                       encoding:NSMacOSRomanStringEncoding
//                                                          error:&err];
//            }
            if (err) {
                [self addLog:wxFile error:err actionPrefix:@"FILE READ FAIL"];
                if (complate) {
                    complate(NO);
                }
                return;
            }
            data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
            
            // 文件处理
            if ([wxFile hasSuffix:@".json"]) {
                data = [self jsonFileTransform:wxFile fileData:data error:&err];
            }else if ([wxFile hasSuffix:@".js"]) {
                data = [self jsFileTransform:wxFile fileData:data error:&err];
            }else if ([wxFile hasSuffix:@".wxml"]) {
                needRename = YES;
                data = [self wxmlFileTransform:wxFile fileData:data error:&err];
            }else if ([wxFile hasSuffix:@".wxss"]) {
                needRename = YES;
                data = [self wxssFileTransform:wxFile fileData:data error:&err];
            }else if ([wxFile hasSuffix:@".wxs"]) {
                needRename = YES;
                data = [self wxsFileTransform:wxFile fileData:data error:&err];
            }
            if (err) {
                [self addLog:wxFile error:err actionPrefix:dataString];//@"FILE TRANSFORM FAIL"];
                if (complate) complate(NO);
                return;
            }
        }
        
        
        // 文件导出
        // 新文件名(相对路径)
        NSString *myFile= needRename ? [self fileNameWithFile:wxFile] : wxFile;
        NSString *savePath = [NSString stringWithFormat:@"%@/%@", self.toPath, myFile];
        NSFileManager *fileManager = [[NSFileManager alloc] init];
        // 创建文件夹
        [fileManager createDirectoryAtPath:[savePath stringByDeletingLastPathComponent]
               withIntermediateDirectories:YES
                                attributes:nil
                                     error:&err];
        if (err) {
            [self addLog:wxFile error:err actionPrefix:@"CREAT FOLDER FAIL"];
            if (complate) {
                complate(NO);
            }
            return;
        }
        
        // 保存
        if (icCopy) {
            if ([fileManager fileExistsAtPath:savePath]) {
                [fileManager removeItemAtPath:savePath error:&err];
                if (err) {
                    [self addLog:wxFile error:err actionPrefix:@"REMOVE FILE FAIL"];
                    if (complate) {
                        complate(NO);
                    }
                    return;
                }
            }

            [[NSFileManager defaultManager] copyItemAtPath:wxFullPath toPath:savePath error:&err];
            if (err) {
                [self addLog:wxFile error:err actionPrefix:@"COPY FILE FAIL"];
                if (complate) {
                    complate(NO);
                }
                return;
            }
        }else if (![data writeToFile:savePath atomically:YES]) {
            [self addLog:wxFile error:err actionPrefix:@"MY_FILE WRITING FAIL"];
            if (complate) {
                complate(NO);
            }
            return;
        }
        if (complate) {
            complate(YES);
        }
    });
}

// MARK: - json 文件转换处理
- (NSData *)jsonFileTransform:(NSString *)wxFile fileData:(NSData *)data error:(NSError **)error {
    // 文件内容转字典
    NSError *err;
    NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data
                                                         options:NSJSONReadingAllowFragments
                                                           error:error];
    if (err) {
        [self addLog:wxFile error:err actionPrefix:@"FILE TO DATA(READ) FAIL"];
        return data;
    }
    
    // 字典处理
    NSMutableDictionary *myDict = [NSMutableDictionary dictionary];
    if ([wxFile hasSuffix:@"app.json"]) {
        if (dict[@"pages"]) {
            myDict[@"pages"] = dict[@"pages"];
        }
        if (dict[@"subpackages"]) {
            myDict[@"subPackages"] = dict[@"subpackages"];
        }
        if (dict[@"preloadRule"]) {
            myDict[@"preloadRule"] = dict[@"preloadRule"];
        }
        
        if (dict[@"window"]) {
            NSMutableDictionary *myWindowDict = [NSMutableDictionary dictionary];
            [self wxWindowParans:dict[@"window"] toMyParams:myWindowDict];
            myDict[@"window"] = myWindowDict;
        }
        if (dict[@"tabBar"]) {
            NSMutableDictionary *myTabBarDict = [NSMutableDictionary dictionary];
            [self wxTabBarParans:dict[@"tabBar"] toMyParams:myTabBarDict];
            myDict[@"tabBar"] = myTabBarDict;
        }
    }else if ([wxFile hasSuffix:@"/ext.json"]) {
        if (dict[@"extEnable"]) {
            myDict[@"extEnable"] = dict[@"extEnable"];
        }
        if (dict[@"extPages"]) {
            myDict[@"subPackages"] = dict[@"extPages"];
        }
        if (dict[@"ext"]) {
            myDict[@"ext"] = dict[@"ext"];
        }
        if (dict[@"window"]) {
            NSMutableDictionary *myWindowDict = [NSMutableDictionary dictionary];
            [self wxWindowParans:dict[@"window"] toMyParams:myWindowDict];
            myDict[@"window"] = myWindowDict;
        }
        if (dict[@"tabBar"]) {
            NSMutableDictionary *myTabBarDict = [NSMutableDictionary dictionary];
            [self wxTabBarParans:dict[@"tabBar"] toMyParams:myTabBarDict];
            myDict[@"tabBar"] = myTabBarDict;
        }
    }else if ([wxFile hasSuffix:@"mini.project.json"]) {
        myDict = [dict mutableCopy];
    }else {
        [self wxWindowParans:dict toMyParams:myDict];
    }

    // json to data
    NSData *jsonData =[NSJSONSerialization dataWithJSONObject:myDict
                                                      options:NSJSONWritingPrettyPrinted
                                                        error:error];
    if (err) {
        [self addLog:wxFile error:err actionPrefix:@"MY_JSON TO DATA FAIL"];
        return data;
    }
    
    //NSJSONSerialization converts a URL string from http://... to http:\/\/... remove the extra escapes
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    jsonString = [jsonString stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"];
    jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
    return jsonData;
}

// MARK: - json文件 window 字段属性转换处理
- (void)wxWindowParans:(NSDictionary *)wxParams toMyParams:(NSMutableDictionary *)myDict {
    [wxParams enumerateKeysAndObjectsUsingBlock:^(NSString *key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        if ([key isEqualToString:@"navigationBarBackgroundColor"]) { // 导航栏背景颜色
            myDict[@"titleBarColor"] = obj;
        }
        if ([key isEqualToString:@"navigationBarTextStyle"] && [obj isEqualToString:@"white"]) { //导航栏标题颜色
            myDict[@"barButtonTheme"] = @"light";
        }
        if ([key isEqualToString:@"navigationBarTitleText"]) { // 导航栏标题文字内容
            myDict[@"defaultTitle"] = obj;
        }
        if ([key isEqualToString:@"navigationStyle"] && [obj isEqualToString:@"custom"]) { // 导航栏样式
            myDict[@"transparentTitle"] = @"always";
            myDict[@"titlePenetrate"] = @"YES";
        }
        if ([key isEqualToString:@"backgroundColorTop"]) { // 顶部窗口的背景色 --> 下拉露出显示背景图的底色
            myDict[@"backgroundImageColor"] = obj;
        }
        if ([key isEqualToString:@"backgroundColor"]) { // 窗口的背景色
            myDict[@"backgroundColor"] = obj;
        }
        if ([key isEqualToString:@"enablePullDownRefresh"]) { // 是否开启当前页面下拉刷新
            myDict[@"pullRefresh"] = obj;
        }
        if ([key isEqualToString:@"onReachBottomDistance"]) { // 页面上拉触底事件触发时距页面底部距离,单位为px
            myDict[@"onReachBottomDistance"] = obj;
        }
        if ([key isEqualToString:@"disableScroll"] && [obj boolValue]) { // 设置为 true 则页面整体不能上下滚动
            myDict[@"allowsBounceVertical"] = @"NO";
        }
        if ([key isEqualToString:@"usingComponents"]) { // 使用组件集合
            myDict[@"usingComponents"] = obj;
        }
        if ([key isEqualToString:@"component"]) { // 是否为组件
            myDict[@"component"] = obj;
        }
    }];
}

// MARK: - json文件 tabbar 字段属性转换处理
- (void)wxTabBarParans:(NSDictionary *)wxParams toMyParams:(NSMutableDictionary *)myDict {
    [wxParams enumerateKeysAndObjectsUsingBlock:^(NSString *key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        if ([key isEqualToString:@"custom"] && [obj boolValue]) { // 设置为 true 则页面整体不能上下滚动
            NSLog(@"\n\n============== WARING ===============\n\n"
                   "      支付宝小程序不支持自定义tabbar类型      "
                   "\n\n============== WARING ===============\n\n");
        }
        
        if ([key isEqualToString:@"color"]) { // 文字颜色
            myDict[@"textColor"] = obj;
        }
        
        if ([key isEqualToString:@"selectedColor"]) { // 文字选中时的颜色
            myDict[@"selectedColor"] = obj;
        }
        if ([key isEqualToString:@"backgroundColor"]) { // 背景色
            myDict[@"backgroundColor"] = obj;
        }
        if ([key isEqualToString:@"list"]) { // tab 的列表
            NSArray <NSDictionary *>*list = obj;
            NSMutableArray *items = [NSMutableArray arrayWithCapacity:list.count];
            [list enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                NSMutableDictionary *item = [NSMutableDictionary dictionary];
                // 设置页面路径
                if(obj[@"pagePath"]) item[@"pagePath"] = obj[@"pagePath"];
                // 按钮文字
                if(obj[@"text"]) item[@"name"] = obj[@"text"];
                // 图片路径
                if(obj[@"iconPath"]) item[@"icon"] = obj[@"iconPath"];
                // 高亮图标路径
                if(obj[@"selectedIconPath"]) item[@"activeIcon"] = obj[@"selectedIconPath"];
                [items addObject:item];
            }];
            myDict[@"items"] = items;
        }
    }];
}

// MARK: - js 文件处理
- (NSData *)jsFileTransform:(NSString *)wxFile fileData:(NSData *)data error:(NSError **)error {
    NSString __block *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSArray *ignoreFiles = @[@"bmap.js", @"date.js", @"config.js"]; // 要忽略的文件
    // 插入api对象引用
    NSString *fileName = [wxFile lastPathComponent];
    if (![ignoreFiles containsObject:fileName] && ![wxFile containsString:@"utils/my/api/"]) {
        if ([content containsString:@"wx."]) {
            NSMutableString *newContent = [[NSMutableString alloc] initWithString:content];
            [newContent insertString:@"const wx = require('/utils/my/api/index.js')(my)\n" atIndex:0];
            content = newContent;
        }
        
        if ([wxFile hasPrefix:@"component/"]) { // 组件
            NSDictionary *temp = @{
                @"/****** alipay begin" : @"///****** alipay begin",
                @"*///****** alipay end" : @"///****** alipay end",
                @"///****** wx begin" : @"/****** wx begin",
                @"///****** wx end" : @"*///****** wx end",
                @"properties" : @"props"
            };
            
            [temp enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL * _Nonnull stop) {
                if ([content containsString:key]) {
                    content = [content stringByReplacingOccurrencesOfString:key withString:obj];
                }
            }];
        }
    }
    if ([fileName isEqualToString:@"config.js"]) {
        content = [content stringByReplacingOccurrencesOfString:@"MINI_APP_TYPE = 'wx'"
                                                     withString:@"MINI_APP_TYPE = 'my'"];
    }
    return [content dataUsingEncoding:NSUTF8StringEncoding];
}

// MARK: - wxml 文件处理
- (NSData *)wxmlFileTransform:(NSString *)wxFile fileData:(NSData *)data error:(NSError **)error {
    NSString __block *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSDictionary *temp = @{
        @"wxml" : @"axml",
        @"wx:" : @"a:",
        
        @"bindtap=" : @"onTap=",
        @"catchtap=" : @"catchTap=",
        @"bindchange=" : @"onChange=",
        @"bindinput=" : @"onInput=",
        @"bindfocus=" : @"onFocus=",
        @"bindblur=" : @"onBlur=",
        @"bindconfirm=" : @"onConfirm=",
        @"bindscroll=" : @"onScroll=",
        @"longpress=" : @"onLongTap=",
        @"touchstart=" : @"touchStart=",
        @"touchmove=" : @"touchMove=",
        @"touchend=" : @"touchEnd=",
        @"touchcancel=" : @"touchCancel=",
        @"bindsubmit=": @"onSubmit=",
        @"bindreset=": @"onReset=",
        
        @"<wxs src=" :  @"<import-sjs from=",
        @"wxs>" : @"import-sjs>",
        @"module=" : @"name=",
        @".wxs" : @".sjs",
        
        @"open-type=\"getUserInfo\"" : @"open-type=\"getAuthorize\" scope=\"userInfo\"",
        @"open-type=\"getPhoneNumber\"" : @"open-type=\"getAuthorize\" scope=\"phoneNumber\"",
        @"bindgetuserinfo=" : @"onGetAuthorize=",
        @"bindgetphonenumber=" : @"onGetAuthorize=",
        @"canvas-id=" : @"id="
    };
    [temp enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL * _Nonnull stop) {
        if ([content containsString:key]) {
            content = [content stringByReplacingOccurrencesOfString:key withString:obj];
        }
    }];
    
    return [content dataUsingEncoding:NSUTF8StringEncoding];
}

// MARK: - wxml 文件处理
- (NSData *)wxssFileTransform:(NSString *)wxFile fileData:(NSData *)data error:(NSError **)error {
    NSString __block *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSDictionary *temp = @{
        @"wxss" : @"acss"
    };
    [temp enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL * _Nonnull stop) {
        if ([content containsString:key]) {
            content = [content stringByReplacingOccurrencesOfString:key withString:obj];
        }
    }];
    return [content dataUsingEncoding:NSUTF8StringEncoding];
}

// MARK: - wxs 文件处理
- (NSData *)wxsFileTransform:(NSString *)wxFile fileData:(NSData *)data error:(NSError **)error {
    NSString __block *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSDictionary *temp = @{
        @"module.exports =" : @"export default"
    };
    [temp enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL * _Nonnull stop) {
        if ([content containsString:key]) {
            content = [content stringByReplacingOccurrencesOfString:key withString:obj];
        }
    }];
    return [content dataUsingEncoding:NSUTF8StringEncoding];
}


// MARK: - 文件拓展名修改
- (NSString *)fileNameWithFile:(NSString *)wxFile {
    NSString *myFile = wxFile;
    if ([wxFile hasSuffix:@".wxml"]) {
        myFile = [wxFile stringByReplacingOccurrencesOfString:@".wxml" withString:@".axml"];
    }else if ([wxFile hasSuffix:@".wxss"]) {
        myFile = [wxFile stringByReplacingOccurrencesOfString:@".wxss" withString:@".acss"];
    }else if ([wxFile hasSuffix:@".wxs"]) {
        myFile = [wxFile stringByReplacingOccurrencesOfString:@".wxs" withString:@".sjs"];
    }
    return myFile;
}

- (void)addLog:(NSString *)file error:(NSError *)err actionPrefix:(NSString *)prefix{
    NSString *fileName = [[file componentsSeparatedByString:@"/"] lastObject];
    NSString *log = [NSString stringWithFormat:@"%@%@ %@\n%@\n", prefix, fileName.length ? @":" : @"", fileName, err ? err : @""];
    NSLog(@"%@", log);
    if (!self.logs) {
        self.logs = [NSMutableArray array];
    }
    if ([log isKindOfClass:[NSString class]]) {
        [self.logs addObject:log];
    }else {
        NSLog(@"%@", log);
    }
    
    if (self.logsInput) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.logsInput(self.logs, log);
        });
    }
}

- (void)clearLogs {
    [self.logs removeAllObjects];
    if (self.logsInput) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.logsInput(self.logs, @"日志清除成功");
        });
    }
}
@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容