項目不大或新開項目导坟,可優(yōu)先嘗試使用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全局數(shù)據舍沙,只能通過在app中定義好的函數(shù)訪問。 如下:
// 正確方式
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參數(shù)說明 { top: 狀態(tài)欄高度 width:寬度 height:標題bar高度 H:狀態(tài)欄+標題欄 }
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:在支付寶創(chuàng)一個同名
window
對象鹰祸,然后對子元素逐一轉換(如下表)
內容 | 微信條件/字段 | 支付寶目標 |
---|---|---|
導航欄背景顏色 | navigationBarBackgroundColor存在 | titleBarColor |
導航欄標題顏色 | navigationBarTextStyle=white | barButtonTheme = light |
導航欄標題文字內容 | navigationBarTitleText | defaultTitle |
導航欄樣式 | navigationStyle =custom | transparentTitle=always titlePenetrate=YES |
頂部窗口的背景色 | backgroundColorTop | backgroundImageColor |
窗口的背景色 | backgroundColor | backgroundColor |
是否開啟當前頁面下拉刷新 | enablePullDownRefresh | pullRefresh |
頁面上拉觸底事件觸發(fā)時距頁面底部距離,單位為px | onReachBottomDistance | onReachBottomDistance |
設置為 true 則頁面整體不能上下滾動 | disableScroll=True | allowsBounceVertical=NO |
使用組件集合 | usingComponents | usingComponents |
是否為組件 | component | component |
- tabBar:在支付寶創(chuàng)一個同名
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循環(huán)語句
"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函數(shù) - 2.2 支付寶授權錯誤回調兼容(可選操作):
onError="onAuthError"
遥缕, 在onAuthError
中處理授權失敗回調(包括用戶拒絕和系統(tǒng)異常)問題
- 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函數(shù) - 2.2 支付寶授權錯誤回調兼容(可選操作):onError="onAuthError"宵呛, 在onAuthError中處理授權失敗回調(包括用戶拒絕和系統(tǒng)異常)問題
- 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" : "http:///****** alipay begin",
"*///****** alipay end" : "http:///****** alipay end",
"http:///****** wx begin" : "/****** wx begin",
"http:///****** 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需要按如下規(guī)定書寫:
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
組件兼容方式(對組件進行更細致的封裝膜蠢,統(tǒng)一兼容,實測對特俗組件兼容度不夠好)
-
- 對自定義組件進行逐一兼容(本篇采用方式莉兰,更具針對性)挑围,如下:為了兼容支付寶,需要做一些適配糖荒、增加一些設定
8.1 支付寶組件中的樣式沒有區(qū)域隔離杉辙,會對使用組件的頁面生效,導致顯示異常捶朵,所以需要自行進行區(qū)域隔離(如:可以加上__
或 組件名稱簡寫__
類似的前綴蜘矢、 mp__classname等)
8.2 事件綁定:微信是提供triggerEvent
方法進行事件傳遞,而支付寶是提供props中的屬性函數(shù)
综看。 兼容方式如下:
-
微信
綁定事件的方法名
統(tǒng)一首字母大寫
: 如 this.triggerEvent('FuncName'
, data) >>> 對應支付寶 >>> this.props.onFuncName
({detail: data})
-
-
支付寶
的綁定事件需要寫在props中
品腹,且以on開始
的駝峰寫法
:onFuncName
, 通過在組件內使用this.props.onFuncName({detail: data})
進行事件觸發(fā), 參數(shù)傳遞時需要以{detail: data}
這樣的固定格式,detail是固定key红碑,data是自定義返回參數(shù)
-
- 在使用組件時舞吭,統(tǒng)一以
on開始
的駝峰寫法
:onFuncName="bindFuncName"
進行事件綁定,方便同時兼容支付寶和微信
- 在使用組件時舞吭,統(tǒng)一以
-
- 綁定事件的實現(xiàn)之固定兼容方式
// 1. 在methods中實現(xiàn)下面函數(shù) 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 // 根據實際情況調用對應的函數(shù) if (name === 'EventFuncName') { // 注意EventName首字大寫 this.props.onEventFuncName({detail: detail}) // 參數(shù)固定寫法 } } // 2. 在事件觸發(fā)的地方調用bindEvent this.bindEvent('EventFuncName', { key: value, ... }); },
8.3 固定寫法:代碼分為微信
析珊、支付寶
羡鸥、公共
,并用固定注釋標記區(qū)域(如下)唾琼,方便轉換工具進行轉換(特定格式進行隔離兄春,方便轉換是進行切換)
```
///****** wx begin
wx code...
///****** wx end
/****** alipay begin
my code...
*///****** alipay end
```
8.4 支付寶不支持通過setData
的方法設置props的屬性
,所以直接使用如this.properties.curStore = curStore
的方法修改properties
屬性。 如果使用setData方法取修改props屬性會導致數(shù)據渲染異常(會在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實現(xiàn)參考源碼
// ======================== .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 () {
/// 總文件數(shù)
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; // 已處理數(shù)量
NSInteger __block qty = 0; // 已處理數(shù)量
NSInteger __block failQty = 0; // 失敗文件數(shù)量
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];
// 創(chuàng)建文件夾
[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"]) { // 頁面上拉觸底事件觸發(fā)時距頁面底部距離芜茵,單位為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" : @"http:///****** alipay begin",
@"*///****** alipay end" : @"http:///****** alipay end",
@"http:///****** wx begin" : @"/****** wx begin",
@"http:///****** 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