wxapp-boilerplate icon indicating copy to clipboard operation
wxapp-boilerplate copied to clipboard

希望能提供对支付宝小程序js api转换的处理

Open chaucerling opened this issue 6 years ago • 5 comments

类似这个项目https://github.com/douzi8/wxToAlipay/tree/8047bb809f32a2c882f24a5584b264018e91b1ab/lib/js

能自定义mapping,封装微信和小程序的差异

chaucerling avatar Oct 08 '18 10:10 chaucerling

Cap32 avatar Oct 09 '18 05:10 Cap32

//src/shim.js
function noop() {}

function paramsMap(options, maps = {}) {
	let params = {};

	for (let key in options) {
		let myKey = maps.hasOwnProperty(key) ? maps[key] : key;
		params[myKey] = options[key];
	}

	return params;
}

// 修改 api params 格式
function setStorageSync(key, data) {
	return {
		key,
		data,
	};
}

function removeStorageSync(key) {
	return {
		key,
	};
}

function getStorageSync(key) {
	return my.getStorageSync({
		key: key,
	}).data;
}

function previewImage(options) {
	let params = paramsMap(options);
	let current = params.current;

	if (current) {
		current = options.urls.indexOf(current);
	}

	if (current === -1 || !current) {
		current = 0;
	}

	params.current = current;

	return params;
}

function makePhoneCall(options) {
	return paramsMap(options, {
		phoneNumber: 'number',
	});
}

function request(options) {
	let params = paramsMap(options, {
		header: 'headers',
	});
	let success = params.success || noop;

	params.success = function (res) {
		let result = paramsMap(res, {
			headers: 'header',
			status: 'statusCode',
		});

		success(result);
	};

	return params;
}

/**
 * wx success里面的system字段为'Android 6.0.1'
 */
function getSystemInfo(options) {
	let params = paramsMap(options);
	let success = params.success || noop;

	params.success = function (res) {
		success(getSystemInfoSync(res));
	};

	return params;
}

function getSystemInfoSync(res) {
	res.system = res.platform + ' ' + res.system;

	// 支付宝小程序windowHeight可能拿到0
	if (!res.windowHeight) {
		res.windowHeight = parseInt(res.screenHeight * res.windowWidth / res.screenWidth, 10) - 40;
	}

	return res;
}

/**
 * wx模态弹窗不同的参数对应到支付宝confirm和alert API
 */
function showModal(options) {
	let params = paramsMap(options);
	let showCancel = params.showCancel;

	if (typeof showCancel === 'undefined') {
		showCancel = true;
	}

	// 确认框
	if (showCancel) {
		params.confirmButtonText = params.confirmText;
		params.cancelButtonText = params.cancelText;
	}
	else {
		// 提醒框
		params.buttonText = params.confirmText;
	}

	my[showCancel ? 'confirm' : 'alert'](params);
}

/**
 * 参数{icon: 'loading'} 无法成功映射,建议不要使用
 */
function showToast(options) {
	let params = paramsMap(options, {
		title: 'content',
		icon: 'type',
	});

	return params;
}

/**
 * sucess回调没有取消操作
 * 点击取消或蒙层时,回调fail, errMsg 为 "showActionSheet:fail cancel"
 */
function showActionSheet(options) {
	let params = paramsMap(options, {
		itemList: 'items',
	});

	let success = params.success || noop;
	let fail = params.fail || noop;

	params.success = function ({
		index: tapIndex,
	}) {
		if (tapIndex === -1) {
			fail({
				errMsg: 'showActionSheet:fail cancel',
			});
		}
		else {
			success({
				tapIndex,
			});
		}
	};

	return params;
}

// 此函数可以根据业务自行定制, 参数无法统一映射
function login(options) {
	let params = paramsMap(options);
	let success = params.success || noop;

	params.scopes = 'auth_user';

	params.success = function (res) {
		success({
			code: res.authCode,
		});
	};

	return params;
}

// 此函数可以根据业务自行定制, 参数无法统一映射
function requestPayment(options) {
	let params = paramsMap(options, {
		alipay_trade_body: 'orderStr',
	});

	let success = params.success || noop;
	let fail = params.fail || noop;

	params.success = function (res) {
		if (res.resultCode === 9000) {
			success();
		}
		else {
			fail();
		}
	};

	return params;
}

function showLoading(options) {
	let params = paramsMap(options, {
		title: 'content',
	});

	return params;
}

const FUNCTION_MAP = {
	login: 'getAuthCode',
	request: 'httpRequest',
	setNavigationBarTitle: 'setNavigationBar',
	setNavigationBarColor: 'setNavigationBar',
	requestPayment: 'tradePay',
};

// // 处理
const shim = {};
const myShim = () => {
	for (let key in my) {
		if (my.hasOwnProperty(key) && typeof my[key] === 'function') {
			let result = FUNCTION_MAP[key];
			let apiName = result || key;
			let paramsFunc = null;
			let func = null;
			switch (key) {
				case 'login':
					paramsFunc = login;
					break;
				case 'showActionSheet':
					paramsFunc = showActionSheet;
					break;
				case 'showToast':
					paramsFunc = showToast;
					break;
				case 'showLoading':
					paramsFunc = showLoading;
					break;
				case 'request':
					paramsFunc = request;
					break;
				case 'makePhoneCall':
					paramsFunc = makePhoneCall;
					break;
				case 'previewImage':
					paramsFunc = previewImage;
					break;
				case 'setStorageSync':
					paramsFunc = setStorageSync;
					break;
				case 'removeStorageSync':
					paramsFunc = removeStorageSync;
					break;
				case 'getSystemInfo':
					paramsFunc = getSystemInfo;
					break;
				case 'requestPayment':
					paramsFunc = requestPayment;
					break;
				case 'showModal':
					paramsFunc = showModal;
					break;
				case 'getStorageSync':
					func = getStorageSync;
					break;
				case 'getSystemInfoSync':
					func = getSystemInfoSync;
					break;
					// return getSystemInfoSync(my.getSystemInfoSync());
			}
			shim[key] = (options, ...params) => {
				if (func) {
					console.log(`调用 api:${apiName}, params:${JSON.stringify(params)}`);
					return func(options, ...params);
				}
				else if (paramsFunc) {
					console.log(`调用 void api:${apiName}, params:${JSON.stringify(paramsFunc(options, ...params))}`);
					return my[apiName](paramsFunc(options, ...params));
				}
				else {
					console.log(`调用 void api:${apiName}, params:${JSON.stringify(options, ...params)}`);
					return my[apiName](options, ...params);
				}
			};
		}
	}
};
myShim();

export {
	shim as default,
};

然后在具体页面引用 import shim from 'shim.js';,将 wx. 调用改为 shim. 是可以在编译为支付宝小程序时正确执行,不需要改动调用参数

但我想在webpack打包成支付宝小程序时引用 shim.js,并且将 wx 改为 shim

//webpack.config.babel.js
plugins: [
  new EnvironmentPlugin({
    NODE_ENV: 'development',
  }),
  new DefinePlugin({
    __DEV__: isDev,
    __WECHAT__: isWechat,
    __ALIPAY__: isAlipay,
    wx: isWechat ? 'wx' : 'shim',
    my: isWechat ? 'wx' : 'shim',
  }),
  new WXAppWebpackPlugin({
    clear: !isDev,
  }),
  new ProvidePlugin({
    'shim': 'shim',
  }),
  new optimize.ModuleConcatenationPlugin(),
  new IgnorePlugin(/vertx/),
  shouldLint && new StylelintPlugin(),
  min && new MinifyPlugin(),
  new CopyPlugin(copyPatterns, { context: srcDir }),
].filter(Boolean),
///
alias: {
  utils: require('path').resolve(__dirname, 'src/utils'),
  shim: require('path').resolve(__dirname, 'src/shim.js'),
},

这样就报错了 Uncaught ReferenceError: shim is not defined

chaucerling avatar Oct 09 '18 17:10 chaucerling

Uncaught ReferenceError: shim is not defined 看起来是 runtime error,能否提供更多的调用上下文和 error stack?

不过从你的配置可以看得出一点问题:

//webpack.config.babel.js
// ...
new DefinePlugin({
    __DEV__: isDev,
    __WECHAT__: isWechat,
    __ALIPAY__: isAlipay,
    wx: isWechat ? 'wx' : 'shim',
    my: isWechat ? 'wx' : 'shim',
  })

当打包 target 是支付宝小程序时,my 会变成 shim,而在 src/shim.js 里,for (let key in my) { 这里的 my 将会被转换为 shim,此时 shim 为空对象。因此个人建议 webpack.config.jsDefinePlugin 应该是 my: isWechat ? 'wx' : 'my',不然编译后的代码将无法引用 my 对象

另外,src/shim.js 里面可以通过 if (__WECHAT__ ) {}if (__ALIPAY__) {} 来判断环境差异

Cap32 avatar Oct 11 '18 04:10 Cap32

我现在写了一个 miniapp.js 来封装一般的 api,在 page.js 里将 wxmy 替换为 miniapp 业务相关的 api,例如登录授权和支付要看具体流程另外封装

// src/utils/miniapp.js
// 整合各小程序的api,命名和调用参数以微信为标准
import promisify from 'utils/promisify.js';

function noop() {}

function paramsMap(options, maps = {}) {
	let params = {};

	for (let key in options) {
		let myKey = maps.hasOwnProperty(key) ? maps[key] : key;
		params[myKey] = options[key];
	}
	return params;
}

function mySystemInfo2wx(systemInfo) {
	systemInfo.system = `${systemInfo.platform} ${systemInfo.system}`;
	// 支付宝小程序windowHeight可能拿到0
	if (!systemInfo.windowHeight) {
		systemInfo.windowHeight = parseInt(systemInfo.screenHeight * systemInfo.windowWidth / systemInfo.screenWidth, 10) - 40;
	}
	return systemInfo;
}

const miniapp = {
	request: (options) => {
		if (__WECHAT__) {
			return wx.request(options);
		}
		if (__ALIPAY__) {
			let params = paramsMap(options, {
				header: 'headers',
			});
			let success = params.success || noop;
			params.success = function (res) {
				let result = paramsMap(res, {
					headers: 'header',
					status: 'statusCode',
				});

				success(result);
			};
			return my.httpRequest(params);
		}
	},
	getLocation: (options) => {
		if (__WECHAT__) {
			// wgs84 返回 gps 坐标,gcj02 返回可用于 wx.openLocation 的坐标
			return wx.getLocation(options);
		}
		if (__ALIPAY__) {
			console.warn('alipay api: getLocation 只支持 gcj02 坐标系');
			options.type = 0; // 高德地图用的是 gcj02
			return my.getLocation(options);
		}
	},
	openLocation: (options) => {
		if (__WECHAT__) {
			return wx.openLocation(options);
		}
		if (__ALIPAY__) {
			options.scale = options.scale || 18;
			return my.getLocation(options);
		}
	},
	getSystemInfo: (options) => {
		if (__WECHAT__) {
			return wx.getSystemInfo(options);
		}
		if (__ALIPAY__) {
			let success = options.success || noop;
			options.success = function (res) {
				success(mySystemInfo2wx(res));
			};
			return my.getSystemInfo(options);
		}
	},
	getSystemInfoSync: () => {
		if (__WECHAT__) {
			return wx.getSystemInfoSync();
		}
		if (__ALIPAY__) {
			return mySystemInfo2wx(my.getSystemInfoSync());
		}
	},
	getStorage: (options) => {
		if (__WECHAT__) {
			return wx.getStorage(options);
		}
		if (__ALIPAY__) {
			let fail = options.fail || noop;
			options.success = function (res) {
				if (!res.data) {
					fail(res);
				}
 else {
					console.log(res);
				}
			};
			return my.getStorage(options);
		}
	},
	getStorageSync: (key) => {
		if (__WECHAT__) {
			return wx.getStorageSync(key);
		}
		if (__ALIPAY__) {
			return my.getStorageSync({
				key: key,
			}).data;
		}
	},
	setStorage: (options) => {
		if (__WECHAT__) {
			return wx.setStorage(options);
		}
		if (__ALIPAY__) {
			return my.setStorage(options);
		}
	},
	setStorageSync: (key, data) => {
		if (__WECHAT__) {
			return wx.setStorageSync(key, data);
		}
		if (__ALIPAY__) {
			return my.setStorageSync({
				key: key,
				data: data,
			});
		}
	},
	removeStorage: (options) => {
		if (__WECHAT__) {
			return wx.removeStorage(options);
		}
		if (__ALIPAY__) {
			return my.removeStorage(options);
		}
	},
	removeStorageSync: (key) => {
		if (__WECHAT__) {
			return wx.removeStorageSync(key);
		}
		if (__ALIPAY__) {
			return my.removeStorageSync({
				key: key,
			});
		}
	},
	clearStorage: (options) => {
		if (__WECHAT__) {
			return wx.clearStorage(options);
		}
		if (__ALIPAY__) {
			return my.clearStorage(options);
		}
	},
	clearStorageSync: () => {
		if (__WECHAT__) {
			return wx.clearStorageSync();
		}
		if (__ALIPAY__) {
			return my.clearStorageSync();
		}
	},
	redirectTo: (options) => {
		if (__WECHAT__) {
			return wx.redirectTo(options);
		}
		if (__ALIPAY__) {
			return my.redirectTo(options);
		}
	},
	navigateTo: (options) => {
		if (__WECHAT__) {
			return wx.navigateTo(options);
		}
		if (__ALIPAY__) {
			return my.navigateTo(options);
		}
	},
	navigateBack: (options) => {
		if (__WECHAT__) {
			return wx.navigateBack(options);
		}
		if (__ALIPAY__) {
			return my.navigateBack(options);
		}
	},
	showModal: (options) => {
		if (__WECHAT__) {
			return wx.showModal(options);
		}
		if (__ALIPAY__) {
			// 支付宝不支持自定义按钮颜色
			if (options.showCancel) {
				let params = paramsMap(options, {
					confirmButtonText: 'confirmText',
					cancelButtonText: 'cancelText',
				});
				return my.confirm(params);
			}
			else {
				let params = paramsMap(options, {
					buttonText: 'confirmText',
				});
				return my.alert(params);
			}
		}
	},
	showToast: (options) => {
		if (__WECHAT__) {
			return wx.showToast(options);
		}
		if (__ALIPAY__) {
			let params = paramsMap(options, {
				content: 'title',
				type: 'icon',
			});
			params.duration = params.duration || 1500;
			if (params.type === 'loading') {
				params.type = 'none';
				console.warn('alipay api: showToast 不支持 type=loading, 暂时使用 none 代替');
			}
			return my.showToast(options);
		}
	},
	hideToast: () => {
		if (__WECHAT__) {
			return wx.hideToast();
		}
		if (__ALIPAY__) {
			return my.hideToast();
		}
	},
	showShareMenu: (options) => {
		if (__WECHAT__) {
			return wx.showShareMenu(options);
		}
		if (__ALIPAY__) {
			return null;
		}
	},
	makePhoneCall: (options) => {
		if (__WECHAT__) {
			return wx.makePhoneCall(options);
		}
		if (__ALIPAY__) {
			let params = paramsMap(options, {
				number: 'phoneNumber',
			});
			return my.makePhoneCall(params);
		}
	},
	setNavigationBarTitle: (options) => {
		if (__WECHAT__) {
			return wx.setNavigationBarTitle(options);
		}
		if (__ALIPAY__) {
			return my.setNavigationBar(options);
		}
	},
	setNavigationBarColor: (options) => {
		if (__WECHAT__) {
			return wx.setNavigationBarColor(options);
		}
		if (__ALIPAY__) {
			console.warn('alipay api: setNavigationBar 不支持 frontColor, animation');
			return my.setNavigationBar(options);
		}
	},
};

miniapp.pro = {};
for (let key in miniapp) {
	if (miniapp.hasOwnProperty(key) && typeof miniapp[key] === 'function') {
		miniapp.pro[key] = promisify(miniapp[key]);
	}
}

export {
	miniapp as default,
};

webpack 添加了对 json 和 wxml 的转换

//webpack.config.babel.js
module: {
  rules: [isWechat ? {} : {
      test: /\.json$/,
      loader: 'string-replace-loader',
      options: {
        multiple: [{
            search: 'navigationBarTitleText',
            replace: 'defaultTitle',
            flags: 'g',
          },
          {
            search: 'navigationBarBackgroundColor',
            replace: 'titleBarColor',
            flags: 'g',
          },
          {
            search: 'enablePullDownRefresh',
            replace: 'pullRefresh',
            flags: 'g',
          },
          {
            search: 'usingComponents',
            replace: 'usingComponents',
            flags: 'g',
          }
        ],
      },
    },
    // *.wxml
    isWechat ? {} : {
      test: /\.wxml$/,
      include: /src/,
      loader: 'string-replace-loader',
      options: {
        multiple: [{
            search: 'wx:if',
            replace: 'a:if',
            flags: 'g',
          },
          {
            search: 'wx:elif',
            replace: 'a:elif',
            flags: 'g',
          },
          {
            search: 'wx:else',
            replace: 'a:else',
            flags: 'g',
          },
          {
            search: 'wx:for',
            replace: 'a:for',
            flags: 'g',
          },
          {
            search: 'wx:for-index',
            replace: 'a:for-index',
            flags: 'g',
          },
          {
            search: 'wx:for-item',
            replace: 'a:for-item',
            flags: 'g',
          },
          {
            search: 'bindtap',
            replace: 'onTap',
            flags: 'g',
          },
          {
            search: 'catchtap',
            replace: 'catchTap',
            flags: 'g',
          },
          {
            search: 'bindinput',
            replace: 'onInput',
            flags: 'g',
          },
          {
            search: 'bindchange',
            replace: 'onChange',
            flags: 'g',
          },
          {
            search: 'bindfocus',
            replace: 'onFocus',
            flags: 'g',
          },
          {
            search: 'bindsubmit',
            replace: 'onSubmit',
            flags: 'g',
          },
          {
            search: 'bindscrolltolower',
            replace: 'onScrollToLower',
            flags: 'g',
          },
        ],
      },
    },
  ]
}

//  全局引入 miniapp,供 page.js 调用
new ProvidePlugin({
    'miniapp': [require('path').resolve(__dirname, 'src/utils/miniapp.js'), 'default'],
}),

chaucerling avatar Oct 18 '18 03:10 chaucerling

我在开发支付宝小程序过程中遇到的一些兼容问题,然后在这个项目的基础上添加了一些功能,保证代码能以微信的标准做开发 写了一个example,如果这边考虑整合,我可以提交一个pr https://github.com/chaucerling/wxapp-boilerplate-alipay-example

chaucerling avatar Dec 30 '18 09:12 chaucerling