项目不大或新开项目,可优先尝试使用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兼容源码 (建议修改部分进行标记、新增放到最好,方便迭代
)
如对getAccountInfoSync
和checkSession
进行自定义兼容
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. wxml
与axml
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 padding
与margin
- 支付宝中
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: 获取用户信息
- 工具转换说明
-
open-type="getUserInfo"
--->open-type="getAuthorize" scope="userInfo"
// 注意用双引号 -
bindgetuserinfo=
--->onGetAuthorize=
-
- 代码适配说明
- 2.1 由于支付宝通过按钮只能获取授权,我们需要在取得授权后手动调用api获取信息
my.getOpenUserInfo()
,
参考代码:pages/main/user/userInfo.js 中的getUserInfo函数 - 2.2 支付宝授权错误回调兼容(可选操作):
onError="onAuthError"
, 在onAuthError
中处理授权失败回调(包括用户拒绝和系统异常)问题
- 2.1 由于支付宝通过按钮只能获取授权,我们需要在取得授权后手动调用api获取信息
- 支付宝文档:https://opendocs.alipay.com/mini/api/ch8chh
ps2: 获取手机号 转换说明
- 工具转换说明
-
open-type="getPhoneNumber"
--->open-type="getAuthorize" scope="phoneNumber"
// 注意用双引号 -
bindgetphonenumber=
--->onGetAuthorize
-
- 代码适配说明:
- 2.1 由于支付宝通过按钮只能获取授权,我们需要在取得授权后手动调用api获取信息
my.getPhoneNumber()
,
参考代码:pages/main/user/signup.js 中的getPhoneNumberFromWechat函数 - 2.2 支付宝授权错误回调兼容(可选操作):onError="onAuthError", 在onAuthError中处理授权失败回调(包括用户拒绝和系统异常)问题
- 2.1 由于支付宝通过按钮只能获取授权,我们需要在取得授权后手动调用api获取信息
- 支付宝文档: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. wxss
与 acss
// 文件引用转换
"wxss" : @"acss"
7. wxs
与sjs
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 兼容方式:
-
AntMove
组件兼容方式(对组件进行更细致的封装,统一兼容,实测对特俗组件兼容度不够好)
-
- 对自定义组件进行逐一兼容(本篇采用方式,更具针对性),如下:为了兼容支付宝,需要做一些适配、增加一些设定
8.1 支付宝组件中的样式没有区域隔离,会对使用组件的页面生效,导致显示异常,所以需要自行进行区域隔离(如:可以加上__
或 组件名称简写__
类似的前缀、 mp__classname等)
8.2 事件绑定:微信是提供triggerEvent
方法进行事件传递,而支付宝是提供props中的属性函数
。 兼容方式如下:
-
微信
绑定事件的方法名
统一首字母大写
: 如 this.triggerEvent('FuncName'
, data) >>> 对应支付宝 >>> this.props.onFuncName
({detail: data})
-
-
支付宝
的绑定事件需要写在props中
,且以on开始
的驼峰写法
:onFuncName
, 通过在组件内使用this.props.onFuncName({detail: data})
进行事件触发, 参数传递时需要以{detail: data}
这样的固定格式,detail是固定key,data是自定义返回参数
-
- 在使用组件时,统一以
on开始
的驼峰写法
:onFuncName="bindFuncName"
进行事件绑定,方便同时兼容支付宝和微信
- 在使用组件时,统一以
-
- 绑定事件的实现之固定兼容方式
// 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