# tms-core
# index
tms-core入口文件
点击查看源码
/**
* @module: index
* @desc: tms-core入口文件
*/
import runtime from './runtime/index';
import Request from './request';
import AutoReport from './report/proxy/index';
import Reporter from './report/index';
import { getConfig, getApolloConfig } from './config';
import syncApi from './syncfnmanager';
import nav from './navigator';
import { getLogManager, getRealtimeLogManager } from './log';
import { setEnvInfo, getEnvInfo, setAuthInfo, setAppPagePaths, isAppPageExist, getHomePage } from './env';
import md5 from './md5';
import { callCloudFunc } from './cloudService';
import { getEventDispatcher } from './eventDispatcher';
import { serialize } from './objUtils';
import { calCoordinateDistance, formatDistance } from './distanceUtils';
import { rpxToPx, pxToRpx } from './rpx';
import { getNavBarConfigData } from './navbarUtils';
import { compareVersion } from './compareVersion';
import { formatPlate, subStr, hidePhoneCenter, isValidPhone, isValidPlate, isValidAuthCode, roundStr, } from './stringUtils';
import { round, } from './numUtils';
import { groupTimeDuration, formatTimeDuration, formatTime, formatTimeStr, formatTimeWithDetails, formatDateTime, dateToString, parseDateTime, } from './timeUtils';
import { ipxInit, isIPX, getIpxClass, getIpxConfig, } from './ipxHelper';
import getLocInstance from './location/index';
import LocationBase from './location/base';
import { getMpOpenId, getOuterOpenId, getEncryptUserInfo } from './mpInfo';
import * as storage from './storage';
import * as uiUtil from './tmsuiUtils';
import { throttle, debounce } from './funcUtils';
/**
* @public
* @description 创建网络请求对象,用于向腾讯出行服务平台后台发送网络请求
* @param {Object} [config] 参数配置
* @param {Boolean} [config.withAuth=true] 是否填充登录态参数
* @param {String} [config.host] 自定义的host域名
* @param {Object} [config.baseParam] 默认携带的参数
* @returns {Object} [Request实例](#class-request)
* @example
* const $ = getApp().tms.createRequest();
* $.get(apiPath)
* .then((resp) => {
* // ...
* })
* .catch((err) => {
* // ...
* });
*/
const createRequest = (config = {}) => new Request(config);
/**
* @description 埋点上报
* @returns {Object} 包含[report方法](#report)的对象
* @example
* getReporter().report2('当前页面|组件的唯一标志', {});
*/
const getReporter = () => Reporter;
/**
* @description 获取地理位置方法的集合
* @returns {Class} [Location类](#class-location)
*/
const getLocationManager = () => getLocInstance();
/**
* @description 获取位置信息base类(业务无关)
* @returns {Class} [Location类](#class-location)
*/
const getLocationBaseClass = () => LocationBase;
/**
* @description init core包初始化, 小程序已在app.js中对齐初始化
* @param {Object} options 初始化
* @param {String} options.appVersion 小程序本次发版的版本号
* @param {String} options.wxAppId appId
* @param {String} options.secretKey 密钥
* @param {String} options.client 小程序名
* @param {String} options.appEnv 环境名称 production、development、test、predist
* @param {Array} options.appPagePaths 小程序包含的所有页面路径
* @param {String} options.homePage 首页的相关配置 { path: "/page/**", isTab: false }
* @param {String} options.cloudEnvId 云函数的环境id
* @param {String} options.defaultHost 小程序请求调用接口的域名,所有请求都会走腾讯出行网关服务,腾讯出行网关在转发到相关服务
* @returns {void} 无返回值.
*/
const init = (options = {}) => {
const { appVersion, wxAppId, secretKey = '', client, defaultHost, cloudEnvId, appEnv, appPagePaths, homePage, } = options;
const envInfo = {
wxAppId,
appVersion,
appEnv,
client,
cloudEnvId,
};
setEnvInfo(envInfo);
setAppPagePaths(appPagePaths, homePage);
Reporter.init(envInfo);
AutoReport.init();
Request.defaultHost = defaultHost;
Request.defaultSecretKey = secretKey;
// 初始化云环境
wx.cloud.init({ env: cloudEnvId });
};
/**
* @description 获取用户位置信息 -- 兼容版 现有业务中有的使用promise, 有的没有, 做一下兼容
* @returns { Promise } 位置信息
*/
const getUserLocation = () => {
const userLocation = getLocInstance().getUserLocation();
if (userLocation) {
return Promise.resolve(userLocation);
}
return getLocInstance().getLocationDetail(false);
};
/**
* @namespace api
* @description 对外暴露的api
* @param {Function} init 初始化方法
* @param {Function} createRequest 创建Request的实例的方法
* @param {Function} getReporter 获取上报管理器
* @param {Function} getLocationManager 获取地理位置管理器
* @param {Function} setUserLocation 设置用户位置信息
* @param {Function} getUserLocation 获取用户位置信息
* @param {Function} cloudService... 云函数相关处理查看 [cloudService](#clouddservice)
* @param {Function} compareVersion... 版本比较查看 [compareVersion](#compareversion)
* @param {Function} config... 配置相关函数查看 [config](#config)
* @param {Function} env... 环境相关处理查看 [env](#env)
* @param {Function} eventdispatcher... 事件管理相关处理 [eventdispatcher](#eventdispatcher)
* @param {Function} ipxHelper... iphonex相关方法查看 [ipxHelper](#ipxhelper)
* @param {Function} log... 上报相关方法查看 [log](#log)
* @param {Function} md5... md5加密查看 [md5](#md5)
* @param {Function} mpInfo... 服务接入相关接口查看 [mpInfo](#mpinfo)
* @param {Function} navbarUtils... 获取导航高度的相关函数查看 [navbarUtils](#navbarutils)
* @param {Function} navigator... 小程序跳转相关方法 [navigator](#navigator)
* @param {Function} numUtils... 数字相关处理查看 [numUtils](#numutils)
* @param {Function} objUtils... 对象相关处理查看[objUtils](#objutils)
* @param {Function} request... 请求相关处理查看 [request](#request)
* @param {Function} rpx... px与rpx的相互转化查看 [rpx](#rpx)
* @param {Function} runtime... 集成业务通用接口查看 [runtime-index](#runtime-index)
* @param {Function} storage... 本地缓存查看 [storage](#storage)
* @param {Function} stringUtils... 字符串相关处理查看 [stringUtils](#stringutils)
* @param {Function} syncfnmanager... 小程序调用wx同步方法的管理查看 [syncfnmanager](#syncfnmanager)
* @param {Function} timeUtils... 数字相关处理查看 [timeUtils](#timeutils)
*/
const api = {
init,
createRequest,
setAuthInfo,
getLogManager,
getRealtimeLogManager,
md5,
getReporter,
getLocationManager,
getLocationBaseClass,
getEventDispatcher,
getEnvInfo,
getConfig,
getApolloConfig,
navigateToWebview: nav.navigateToWebview,
isAppPageExist,
getHomePage,
callCloudFunc,
setUserLocation: (loc) => {
getLocInstance().setUserLocation(loc);
},
getUserLocation,
/* 字符串方法 */
formatPlate,
subStr,
hidePhoneCenter,
isValidPhone,
isValidPlate,
isValidAuthCode,
roundStr,
/* 数字方法 */
round,
/* 时间方法 */
groupTimeDuration,
formatTimeDuration,
formatTime,
formatTimeStr,
formatTimeWithDetails,
formatDateTime,
dateToString,
parseDateTime,
/* IPX方法 */
ipxInit,
isIPX,
getIpxClass,
getIpxConfig,
/* 处理对象方法 */
serialize,
/* 距离方法 */
calCoordinateDistance,
formatDistance,
/* 获取外部合作商openid */
getMpOpenId,
getOuterOpenId,
/* 获取加密的用户信息 */
getEncryptUserInfo,
rpxToPx,
pxToRpx,
compareVersion,
getNavBarConfigData,
storage,
...syncApi,
...uiUtil,
throttle,
debounce,
...runtime,
};
export default api;
# namespace api
对外暴露的api
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| init | function | 初始化方法 | --- | 否 |
| createRequest | function | 创建Request的实例的方法 | --- | 否 |
| getReporter | function | 获取上报管理器 | --- | 否 |
| getLocationManager | function | 获取地理位置管理器 | --- | 否 |
| setUserLocation | function | 设置用户位置信息 | --- | 否 |
| getUserLocation | function | 获取用户位置信息 | --- | 否 |
| cloudService... | function | 云函数相关处理查看 cloudService | --- | 否 |
| compareVersion... | function | 版本比较查看 compareVersion | --- | 否 |
| config... | function | 配置相关函数查看 config | --- | 否 |
| env... | function | 环境相关处理查看 env | --- | 否 |
| eventdispatcher... | function | 事件管理相关处理 eventdispatcher | --- | 否 |
| ipxHelper... | function | iphonex相关方法查看 ipxHelper | --- | 否 |
| log... | function | 上报相关方法查看 log | --- | 否 |
| md5... | function | md5加密查看 md5 | --- | 否 |
| mpInfo... | function | 服务接入相关接口查看 mpInfo | --- | 否 |
| navbarUtils... | function | 获取导航高度的相关函数查看 navbarUtils | --- | 否 |
| navigator... | function | 小程序跳转相关方法 navigator | --- | 否 |
| numUtils... | function | 数字相关处理查看 numUtils | --- | 否 |
| objUtils... | function | 对象相关处理查看objUtils | --- | 否 |
| request... | function | 请求相关处理查看 request | --- | 否 |
| rpx... | function | px与rpx的相互转化查看 rpx | --- | 否 |
| runtime... | function | 集成业务通用接口查看 runtime-index | --- | 否 |
| storage... | function | 本地缓存查看 storage | --- | 否 |
| stringUtils... | function | 字符串相关处理查看 stringUtils | --- | 否 |
| syncfnmanager... | function | 小程序调用wx同步方法的管理查看 syncfnmanager | --- | 否 |
| timeUtils... | function | 数字相关处理查看 timeUtils | --- | 否 |
# Object createRequest
创建网络请求对象,用于向腾讯出行服务平台后台发送网络请求
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| config | Object | 参数配置 | --- | 是 |
| config.withAuth | Boolean | 是否填充登录态参数 | --- | 是 |
| config.host | String | 自定义的host域名 | --- | 是 |
| config.baseParam | Object | 默认携带的参数 | --- | 是 |
返回值
| 类型 | 说明 |
|---|---|
Object | Request实例 |
示例代码
Example
const $ = getApp().tms.createRequest();
$.get(apiPath)
.then((resp) => {
// ...
})
.catch((err) => {
// ...
});
# Object getReporter
埋点上报
返回值
| 类型 | 说明 |
|---|---|
Object | 包含report方法的对象 |
示例代码
Example
getReporter().report2('当前页面|组件的唯一标志', {});
# Class getLocationManager
获取地理位置方法的集合
返回值
| 类型 | 说明 |
|---|---|
Class | Location类 |
# Class getLocationBaseClass
获取位置信息base类(业务无关)
返回值
| 类型 | 说明 |
|---|---|
Class | Location类 |
# void init
init core包初始化, 小程序已在app.js中对齐初始化
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| options | Object | 初始化 | --- | 否 |
| options.appVersion | String | 小程序本次发版的版本号 | --- | 否 |
| options.wxAppId | String | appId | --- | 否 |
| options.secretKey | String | 密钥 | --- | 否 |
| options.client | String | 小程序名 | --- | 否 |
| options.appEnv | String | 环境名称 production、development、test、predist | --- | 否 |
| options.appPagePaths | Array | 小程序包含的所有页面路径 | --- | 否 |
| options.homePage | String | 首页的相关配置 { path: "/page/**", isTab: false } | --- | 否 |
| options.cloudEnvId | String | 云函数的环境id | --- | 否 |
| options.defaultHost | String | 小程序请求调用接口的域名,所有请求都会走腾讯出行网关服务,腾讯出行网关在转发到相关服务 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
void | 无返回值. |
# Promise getUserLocation
获取用户位置信息 -- 兼容版 现有业务中有的使用promise, 有的没有, 做一下兼容
返回值
| 类型 | 说明 |
|---|---|
Promise | 位置信息 |
# cloudService
小程序调用云函数相关方法
点击查看源码
/**
* @desc: 小程序调用云函数相关方法
*/
import { getEnvInfo, getAuthInfo } from './env';
const logger = wx.getLogManager({});
/**
* callCloudFunc 方法 调用云函数
* @param {String} name 要调用的云函数
* @param {Object} data 需要传给云函数的参数
* @param {Boolean} withAuth 是否需要补充userId,token参数,当云函数需要调用SinanServer时,需要这些参数来完成鉴权
* @returns {viod} 云函数
* @example
* const { tms } = getApp({ allowDefault: true });
* tms.callCloudFunc('user', { $url: 'user/getState', ...param }, true).then(({ result }) => result)
*/
export const callCloudFunc = async (name = '', data = {}, withAuth = true) => {
const { cloudEnvId, appVersion } = getEnvInfo();
const timestamp = Date.now();
const random = Math.random()
.toString()
.slice(2, 7);
const sourceId = 7; // 6 未知 7 云函数 8 出行 9 我的车
const mergedData = {
...data,
timestamp,
seqId: `${timestamp}${sourceId}${random}`,
appVersion, // 补充appVersion参数
};
if (withAuth) { // 补充userId, token参数
const { userId, token } = await getAuthInfo();
Object.assign(mergedData, { userId, token });
}
const res = await new Promise((resolve, reject) => {
wx.cloud.callFunction({
name,
data: mergedData,
config: { env: cloudEnvId },
success: (res) => {
let result = JSON.stringify(res);
if (result.length > 500) {
result = `${result.substring(0, 500)} 内容太长被截断`;
}
const obj = {
name,
env: cloudEnvId,
params: JSON.stringify(mergedData),
res: result,
};
const str = JSON.stringify(obj, null, ' ').replace(/\\"/ig, '\'');
logger.log(`云函数请求成功:\n${str}`);
resolve(res);
},
fail: (err) => {
const obj = {
name,
env: cloudEnvId,
params: JSON.stringify(mergedData),
err: JSON.stringify(err),
};
const str = JSON.stringify(obj, null, ' ').replace(/\\"/ig, '\'');
logger.warn(`云函数请求失败:\n${str}`);
reject({ err, name, mergedData });
},
});
});
return res;
};
# viod callCloudFunc
callCloudFunc 方法 调用云函数
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| name | String | 要调用的云函数 | --- | 否 |
| data | Object | 需要传给云函数的参数 | --- | 否 |
| withAuth | Boolean | 是否需要补充userId,token参数,当云函数需要调用SinanServer时,需要这些参数来完成鉴权 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
viod | 云函数 |
示例代码
Example
const { tms } = getApp({ allowDefault: true });
tms.callCloudFunc('user', { $url: 'user/getState', ...param }, true).then(({ result }) => result)
# compareVersion
点击查看源码
/**
* 版本比较函数
* @param {String} sourceVersion 作为基准版本号
* @param {String} targetVersion 目标比较版本号
* @returns {Number} 比较结果
* 返回值说明:
* 1 : 大于基准版本号
* 0 : 等于基准版本号
* -1: 小于基准版本号
*/
export const compareVersion = (sourceVersion, targetVersion) => {
if (typeof sourceVersion !== 'string' || typeof targetVersion !== 'string') {
throw new Error('版本比较参数类型有误');
}
const toInt = n => parseInt(n, 10); // eslint-disable-line require-jsdoc
const sourceArray = sourceVersion.split('.').map(toInt);
const targetArray = targetVersion.split('.').map(toInt);
for (let i = 0; i < sourceArray.length; i += 1) {
if (sourceArray[i] > targetArray[i]) {
return 1;
}
if (sourceArray[i] < targetArray[i]) {
return -1;
}
}
return 0;
};
# Number compareVersion
版本比较函数
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| sourceVersion | String | 作为基准版本号 | --- | 否 |
| targetVersion | String | 目标比较版本号 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Number | 比较结果返回值说明:1 : 大于基准版本号0 : 等于基准版本号-1: 小于基准版本号 |
# config
小程获取配置相关接口
点击查看源码
/**
* @desc: 小程获取配置相关接口
*/
import Request from './request';
import { getEnvInfo } from './env';
const parseAllCfgs = (configPaths, resData, defaultCfgs) => configPaths.map((path, index) => {
const found = resData.find(cfg => cfg.configPath === path);
if (found) {
return JSON.parse(found.configValue);
}
if (defaultCfgs?.[index]) {
return defaultCfgs[index];
}
return {}; // 没找到配置,返回一个空对象
});
const formatConfigPaths = (configPath) => {
const configPaths = Array.isArray(configPath) ? configPath : [configPath];
const { client } = getEnvInfo();
configPaths.forEach((path, index) => {
configPaths[index] = path.replace(/\$\{client\}/, client);
});
return configPaths;
};
/**
* getConfig 批量拉取配置
* @description 拉取运营平台上的配置内容。关于运营平台的具体用法,参见{@link https://iwiki.woa.com/pages/viewpage.action?pageId=527948584}
* @example <caption>拉取单个配置</caption>
* const { tms } = getApp({ allowDefault: true });
* const cfg = await tms.getConfig('/${client}/violation/subscribe', {}, { title: '当前城市不支持订阅'})
* console.log(cfg); // 成功则返回服务端存储的配置,失败返回默认值
* @example <caption>批量拉取配置</caption>
const { tms } = getApp({ allowDefault: true });
const cfgs = await tms.getConfig([
'/${client}/home/service',
'/${client}/home/navbar',
], {}, [
[
{ caption: '违章代缴', icon: 'violation.png' }
],
{ title: '晚上好,欢迎~' }
]);
console.log(cfgs); // 成功则返回服务端存储的配置,失败返回默认值
* @param {String|Array} configPath 配置路径,单个路径或配置路径数组,支持替换${client}为当前小程序。例如在出行小程序,${client}会变替换为sinan
* @param {Object} extendAttr 扩展属性,传给配置服务用于检索哪个版本的配置适用于当前用户。
* @param {Object} [defaultCfg] 默认配置,请求失败返回默认值。
* @returns {Promise<Object|Array<Object>>}
* 有默认值时,请求失败返回默认值。无默认值时,请求失败返回Promise.reject
*/
function getConfig(configPath, extendAttr = {}, defaultCfg) {
if (Array.isArray(configPath)) { // 复数形式
if (defaultCfg) { // 有默认值
if (!Array.isArray(defaultCfg) || configPath.length !== defaultCfg.length) {
throw new Error('配置路径和默认值的数组长度不一致');
}
}
}
const configPaths = formatConfigPaths(configPath);
const defaultCfgs = (defaultCfg && (Array.isArray(defaultCfg) ? defaultCfg : [defaultCfg])) || null;
const extendAttrs = typeof extendAttr === 'string' ? extendAttr : JSON.stringify(extendAttr);
return Request.getInstance().post('marketing/config', {
extendAttrs,
configPaths,
})
.then((res = {}) => {
const { errCode, resData } = res || {};
if (resData && resData.length > 0) {
const parsed = parseAllCfgs(configPaths, resData, defaultCfgs);
if (Array.isArray(configPath)) {
return parsed;
}
return parsed[0];
}
if (defaultCfgs) {
return Array.isArray(configPath) ? defaultCfgs : defaultCfgs[0];
}
return Promise.reject({ errCode, resData, msg: `获取${configPaths.join(',')}配置失败,接口调用出错` });
})
.catch((e) => {
if (defaultCfgs) {
return Promise.resolve(Array.isArray(configPath) ? defaultCfgs : defaultCfgs[0]);
}
return Promise.reject({ err: e, msg: `获取${configPaths.join(',')}配置失败,接口调用出错` });
});
}
/**
* getApolloConfig 获取阿波罗平台配置
* @description 拉取阿波罗的配置内容。参见{@link http://mapollo.woa.com/#/26fdbf3}
* @example <caption>拉取单个配置</caption>
* const { tms } = getApp({ allowDefault: true });
* const cfg = await tms.getApolloConfig('sinan_me_faq', {}, { name: '默认配置'})
* console.log(cfg); // 成功则返回服务端存储的配置,失败返回默认值
* @param {String|Array} configPath 配置路径,单个路径或配置路径数组,支持替换${client}为当前小程序。例如在出行小程序,${client}会变替换为sinan
* @param {Object} extendAttr 扩展属性,用于自定义的过滤条件,保留字段 businessKey、moduleKey。
* @param {Object} [defaultCfg] 默认配置,请求失败返回默认值。
* @returns {Promise<Object|Array<Object>>}
* 有默认值时,请求失败返回默认值。无默认值时,请求失败返回Promise.reject
*/
function getApolloConfig(configPath, extendAttr = {}, defaultCfg) {
const configPaths = formatConfigPaths(configPath);
const defaultCfgs = (defaultCfg && (Array.isArray(defaultCfg) ? defaultCfg : [defaultCfg])) || null;
const { businessKey, moduleKey, ...extendAttrs } = extendAttr;
return Request.getInstance().post('marketing/apollo/config', {
businessKey,
moduleKey,
configPaths,
extendAttrs: JSON.stringify(extendAttrs),
})
.then((res) => {
const { errCode, resData = {} } = res || {};
const { status, msg, data } = resData;
if (errCode === 0 && status === 0) {
return data
.sort((a, b) =>
// eslint-disable-next-line implicit-arrow-linebreak
configPaths.findIndex(configPath => configPath === a.configKey)
- configPaths.findIndex(configPath => configPath === b.configKey))
.map((item) => {
const newItem = {
...item.content,
};
Object.keys(item).forEach((key) => {
if (key !== 'content') {
newItem[`_${key}`] = item[key];
}
});
return newItem;
});
}
;
throw new Error(msg);
})
.catch((e) => {
if (defaultCfgs)
return Promise.resolve(defaultCfgs);
return Promise.reject({ err: e, msg: `获取${configPaths.join(',')}配置失败` });
});
}
export { getConfig, getApolloConfig, };
# Promise.<(Object|Array.<Object>)> getConfig
拉取运营平台上的配置内容。关于运营平台的具体用法,参见link (opens new window)
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| configPath | String|Array | 配置路径,单个路径或配置路径数组,支持替换${client}为当前小程序。例如在出行小程序,${client}会变替换为sinan | --- | 否 |
| extendAttr | Object | 扩展属性,传给配置服务用于检索哪个版本的配置适用于当前用户。 | --- | 否 |
| defaultCfg | Object | 默认配置,请求失败返回默认值。 | --- | 是 |
返回值
| 类型 | 说明 |
|---|---|
Promise.<(Object|Array.<Object>)> | 有默认值时,请求失败返回默认值。无默认值时,请求失败返回Promise.reject |
示例代码
Example (拉取单个配置)
const { tms } = getApp({ allowDefault: true });
const cfg = await tms.getConfig('/${client}/violation/subscribe', {}, { title: '当前城市不支持订阅'})
console.log(cfg); // 成功则返回服务端存储的配置,失败返回默认值
Example (批量拉取配置)
const { tms } = getApp({ allowDefault: true });
const cfgs = await tms.getConfig([
'/${client}/home/service',
'/${client}/home/navbar',
], {}, [
[
{ caption: '违章代缴', icon: 'violation.png' }
],
{ title: '晚上好,欢迎~' }
]);
console.log(cfgs); // 成功则返回服务端存储的配置,失败返回默认值
# Promise.<(Object|Array.<Object>)> getApolloConfig
拉取阿波罗的配置内容。参见link (opens new window)
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| configPath | String|Array | 配置路径,单个路径或配置路径数组,支持替换${client}为当前小程序。例如在出行小程序,${client}会变替换为sinan | --- | 否 |
| extendAttr | Object | 扩展属性,用于自定义的过滤条件,保留字段 businessKey、moduleKey。 | --- | 否 |
| defaultCfg | Object | 默认配置,请求失败返回默认值。 | --- | 是 |
返回值
| 类型 | 说明 |
|---|---|
Promise.<(Object|Array.<Object>)> | 有默认值时,请求失败返回默认值。无默认值时,请求失败返回Promise.reject |
示例代码
Example (拉取单个配置)
const { tms } = getApp({ allowDefault: true });
const cfg = await tms.getApolloConfig('sinan_me_faq', {}, { name: '默认配置'})
console.log(cfg); // 成功则返回服务端存储的配置,失败返回默认值
# distanceUtils
点击查看源码
/**
* 经纬度距离计算
*/
import { roundStr } from './stringUtils';
// 将角度换算为弧度
function convertDegreesToRadians(degrees) {
return (degrees * Math.PI) / 180;
}
function haverSin(theta) {
const val = Math.sin(theta / 2);
return val * val;
}
// 计算两点间距离,单位 m
function calCoordinateDistance(a, b) {
// 地球半径 平均值,千米
const earthRadius = 6371;
const lat1 = convertDegreesToRadians(a.latitude);
const lon1 = convertDegreesToRadians(a.longitude);
const lat2 = convertDegreesToRadians(b.latitude);
const lon2 = convertDegreesToRadians(b.longitude);
// 差值
const vLon = Math.abs(lon1 - lon2);
const vLat = Math.abs(lat1 - lat2);
const h = haverSin(vLat) + (Math.cos(lat1) * Math.cos(lat2) * haverSin(vLon));
const distance = 2 * earthRadius * Math.asin(Math.sqrt(h));
return distance * 1000;
}
/**
* 对距离进行格式化
* @param {Number} distance 距离,单位米
* @param {Number} keep 保留几位小数
* @param {Boolean} removeTrailingZero 是否移除字符串末尾的无效数字0
* @param {String} unit 单位风格类型:en-英文-m/km,EN-英文大写-M/KM,zh-中文-米/千米,ZH-中文-米/公里
* @returns 格式化后的字符串;distance非法时返回空字符串
*/
function formatDistance(distance, keep = 2, removeTrailingZero = false, unit = 'en') {
const distanceNum = parseFloat(String(distance));
if (isNaN(distanceNum))
return '';
// 数字部分格式化
const num = distanceNum >= 1000 ? distanceNum / 1000 : distanceNum;
const numStr = roundStr(num, keep, removeTrailingZero);
// 单位部分格式化
let units;
switch (unit) {
case 'zh':
units = ['米', '千米'];
break;
case 'ZH':
units = ['米', '公里'];
break;
case 'EN':
units = ['M', 'KM'];
break;
default:
units = ['m', 'km'];
break;
}
const unitStr = units[distanceNum >= 1000 ? 1 : 0];
return `${numStr}${unitStr}`;
}
export { calCoordinateDistance, formatDistance, };
# formatDistance
对距离进行格式化
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| distance | Number | 距离,单位米 | --- | 否 |
| keep | Number | 保留几位小数 | --- | 否 |
| removeTrailingZero | Boolean | 是否移除字符串末尾的无效数字0 | --- | 否 |
| unit | String | 单位风格类型:en-英文-m/km,EN-英文大写-M/KM,zh-中文-米/千米,ZH-中文-米/公里 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
| 格式化后的字符串;distance非法时返回空字符串 |
# env
小程序运行环境相关接口
点击查看源码
/**
* @desc: 小程序运行环境相关接口
*/
/**
* @description 公共对象-环境变量env
* @namespace Env
* @param {String} wxAppId 当前运行时小程序的appId
* @param {String} appVersion 当前运行时所在项目的版本号
* @param {String} appEnv 运行环境 test - 测试、production - 正式
* @param {String} client 运行时项目名,sinan - 腾讯出行服务;
* @param {String} cloudEnvId 运行云函数的环境id
*/
const env = {
wxAppId: '',
appVersion: '',
appEnv: '',
client: '',
cloudEnvId: '',
};
let baseAuthInfo = undefined;
const getAuthInfoQueue = [];
/**
* @description 设置用户信息
* @param {Object} authInfo 用户登录状态信息
* @param {Object} err 当获取用户信息失败时,设置错误信息
* @returns {Void} 无返回值
* @example
* setAuthInfo({
firstLogin: 0
openId: "ou8xs5Uo4MHCETZ_P7IUwOAxvr-M"
token: "d8ffecd9317376bd1de3b49e1fa0e3bb"
uid: "407210826"
unionId: "oFghpwpqGK18ixIlNGy34Y28gCb0"
* })
*/
export const setAuthInfo = (authInfo, err) => {
baseAuthInfo = authInfo;
while (getAuthInfoQueue.length) {
const pro = getAuthInfoQueue.shift();
if (err) {
pro.reject(err);
}
else {
pro.resolve(baseAuthInfo);
}
}
};
/**
* @description 获取登录状态信息
* @returns {Object} 获取当前用户登录状态参数(见上)
* @example
* const data = await getAuthInfo()
{
firstLogin: 0 // 是否第一次登录
openId: "ou8xs5Uo4MHCETZ_P7IUwOAxvr-M"
token: "d8ffecd9317376bd1de3b49e1fa0e3bb"
uid: "407210826"
unionId: "oFghpwpqGK18ixIlNGy34Y28gCb0"
}
*/
export const getAuthInfo = async () => {
if (baseAuthInfo !== undefined) {
return baseAuthInfo;
}
return new Promise((resolve, reject) => getAuthInfoQueue.push({ resolve, reject }));
};
/**
* @description 用于获取运行时所在的环境信息
* @returns {Object} 运行时环境信息 (返回值见上Env:namepace)
*/
export const getEnvInfo = () => env;
/**
* 设置环境变量
* @param {Object} 运行时环境信息 (入参见上Env:namepace)
* @returns {Void} 无.
*/
export const setEnvInfo = (envInfo) => {
const { wxAppId, appVersion, appEnv, client, cloudEnvId } = envInfo;
env.wxAppId = wxAppId;
env.appVersion = appVersion;
env.appEnv = appEnv;
env.client = client;
env.cloudEnvId = cloudEnvId;
};
let appPagePaths = []; // app页面路径集合
let homePageInfo = null; // 首页信息 { isTab, path, tabs }
/**
* 获取app内所有页面的路径
* @returns {Array<String>} 页面路径集合
*/
export const getAppPagePaths = () => appPagePaths;
/**
* 设置app内所有页面的路径
* @param {Array<String>} paths 页面路径集合
* @param {Object|null} homePage 首页信息
* @param {Boolean} homePath.isTab 首页是否tab页
* @param {String} homePath.path 首页页面路径(isTab=false时使用此字段)
* @param {Array<String>} homePath.tabs tab页面路径列表(isTab=true时使用自此段)
* @returns {void}
*/
export const setAppPagePaths = (paths, homePage) => {
appPagePaths = paths;
const { isTab = false, path = '', tabs = [] } = homePage || {};
homePageInfo = { isTab, path, tabs };
};
/**
* 判断页面是否存在于当前app中
* @param {String} page 页面路径
* @returns {Boolean} 页面是否存在app中
*/
export const isAppPageExist = (page) => {
const route = !page ? '' : String(page).split('?')[0];
if (!route || !Array.isArray(appPagePaths))
return false;
const routeWithoutPrefixSlash = route[0] === '/' ? route.substring(1) : route;
return appPagePaths.some(path => path === routeWithoutPrefixSlash || path === `/${routeWithoutPrefixSlash}`);
};
// 以下变量标识各小程序appId
const MOBILITY_APPID = 'wx65cc950f42e8fff1'; // 出行服务小程序AppId
const MOBILITY_DEMO_APPID = 'wxa7ce727b525f80b0'; // 出行服务demo小程序AppId
const SINAN_HOME = '/modules/home/pages/index/index'; // 出行首页地址
const MYCAR_HOME = '/modules/car/index/index'; // 我的车首页地址
/**
* 获取首页信息
* @returns {Object|null} homePage 首页信息
* @returns {Boolean} homePath.isTab 首页是否tab页
* @returns {String} homePath.path 首页页面路径(isTab=false时使用此字段)
* @returns {Array<String>} homePath.tabs tab页面路径列表(isTab=true时使用自此段)
*/
export const getHomePage = () => {
if (homePageInfo)
return homePageInfo;
// 返回默认信息
const mpAppId = wx.getAccountInfoSync()?.miniProgram?.appId;
const homePagePath = [MOBILITY_APPID, MOBILITY_DEMO_APPID].indexOf(mpAppId) > -1 ? SINAN_HOME : MYCAR_HOME;
homePageInfo = { isTab: false, path: homePagePath, tabs: [] };
return homePageInfo;
};
# namespace Env
公共对象-环境变量env
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| wxAppId | String | 当前运行时小程序的appId | --- | 否 |
| appVersion | String | 当前运行时所在项目的版本号 | --- | 否 |
| appEnv | String | 运行环境 test - 测试、production - 正式 | --- | 否 |
| client | String | 运行时项目名,sinan - 腾讯出行服务; | --- | 否 |
| cloudEnvId | String | 运行云函数的环境id | --- | 否 |
# Void setAuthInfo
设置用户信息
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| authInfo | Object | 用户登录状态信息 | --- | 否 |
| err | Object | 当获取用户信息失败时,设置错误信息 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
示例代码
Example
setAuthInfo({
firstLogin: 0
openId: "ou8xs5Uo4MHCETZ_P7IUwOAxvr-M"
token: "d8ffecd9317376bd1de3b49e1fa0e3bb"
uid: "407210826"
unionId: "oFghpwpqGK18ixIlNGy34Y28gCb0"
})
# Object getAuthInfo
获取登录状态信息
返回值
| 类型 | 说明 |
|---|---|
Object | 获取当前用户登录状态参数(见上) |
示例代码
Example
const data = await getAuthInfo()
{
firstLogin: 0 // 是否第一次登录
openId: "ou8xs5Uo4MHCETZ_P7IUwOAxvr-M"
token: "d8ffecd9317376bd1de3b49e1fa0e3bb"
uid: "407210826"
unionId: "oFghpwpqGK18ixIlNGy34Y28gCb0"
}
# Object getEnvInfo
用于获取运行时所在的环境信息
返回值
| 类型 | 说明 |
|---|---|
Object | 运行时环境信息 (返回值见上Env:namepace) |
# Void setEnvInfo
设置环境变量
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| 运行时环境信息 | Object | (入参见上Env:namepace) | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无. |
# Array.<String> getAppPagePaths
获取app内所有页面的路径
返回值
| 类型 | 说明 |
|---|---|
Array.<String> | 页面路径集合 |
# void setAppPagePaths
设置app内所有页面的路径
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| paths | Array.<String> | 页面路径集合 | --- | 否 |
| homePage | Object|null | 首页信息 | --- | 否 |
| homePath.isTab | Boolean | 首页是否tab页 | --- | 否 |
| homePath.path | String | 首页页面路径(isTab=false时使用此字段) | --- | 否 |
| homePath.tabs | Array.<String> | tab页面路径列表(isTab=true时使用自此段) | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
void |
# Boolean isAppPageExist
判断页面是否存在于当前app中
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| page | String | 页面路径 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Boolean | 页面是否存在app中 |
# Objectnull getHomePage
获取首页信息
返回值
| 类型 | 说明 |
|---|---|
Object|null | homePage 首页信息 |
Boolean | homePath.isTab 首页是否tab页 |
String | homePath.path 首页页面路径(isTab=false时使用此字段) |
Array.<String> | homePath.tabs tab页面路径列表(isTab=true时使用自此段) |
# eventDispatcher
EventDispatcher 类 事件监听器
点击查看源码
/* eslint-disable require-jsdoc */
/**
* @desc: EventDispatcher 类 事件监听器
* @author: kexinwu@tencent.com
*/
/**
* EventDispatcher 类 事件监听器
* @class EventDispatcher
* @classdesc 用于统一维护小程序的事件
*/
export default class EventDispatcher {
constructor() {
this.handlers = [];
}
/**
* @memberof EventDispatcher
* @param {String} type 事件名
* @param {Function} handler 事件处理函数
* @returns {Function} handler 事件处理函数
*/
bind(type, handler) {
if (typeof this.handlers[type] === 'undefined') {
this.handlers[type] = [];
}
this.handlers[type].push(handler);
}
/**
* @memberof EventDispatcher
* @param {String} type 事件名
* @param {Function} handler 事件处理函数
* @returns {Function} handler 事件处理函数
*/
bindOnce(type, handler) {
if (typeof this.handlers[type] === 'undefined') {
this.handlers[type] = [];
}
const isBinded = this.handlers[type].length === 0;
if (isBinded) {
this.handlers[type].push(handler);
}
}
/**
* @memberof EventDispatcher
* @param {String} type 事件名
* @returns {Void} 无返回值
*/
remove(type) {
this.handlers[type] = [];
}
/**
* @memberof EventDispatcher
* @param {String} type 事件名
* @param {Function} handler 事件处理函数
* @returns {Void} 无返回值
*/
unbind(type, handler) {
if (this.handlers[type] instanceof Array) {
const handlers = this.handlers[type];
let i = 0;
const l = handlers.length;
for (; i < l; i += 1) {
if (handlers[i] === handler) {
break;
}
}
handlers.splice(i, 1);
}
}
/**
* @memberof EventDispatcher
* @param {Object} evt 事件描述
* @param {String} evt.type 事件类型
* @returns {Function} handler 事件处理函数
*/
dispatch(evt) {
if (this.handlers[evt.type] instanceof Array) {
const handlers = this.handlers[evt.type];
handlers.forEach(handler => handler(evt));
}
}
}
/**
* @description 获取EventDispatcher的实例
* @returns {Object} [EventDispatcher实例](#class-eventdispatcher)
* @example
* const { tms } = getApp({ allowDefault: true });
* const events = tms.getEventDispatcher();
* events.bind('xxx', () => {})
* events.dispatch({type: 'xxx'})
*/
export const getEventDispatcher = () => new EventDispatcher();
# EventDispatcher
用于统一维护小程序的事件
# function bind
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| type | String | 事件名 | --- | 否 |
| handler | function | 事件处理函数 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
function | handler 事件处理函数 |
# function bindOnce
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| type | String | 事件名 | --- | 否 |
| handler | function | 事件处理函数 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
function | handler 事件处理函数 |
# Void remove
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| type | String | 事件名 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
# Void unbind
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| type | String | 事件名 | --- | 否 |
| handler | function | 事件处理函数 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
# function dispatch
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| evt | Object | 事件描述 | --- | 否 |
| evt.type | String | 事件类型 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
function | handler 事件处理函数 |
# Object getEventDispatcher
获取EventDispatcher的实例
返回值
| 类型 | 说明 |
|---|---|
Object | EventDispatcher实例 |
示例代码
Example
const { tms } = getApp({ allowDefault: true });
const events = tms.getEventDispatcher();
events.bind('xxx', () => {})
events.dispatch({type: 'xxx'})
# funcUtils
点击查看源码
/**
* 函数节流
* 用途:如果func触发频繁,限制一段时间内只能执行一次
* @param func 执行函数
* @param wait 要延迟的时间,单位:毫秒
* @param immediate 首次调用或者超过wait时间间隔后调用时,是否先立即执行func
*/
function throttle(func, wait = 500, immediate = false) {
let context = null;
let args = null;
let timer = 0;
let last = 0;
const run = () => {
func.apply(context, args);
timer = 0;
context = null;
args = null;
};
return function () {
// @ts-ignore
context = this;
// eslint-disable-next-line
args = arguments;
if (timer) {
return;
}
const t = last;
last = Date.now();
if (immediate && last - t >= wait) {
run();
}
else {
timer = setTimeout(run, wait);
}
};
}
/**
* 函数防抖
* 用途:如果func触发频繁,则在一段时间后没有再触发就执行一次
* @param func 执行函数
* @param wait 要延迟的时间,单位:毫秒
* @param immediate 首次调用或者超过wait时间间隔后调用时,是否先立即执行func
*/
function debounce(func, wait = 500, immediate = false) {
let args = null;
let context = null;
let timer = 0;
let triggerTime = 0;
const run = () => {
const t = Date.now() - triggerTime - wait;
if (t < 0) {
timer = setTimeout(run, -t);
}
else {
func.apply(context, args);
timer = 0;
args = null;
context = null;
}
};
return function () {
// @ts-ignore
context = this;
// eslint-disable-next-line
args = arguments;
const t = triggerTime;
triggerTime = Date.now();
if (timer || (immediate && triggerTime - t < wait)) {
return;
}
if (immediate) {
func.apply(context, args);
args = null;
context = null;
}
else {
timer = setTimeout(run, wait);
}
};
}
export { throttle, debounce, };
# throttle
函数节流 用途:如果func触发频繁,限制一段时间内只能执行一次
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| func | 执行函数 | --- | 否 | |
| wait | 要延迟的时间,单位:毫秒 | --- | 否 | |
| immediate | 首次调用或者超过wait时间间隔后调用时,是否先立即执行func | --- | 否 |
# debounce
函数防抖 用途:如果func触发频繁,则在一段时间后没有再触发就执行一次
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| func | 执行函数 | --- | 否 | |
| wait | 要延迟的时间,单位:毫秒 | --- | 否 | |
| immediate | 首次调用或者超过wait时间间隔后调用时,是否先立即执行func | --- | 否 |
# ipxHelper
iPhoneX相关交接接口
点击查看源码
/**
* @desc: iPhoneX相关交接接口
* @author: petegao@tencent.com
* Created: 2019-01-14.
*
*/
import syncfnmanager from './syncfnmanager';
let isInit = false;
let isIPXSign = false;
let ipxClass = '';
/**
* @private
* @returns {Void}
*/
function init() {
if (isInit) {
return;
}
isInit = true;
const sysInfo = syncfnmanager.getSystemInfoSync();
const { model, platform: pl } = sysInfo;
if (pl !== 'ios') {
return;
}
if (model.search('iPhone X') > -1 || model.search('iPhone 11') > -1) {
isIPXSign = true;
ipxClass = 'view__ipx';
}
}
/**
* @description 判断是否是 iPhoneX
* @returns {Boolean}
*/
function isIPX() {
init();
return isIPXSign;
}
/**
* @description 返回 ipxClass
* @returns {String}
*/
function getIpxClass() {
init();
return ipxClass;
}
/**
* @param {Object} config 用户需要合并的IPX的配置
* @returns {Object} 合并后的IPX的配置
*/
function getIpxConfig(config) {
init();
return {
...config,
data: {
...config.data,
isIPX: isIPXSign,
ipxClass,
},
};
}
// 兼容旧接口,已经没什么用了
function ipxInit() {
}
export { ipxInit, isIPX, getIpxClass, getIpxConfig, };
# Boolean isIPX
判断是否是 iPhoneX
返回值
| 类型 | 说明 |
|---|---|
Boolean |
# String getIpxClass
返回 ipxClass
返回值
| 类型 | 说明 |
|---|---|
String |
# Object getIpxConfig
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| config | Object | 用户需要合并的IPX的配置 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Object | 合并后的IPX的配置 |
# location
封装业务侧位置的接口。 将用户经纬度转化为城市等展示信息
点击查看源码
/**
* @module: location
* @desc: 封装业务侧位置的接口。 将用户经纬度转化为城市等展示信息
*/
import Request from '../request';
import EventDispatcher from '../eventDispatcher';
import LocationBase from './base'; // eslint-disable-line
import { formatCityCode, formatPoi } from './utils'; // eslint-disable-line
const event = new EventDispatcher();
const userLocationCache = {}; // 获取位置缓存
// 用户手动选择的位置信息(切换城市后的位置信息)
let userLocation;
// 用户所在的城市
let cityTheUserAt = '';
// 用户城市变化事件名
const CityChangeEventName = 'loc_city_changed';
const LocationType = 'gcj02'; // 获取经纬度时的坐标名称
let ipLocationPromise = null; // ip定位请求的promise
/**
* @class Location
*/
class Location extends LocationBase {
/**
* @desc 默认位置信息
* @property {object} locForNoAuth 默认的位置信息
* @property {string} locForNoAuth.privince 省份信息
* @property {string} locForNoAuth.cityCode 城市编码
* @property {string} locForNoAuth.cityName 城市名称
* @property {string} locForNoAuth.district 地区名称
* @property {string} locForNoAuth.latitude 维度
* @property {string} locForNoAuth.longitude 经度
* @example
* const app = getApp({ allowDefault: true });
* const locationManager = app.tms.getLocationManager(); // 获取位置管理器
* console.log(locationManager.locForNoAuth) // 获取默认位置
*/
locForNoAuth = {
province: '广东省',
cityCode: '440300',
cityName: '深圳市',
district: '福田区',
latitude: 22.54286,
longitude: 114.05956,
};
/**
* 构造函数
*/
constructor() {
super();
}
/**
* @description 根据经纬度获取地理位置信息, 比如城市信息
* @param { number } lat 用户纬度信息
* @param { number } lng 用户经度信息
* @param { number } getPoi 位置 0 - 不获取(不返回poi字段), 1 - 获取(返回poi字段)
* @returns { Promise<PostionType | void> } 获取位置信息的promise
* @example
* const app = getApp({ allowDefault: true });
* const locationManager = app.tms.getLocationManager(); // 获取位置管理器
* await locationManager.getPoiInfo('xxx', 'xxx', 1)
*/
getPoiInfo(lat, lng, getPoi) {
// 优先查询缓存中是否有位置信息,如果有直接返回
const cacheName = `${lat}-${lng}-${getPoi}`;
// @ts-ignore
if (userLocationCache[cacheName]) { // eslint-disable-line
return userLocationCache[cacheName];
}
userLocationCache[cacheName] = Request.getInstance().post('basic/lbs/decode', { lat, lng, getPoi })
.then((res) => {
const { errCode, resData = {} } = res;
if (errCode === 0) {
const { result = {} } = resData;
const { ad_info: adInfo = {} } = result;
const loc = {
nationCode: adInfo.nation_code,
province: adInfo.province,
cityName: adInfo.city,
district: adInfo.district,
adCode: adInfo.adcode,
cityCode: formatCityCode(adInfo.city_code, adInfo.nation_code),
latitude: lat,
longitude: lng,
};
if (getPoi === 0) {
return loc;
}
loc.adCode = adInfo.adcode;
loc.poi = formatPoi(result);
return loc;
}
return Promise.reject(res);
})
.catch(err => Promise.reject(err));
return userLocationCache[cacheName];
}
/**
* @function
* @memberof Location
* @description 获取位置、城市信息
* @param {Boolean} showModalWhenCloseAuth 获取位置时,如果用户关闭了位置授权,是否弹窗提示用户打开授权
* @param {String} type 坐标类型
* @param {String} content 弹窗内容
* @param {Number} getPoi 是否获取详细poi信息, 0 - 不获取(不返回poi字段), 1 - 获取(返回poi字段)
* @returns {Promise<POI|ERR>} 返回对象
* @example
* const app = getApp({ allowDefault: true });
* const locationManager = app.tms.getLocationManager(); // 获取位置管理器
* const res = await locationManager.getLocationDetail(false, 'gcj02', '', 1);
* <caption>res-POI类型示例</caption>
* {
* cityName: '北京市',
* cityCode: '100100',
* province: '北京市',
* latitude: 325.255333,
* longitude: 116.2545454,
* adCode: 1212144,
* poi: {
* id: '1114545554511',
* title: '腾讯北京总部大厦',
* address : '北京市海淀区东北旺西路',
* }
* }
* @example
* <caption>res-ERR类型示例</caption>
* {
* err,
* unAuthed: true,
* locationScopeStatus: false
* }
*/
getLocationDetail(showModalWhenCloseAuth = false, type = LocationType, content = '', getPoi = 0) {
return this.getLocation(showModalWhenCloseAuth, type, content)
.then(async (res) => {
const res2 = await this.getPoiInfo(res.latitude, res.longitude, getPoi);
// @ts-ignore
res2.accuracy = res.accuracy;
return res2;
})
.catch(err => Promise.reject(err));
}
/**
* @description 以静默方式获取用户详细位置(经纬度,poi信息, 省市信息),说明如下:<br>
* 1、从未授权情况下 | 用户删除小程序之后调用该方法不会弹微信自带的授权弹窗;<br>
* 2、拒绝授权后,调用该方法不会弹窗提示用户去授权(wx.showModal);<br>
* @memberof Location
* @param {Number} getPoi 是否获取详细poi信息, 0 - 不获取(不返回poi字段), 1 - 获取(返回poi字段)
* @param {String} type 非必填。坐标类型,默认'gcj02'
* @returns {Promise<LOC|ERR>} 返回对象
* @example
* <caption>LOC类型示例</caption>
* {
* cityName: '北京市',
* cityCode: '100100',
* latitude: 325.255333,
* longitude: 116.2545454,
* adCode: 1212144,
* poi: {
* id: '1114545554511',
* title: '腾讯北京总部大厦',
* address : '北京市海淀区东北旺西路',
* }
* }
* @example
* <caption>ERR类型示例</caption>
* {
* err,
* unAuthed: true,
* locationScopeStatus: false
* }
*/
getLocationDetailSilent(getPoi = 0, type = 'gcj02') {
return this.getLocationSilent(type)
.then(async (res) => {
const res2 = await this.getPoiInfo(res.latitude, res.longitude, getPoi);
// @ts-ignore
res2.accuracy = res.accuracy;
return res2;
})
.catch(err => Promise.reject(err));
}
/**
* @description 获取用户手动选择的位置信息
* @memberof Location
* @returns {Null|LOC} 返回用户位置信息
* @example
* <caption>LOC类型示例</caption>
* {
* province: '北京市',
* cityName: '北京市',
* cityCode: '100100',
* latitude: 325.255333,
* longitude: 116.2545454,
* adCode: 1212144,
* }
*/
getUserLocation() {
return userLocation;
}
/**
* @description 设置用户位置(小程序生命周期内有效)
* @memberof Location
* @param {Object} loc 用户手动选择的位置
* @params {String} loc.province 省份
* @params {String} loc.cityName 城市
* @params {String} loc.cityCode citycode
* @params {Number} loc.latitude 纬度
* @params {Number} loc.longitude 经度
* @params {Number} loc.adCode adCode
* @returns {Void} 无返回值
* @example
* <caption>loc类型示例</caption>
* {
* province: '北京市',
* cityName: '北京市',
* cityCode: '100100',
* latitude: 325.255333,
* longitude: 116.2545454,
* adCode: 1212144,
* }
*/
setUserLocation(loc) {
if (!loc || typeof loc !== 'object') {
return;
}
// 如果城市发生变化,派发事件
const { cityName: curCityName } = loc;
if (curCityName && (cityTheUserAt !== curCityName)) {
cityTheUserAt = curCityName;
userLocation = loc;
event.dispatch({ type: CityChangeEventName });
}
}
/**
* @description 先获取用户手动设置的位置信息,获取不到则定位授权获取用户位置信息
* @memberof Location
* @param {Boolean} showModalWhenCloseAuth 没有授权时是否展示授权弹窗
* @returns {Promise} 用户位置信息
*/
async getMergedLocation(showModalWhenCloseAuth = true) {
const userLocatioin = this.getUserLocation();
// 优先用户选择的城市
if (userLocatioin) {
return userLocatioin;
}
return await this.getLocationDetail(showModalWhenCloseAuth, LocationType);
}
/**
* @memberof Location
* @description 监听用户城市变化(如: 北京市->深圳市)
* @param {Function} cb 监听事件回调函数
* @returns {void}
*/
onCityChange(cb) {
if (cb && typeof cb === 'function') {
event.bind(CityChangeEventName, cb);
}
}
/**
* @memberof Location
* @description 获取路线规划
* @param {String} to 目的地坐标,格式:lat,lng
* @param {String} depart 出发地坐标,格式:lat,lng
* @param {String} mode 出行方式, driving 驾车[默认]
* @returns {Object} result.routes 返回数据 [{}, ...]
*/
async getRoute(to, depart, mode = 'driving') {
let from = depart;
const coorReg = new RegExp(/(\d+\.\d{6,}),(\d+\.\d{6,})/);
if (!coorReg.test(to))
throw Error('目的地参数格式错误');
if (!coorReg.test(from)) { // 出发地缺省使用当前位置
const { latitude, longitude } = await this.getMergedLocation();
from = `${latitude},${longitude}`;
}
return Request.getInstance().post('basic/lbs/direction', { from, to, mode });
}
/**
* ip定位
* 原理:通过调手图接口查询当前IP所在位置,市级的准确率是91%
* 注意:由于服务端对该查询服务有次数限制,所以本函数会缓存成功的promise
* @param force 是否清除上次的缓存重新请求
*/
getIpLocation(force = false) {
if (ipLocationPromise === null || force) {
ipLocationPromise = new Promise((resolve, reject) => {
Request.getInstance().post('basic/lbs/decodeip')
.then((res) => {
if (res.errCode === 0) {
resolve(res.resData);
return;
}
reject({ erMsg: res.errMsg });
ipLocationPromise = null;
})
.catch((e) => {
reject(e);
ipLocationPromise = null;
});
});
}
return ipLocationPromise;
}
}
// 因为在构造函数中会用到wx的api,所以使用到时才实例化
let instance;
function getLocInstance() {
if (!instance) {
instance = new Location();
}
return instance;
}
export default getLocInstance;
# Location
# locForNoAuth
默认位置信息
属性
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| locForNoAuth | object | 默认的位置信息 | --- | 否 |
| locForNoAuth.privince | string | 省份信息 | --- | 否 |
| locForNoAuth.cityCode | string | 城市编码 | --- | 否 |
| locForNoAuth.cityName | string | 城市名称 | --- | 否 |
| locForNoAuth.district | string | 地区名称 | --- | 否 |
| locForNoAuth.latitude | string | 维度 | --- | 否 |
| locForNoAuth.longitude | string | 经度 | --- | 否 |
示例代码
Example
const app = getApp({ allowDefault: true });
const locationManager = app.tms.getLocationManager(); // 获取位置管理器
console.log(locationManager.locForNoAuth) // 获取默认位置
# Promise.<(PostionType|void)> getPoiInfo
根据经纬度获取地理位置信息, 比如城市信息
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| lat | number | 用户纬度信息 | --- | 否 |
| lng | number | 用户经度信息 | --- | 否 |
| getPoi | number | 位置 0 - 不获取(不返回poi字段), 1 - 获取(返回poi字段) | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Promise.<(PostionType|void)> | 获取位置信息的promise |
示例代码
Example
const app = getApp({ allowDefault: true });
const locationManager = app.tms.getLocationManager(); // 获取位置管理器
await locationManager.getPoiInfo('xxx', 'xxx', 1)
# Promise.<(POI|ERR)> getLocationDetail
获取位置、城市信息
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| showModalWhenCloseAuth | Boolean | 获取位置时,如果用户关闭了位置授权,是否弹窗提示用户打开授权 | --- | 否 |
| type | String | 坐标类型 | --- | 否 |
| content | String | 弹窗内容 | --- | 否 |
| getPoi | Number | 是否获取详细poi信息, 0 - 不获取(不返回poi字段), 1 - 获取(返回poi字段) | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Promise.<(POI|ERR)> | 返回对象 |
示例代码
Example (res-POI类型示例)
const app = getApp({ allowDefault: true });
const locationManager = app.tms.getLocationManager(); // 获取位置管理器
const res = await locationManager.getLocationDetail(false, 'gcj02', '', 1);
{
cityName: '北京市',
cityCode: '100100',
province: '北京市',
latitude: 325.255333,
longitude: 116.2545454,
adCode: 1212144,
poi: {
id: '1114545554511',
title: '腾讯北京总部大厦',
address : '北京市海淀区东北旺西路',
}
}
Example (res-ERR类型示例)
{
err,
unAuthed: true,
locationScopeStatus: false
}
# Promise.<(LOC|ERR)> getLocationDetailSilent
以静默方式获取用户详细位置(经纬度,poi信息, 省市信息),说明如下:<br> 1、从未授权情况下 | 用户删除小程序之后调用该方法不会弹微信自带的授权弹窗;<br> 2、拒绝授权后,调用该方法不会弹窗提示用户去授权(wx.showModal);<br>
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| getPoi | Number | 是否获取详细poi信息, 0 - 不获取(不返回poi字段), 1 - 获取(返回poi字段) | --- | 否 |
| type | String | 非必填。坐标类型,默认'gcj02' | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Promise.<(LOC|ERR)> | 返回对象 |
示例代码
Example (LOC类型示例)
{
cityName: '北京市',
cityCode: '100100',
latitude: 325.255333,
longitude: 116.2545454,
adCode: 1212144,
poi: {
id: '1114545554511',
title: '腾讯北京总部大厦',
address : '北京市海淀区东北旺西路',
}
}
Example (ERR类型示例)
{
err,
unAuthed: true,
locationScopeStatus: false
}
# NullLOC getUserLocation
获取用户手动选择的位置信息
返回值
| 类型 | 说明 |
|---|---|
Null|LOC | 返回用户位置信息 |
示例代码
Example (LOC类型示例)
{
province: '北京市',
cityName: '北京市',
cityCode: '100100',
latitude: 325.255333,
longitude: 116.2545454,
adCode: 1212144,
}
# Void setUserLocation
设置用户位置(小程序生命周期内有效)
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| loc | Object | 用户手动选择的位置 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
示例代码
Example (loc类型示例)
{
province: '北京市',
cityName: '北京市',
cityCode: '100100',
latitude: 325.255333,
longitude: 116.2545454,
adCode: 1212144,
}
# Promise getMergedLocation
先获取用户手动设置的位置信息,获取不到则定位授权获取用户位置信息
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| showModalWhenCloseAuth | Boolean | 没有授权时是否展示授权弹窗 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Promise | 用户位置信息 |
# void onCityChange
监听用户城市变化(如: 北京市->深圳市)
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| cb | function | 监听事件回调函数 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
void |
# Object getRoute
获取路线规划
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| to | String | 目的地坐标,格式:lat,lng | --- | 否 |
| depart | String | 出发地坐标,格式:lat,lng | --- | 否 |
| mode | String | 出行方式, driving 驾车[默认] | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Object | result.routes 返回数据 [{}, ...] |
# getIpLocation
ip定位 原理:通过调手图接口查询当前IP所在位置,市级的准确率是91% 注意:由于服务端对该查询服务有次数限制,所以本函数会缓存成功的promise
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| force | 是否清除上次的缓存重新请求 | --- | 否 |
# log
本文件主要负责在小程序中日志打印功能,包含本地日志及实时日志.
点击查看源码
/**
* @desc: 本文件主要负责在小程序中日志打印功能,包含本地日志及实时日志.
* 主要做了两件事:
* 1、参数序列化处理;支持传递任意多个参数,并对类型为对象的参数进行字符串序列化处理(避免打印出来是'[Object Object]'的格式);
* 2、低版本兼容;
*/
// 低版本不支持getLogManager或者getRealtimeLogManager时,用ManagerForLowerVersionLib来兼容
const ManagerForLowerVersionLib = {
debug: () => { },
info: () => { },
log: () => { },
warn: () => { },
error: () => { },
addFilterMsg: () => { },
setFilterMsg: () => { },
};
// 小程序基础库2.7.1版本以上支持,所以需要兼容性处理
let logInstance = null;
let rtLogInstance = null;
/**
* @private
*/
function getLogInstance() {
if (logInstance === null) {
logInstance = wx.getLogManager ? wx.getLogManager() : ManagerForLowerVersionLib;
}
return logInstance;
}
/**
* @private
*/
function getRTLogInstance() {
if (rtLogInstance === null) {
rtLogInstance = wx.getRealtimeLogManager ? wx.getRealtimeLogManager() : ManagerForLowerVersionLib;
}
return rtLogInstance;
}
/**
* @private
* 参数中有对象类型的,将其转换为字符串类型,以便查看
* @param {Array<Any>} params 需要格式化的数据
* @returns {Array<String>} 字符串序列化后的数据
*/
const format = params => params.map(param => (typeof param === 'string' ? param : JSON.stringify(param)));
/**
* @namespace LOG
* @description 普通日志管理器,将日志记录在小程序日志文件中,用户上传后,可以在小程序后台-反馈管理中看到
*/
const LOG = {
/**
* @description 写debug日志
* @param {...Any} params 需要打印的数据,支持任意多个
* @returns {Void} 无返回值
*/
debug(...params) {
getLogInstance().debug(...format(params));
},
/**
* @description 写info日志
* @param {...Any} params 需要打印的数据,支持任意多个
* @returns {Void} 无返回值
*/
info(...params) {
getLogInstance().info(...format(params));
},
/**
* @description 写log日志
* @param {...Any} params 需要打印的数据,支持任意多个
* @returns {Void} 无返回值
*/
log(...params) {
getLogInstance().log(...format(params));
},
/**
* @description 写warn日志
* @param {...Any} params 需要打印的数据,支持任意多个
* @returns {Void} 无返回值
*/
warn(...params) {
getLogInstance().warn(...format(params));
},
/**
* @description 写warn日志. LogManager并没有error方法,为了兼容旧代码,所以声明一个error方法
* @param {...Any} params 需要打印的数据,支持任意多个
* @returns {Void} 无返回值
*/
error(...params) {
LOG.warn(...params);
},
};
/**
* @namespace RTLOG
* @description 实时日志,将日志实时上传至小程序后台-开发-运维中心-实时日志,方便快速排查漏洞,定位问题
*/
const RTLOG = {
/**
* @description 写info日志
* @param {...Any} params 需要打印的数据,支持任意多个
* @returns {Void} 无返回值
*/
info(...params) {
getRTLogInstance().info(...format(params));
},
/**
* @description 写warn日志
* @param {...Any} params 需要打印的数据,支持任意多个
* @returns {Void} 无返回值
*/
warn(...params) {
getRTLogInstance().warn(...format(params));
},
/**
* @description 写error日志
* @param {...Any} params 需要打印的数据,支持任意多个
* @returns {Void} 无返回值
*/
error(...params) {
getRTLogInstance().error(...format(params));
},
/**
* @description 添加过滤关键字
* @param {String} msg 关键字
* @returns {Void} 无返回值
*/
addFilterMsg(msg) {
getRTLogInstance().addFilterMsg(msg);
},
/**
* @description 设置过滤关键字
* @param {String} msg 关键字
* @returns {Void} 无返回值
*/
setFilterMsg(msg) {
getRTLogInstance().setFilterMsg(msg);
},
};
/**
* @description 获取日志管理器对象,该对象提供的方法同wx.getLogManager()提供的方法,详见微信文档
* @returns {Object} [LOG](#namespace-log)
* @example
* const { tms } = getApp({ allowDefault: true });
* const logger = tms.getLogManager();
* logger.log(1, 'str', { a: 1 }, ...);
* logger.info(1, 'str', { a: 1 }, ...);
* logger.debug(1, 'str', { a: 1 }, ...);
* logger.awrn(1, 'str', { a: 1 }, ...);
*/
const getLogManager = () => LOG;
/**
* @description 获取实时日志管理器对象,该对象提供的方法同wx.getRealtimeLogManager()提供的方法,详见微信文档
* @returns {Object} [RTLOG](#namespace-rtlog)
* @example
* const { tms } = getApp({ allowDefault: true });
* const logger = tms.getRealtimeLogManager();
* logger.info(1, 'str', { a: 1 }, ...);
* logger.warn(1, 'str', { a: 1 }, ...);
* logger.error(1, 'str', { a: 1 }, ...);
*/
const getRealtimeLogManager = () => RTLOG;
export { getLogManager, getRealtimeLogManager, };
# namespace LOG
普通日志管理器,将日志记录在小程序日志文件中,用户上传后,可以在小程序后台-反馈管理中看到
# Void debug
写debug日志
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| params | Any | 需要打印的数据,支持任意多个 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
# Void info
写info日志
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| params | Any | 需要打印的数据,支持任意多个 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
# Void log
写log日志
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| params | Any | 需要打印的数据,支持任意多个 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
# Void warn
写warn日志
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| params | Any | 需要打印的数据,支持任意多个 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
# Void error
写warn日志. LogManager并没有error方法,为了兼容旧代码,所以声明一个error方法
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| params | Any | 需要打印的数据,支持任意多个 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
# namespace RTLOG
实时日志,将日志实时上传至小程序后台-开发-运维中心-实时日志,方便快速排查漏洞,定位问题
# Void info
写info日志
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| params | Any | 需要打印的数据,支持任意多个 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
# Void warn
写warn日志
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| params | Any | 需要打印的数据,支持任意多个 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
# Void error
写error日志
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| params | Any | 需要打印的数据,支持任意多个 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
# Void addFilterMsg
添加过滤关键字
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| msg | String | 关键字 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
# Void setFilterMsg
设置过滤关键字
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| msg | String | 关键字 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Void | 无返回值 |
# Object getLogManager
获取日志管理器对象,该对象提供的方法同wx.getLogManager()提供的方法,详见微信文档
返回值
| 类型 | 说明 |
|---|---|
Object | LOG |
示例代码
Example
const { tms } = getApp({ allowDefault: true });
const logger = tms.getLogManager();
logger.log(1, 'str', { a: 1 }, ...);
logger.info(1, 'str', { a: 1 }, ...);
logger.debug(1, 'str', { a: 1 }, ...);
logger.awrn(1, 'str', { a: 1 }, ...);
# Object getRealtimeLogManager
获取实时日志管理器对象,该对象提供的方法同wx.getRealtimeLogManager()提供的方法,详见微信文档
返回值
| 类型 | 说明 |
|---|---|
Object | RTLOG |
示例代码
Example
const { tms } = getApp({ allowDefault: true });
const logger = tms.getRealtimeLogManager();
logger.info(1, 'str', { a: 1 }, ...);
logger.warn(1, 'str', { a: 1 }, ...);
logger.error(1, 'str', { a: 1 }, ...);
# md5
点击查看源码
/* eslint-disable no-param-reassign */
/**
* @public
* @description 基于md5算法对源字符串进行hash,生成hash字符串
* @param {String} str 源字符串
* @returns {String} 源字符串的md5 hash值
* MIT https://github.com/blueimp/JavaScript-MD5
*/
/**
* Add integers, wrapping at 2^32.
* This uses 16-bit operations internally to work around bugs in interpreters.
*
* @param {number} x First integer
* @param {number} y Second integer
* @returns {number} Sum
*/
function safeAdd(x, y) {
const lsw = (x & 0xffff) + (y & 0xffff);
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xffff);
}
/**
* Bitwise rotate a 32-bit number to the left.
*
* @param {number} num 32-bit number
* @param {number} cnt Rotation count
* @returns {number} Rotated number
*/
function bitRotateLeft(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
}
/**
* Basic operation the algorithm uses.
*
* @param {number} q q
* @param {number} a a
* @param {number} b b
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5cmn(q, a, b, x, s, t) {
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5ff(a, b, c, d, x, s, t) {
return md5cmn((b & c) | (~b & d), a, b, x, s, t);
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5gg(a, b, c, d, x, s, t) {
return md5cmn((b & d) | (c & ~d), a, b, x, s, t);
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5hh(a, b, c, d, x, s, t) {
return md5cmn(b ^ c ^ d, a, b, x, s, t);
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5ii(a, b, c, d, x, s, t) {
return md5cmn(c ^ (b | ~d), a, b, x, s, t);
}
/**
* Calculate the MD5 of an array of little-endian words, and a bit length.
*
* @param {Array} x Array of little-endian words
* @param {number} len Bit length
* @returns {Array<number>} MD5 Array
*/
function binlMD5(x, len) {
/* append padding */
x[len >> 5] |= 0x80 << len % 32;
x[(((len + 64) >>> 9) << 4) + 14] = len;
let i;
let olda;
let oldb;
let oldc;
let oldd;
let a = 1732584193;
let b = -271733879;
let c = -1732584194;
let d = 271733878;
for (i = 0; i < x.length; i += 16) {
olda = a;
oldb = b;
oldc = c;
oldd = d;
a = md5ff(a, b, c, d, x[i], 7, -680876936);
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
b = md5gg(b, c, d, a, x[i], 20, -373897302);
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
d = md5hh(d, a, b, c, x[i], 11, -358537222);
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
a = md5ii(a, b, c, d, x[i], 6, -198630844);
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
a = safeAdd(a, olda);
b = safeAdd(b, oldb);
c = safeAdd(c, oldc);
d = safeAdd(d, oldd);
}
return [a, b, c, d];
}
/**
* Convert an array of little-endian words to a string
*
* @param {Array<number>} input MD5 Array
* @returns {string} MD5 string
*/
function binl2rstr(input) {
let i;
let output = '';
const length32 = input.length * 32;
for (i = 0; i < length32; i += 8) {
output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff);
}
return output;
}
/**
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*
* @param {string} input Raw input string
* @returns {Array<number>} Array of little-endian words
*/
function rstr2binl(input) {
let i;
const output = [];
output[(input.length >> 2) - 1] = undefined;
for (i = 0; i < output.length; i += 1) {
output[i] = 0;
}
const length8 = input.length * 8;
for (i = 0; i < length8; i += 8) {
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32;
}
return output;
}
/**
* Calculate the MD5 of a raw string
*
* @param {string} s Input string
* @returns {string} Raw MD5 string
*/
function rstrMD5(s) {
return binl2rstr(binlMD5(rstr2binl(s), s.length * 8));
}
/**
* Calculates the HMAC-MD5 of a key and some data (raw strings)
*
* @param {string} key HMAC key
* @param {string} data Raw input string
* @returns {string} Raw MD5 string
*/
function rstrHMACMD5(key, data) {
let i;
let bkey = rstr2binl(key);
const ipad = [];
const opad = [];
ipad[15] = undefined;
opad[15] = undefined;
if (bkey.length > 16) {
bkey = binlMD5(bkey, key.length * 8);
}
for (i = 0; i < 16; i += 1) {
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5c5c5c5c;
}
const hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
return binl2rstr(binlMD5(opad.concat(hash), 512 + 128));
}
/**
* Convert a raw string to a hex string
*
* @param {string} input Raw input string
* @returns {string} Hex encoded string
*/
function rstr2hex(input) {
const hexTab = '0123456789abcdef';
let output = '';
let x;
let i;
for (i = 0; i < input.length; i += 1) {
x = input.charCodeAt(i);
output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f);
}
return output;
}
/**
* Encode a string as UTF-8
*
* @param {string} input Input string
* @returns {string} UTF8 string
*/
function str2rstrUTF8(input) {
return unescape(encodeURIComponent(input));
}
/**
* Encodes input string as raw MD5 string
*
* @param {string} s Input string
* @returns {string} Raw MD5 string
*/
function rawMD5(s) {
return rstrMD5(str2rstrUTF8(s));
}
/**
* Encodes input string as Hex encoded string
*
* @param {string} s Input string
* @returns {string} Hex encoded string
*/
function hexMD5(s) {
return rstr2hex(rawMD5(s));
}
/**
* Calculates the raw HMAC-MD5 for the given key and data
*
* @param {string} k HMAC key
* @param {string} d Input string
* @returns {string} Raw MD5 string
*/
function rawHMACMD5(k, d) {
return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d));
}
/**
* Calculates the Hex encoded HMAC-MD5 for the given key and data
*
* @param {string} k HMAC key
* @param {string} d Input string
* @returns {string} Raw MD5 string
*/
function hexHMACMD5(k, d) {
return rstr2hex(rawHMACMD5(k, d));
}
/**
* Calculates MD5 value for a given string.
* If a key is provided, calculates the HMAC-MD5 value.
* Returns a Hex encoded string unless the raw argument is given.
*
* @param {string} string Input string
* @param {string} [key] HMAC key
* @param {boolean} [raw] Raw output switch
* @returns {string} MD5 output
*/
function md5(string, key, raw) {
if (!key) {
if (!raw) {
return hexMD5(string);
}
return rawMD5(string);
}
if (!raw) {
return hexHMACMD5(key, string);
}
return rawHMACMD5(key, string);
}
export default md5;
# number safeAdd
Add integers, wrapping at 2^32. This uses 16-bit operations internally to work around bugs in interpreters.
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| x | number | First integer | --- | 否 |
| y | number | Second integer | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
number | Sum |
# number bitRotateLeft
Bitwise rotate a 32-bit number to the left.
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| num | number | 32-bit number | --- | 否 |
| cnt | number | Rotation count | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
number | Rotated number |
# number md5cmn
Basic operation the algorithm uses.
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| q | number | q | --- | 否 |
| a | number | a | --- | 否 |
| b | number | b | --- | 否 |
| x | number | x | --- | 否 |
| s | number | s | --- | 否 |
| t | number | t | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
number | Result |
# number md5ff
Basic operation the algorithm uses.
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| a | number | a | --- | 否 |
| b | number | b | --- | 否 |
| c | number | c | --- | 否 |
| d | number | d | --- | 否 |
| x | number | x | --- | 否 |
| s | number | s | --- | 否 |
| t | number | t | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
number | Result |
# number md5gg
Basic operation the algorithm uses.
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| a | number | a | --- | 否 |
| b | number | b | --- | 否 |
| c | number | c | --- | 否 |
| d | number | d | --- | 否 |
| x | number | x | --- | 否 |
| s | number | s | --- | 否 |
| t | number | t | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
number | Result |
# number md5hh
Basic operation the algorithm uses.
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| a | number | a | --- | 否 |
| b | number | b | --- | 否 |
| c | number | c | --- | 否 |
| d | number | d | --- | 否 |
| x | number | x | --- | 否 |
| s | number | s | --- | 否 |
| t | number | t | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
number | Result |
# number md5ii
Basic operation the algorithm uses.
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| a | number | a | --- | 否 |
| b | number | b | --- | 否 |
| c | number | c | --- | 否 |
| d | number | d | --- | 否 |
| x | number | x | --- | 否 |
| s | number | s | --- | 否 |
| t | number | t | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
number | Result |
# Array.<number> binlMD5
Calculate the MD5 of an array of little-endian words, and a bit length.
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| x | Array | Array of little-endian words | --- | 否 |
| len | number | Bit length | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Array.<number> | MD5 Array |
# string binl2rstr
Convert an array of little-endian words to a string
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| input | Array.<number> | MD5 Array | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
string | MD5 string |
# Array.<number> rstr2binl
Convert a raw string to an array of little-endian words Characters >255 have their high-byte silently ignored.
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| input | string | Raw input string | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Array.<number> | Array of little-endian words |
# string rstrMD5
Calculate the MD5 of a raw string
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| s | string | Input string | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
string | Raw MD5 string |
# string rstrHMACMD5
Calculates the HMAC-MD5 of a key and some data (raw strings)
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| key | string | HMAC key | --- | 否 |
| data | string | Raw input string | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
string | Raw MD5 string |
# string rstr2hex
Convert a raw string to a hex string
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| input | string | Raw input string | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
string | Hex encoded string |
# string str2rstrUTF8
Encode a string as UTF-8
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| input | string | Input string | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
string | UTF8 string |
# string rawMD5
Encodes input string as raw MD5 string
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| s | string | Input string | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
string | Raw MD5 string |
# string hexMD5
Encodes input string as Hex encoded string
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| s | string | Input string | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
string | Hex encoded string |
# string rawHMACMD5
Calculates the raw HMAC-MD5 for the given key and data
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| k | string | HMAC key | --- | 否 |
| d | string | Input string | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
string | Raw MD5 string |
# string hexHMACMD5
Calculates the Hex encoded HMAC-MD5 for the given key and data
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| k | string | HMAC key | --- | 否 |
| d | string | Input string | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
string | Raw MD5 string |
# string md5
Calculates MD5 value for a given string. If a key is provided, calculates the HMAC-MD5 value. Returns a Hex encoded string unless the raw argument is given.
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| string | string | Input string | --- | 否 |
| key | string | HMAC key | --- | 是 |
| raw | boolean | Raw output switch | --- | 是 |
返回值
| 类型 | 说明 |
|---|---|
string | MD5 output |
# mpInfo
支持服务接入相关接口
点击查看源码
/**
* @desc: 支持服务接入相关接口
*/
import Request from './request';
/**
* getMpOpenId 获取接入方用户唯一标识 [变更为 getOuterOpenId]
* @private
* @description 唯一标识 openId 用于与服务商建立账号关联关系
* @category 服务接入
* @param {String} mpId 接入服务商渠道标识
* @param {String} userId 出行用户标识
* @returns {Promise<String>} 返回 openId,失败时返回空
* @example
* const { tms } = getApp({ allowDefault: true });
* tms.getMpOpenId('xxx', '111')
*/
async function getMpOpenId(mpId, userId) {
const { resData } = await Request.getInstance().post('user/mpinfo', { userId, mpId });
const { openId = '' } = resData || {};
return openId;
}
/**
* getOuterOpenId 获取接入方用户唯一标识
* @public
* @description 唯一标识 openId 用于服务接入方作为唯一标识、向腾讯出行服务同步订单等
* @category 服务接入
* @param {String} apiKey 服务接入方渠道标识
* @returns {Promise<String>} 返回 openId,失败时返回空
* @example
* const { tms } = getApp({ allowDefault: true });
* tms.getMpOpenId('xxx', '111')
*/
async function getOuterOpenId(apiKey) {
const { resData } = await Request.getInstance().post('user/mpinfo', { mpId: apiKey });
const { openId = '' } = resData || {};
return openId;
}
/**
* 获取用户加密数据
* @param {String} mpId 服务接入方渠道标识,spId 或 apiKey。注意:如果使用 apiKey,需要在后台增加 apiKey 和 spId 的映射
* @param {String} queryTypes 要加密的用户信息类型集合,多个种类型用','连接;支持的类型见下文“用户信息类型标识” |
* @param {Object} propNameMap 加密数据对象中信息字段名映射
* @returns {Promise<{ encrStr: String }>} 加密字符串
*/
async function getEncryptUserInfo(mpId = '', queryTypes = '', propNameMap = {}) {
if (!mpId)
return Promise.reject('缺少服务标志mpId');
if (!queryTypes)
return Promise.reject('缺少需要加密的用户信息字段描述');
return new Request().get('user/info/encrypt', { mpId, queryTypes, propNameMap })
.then((res) => {
const { resData, errCode, errMsg } = res || {};
if (errCode === 0) {
// 请求成功
return resData;
}
return Promise.reject(errMsg);
})
.catch(() => Promise.reject('请求发生错误,请重试'));
}
export { getMpOpenId, getOuterOpenId, getEncryptUserInfo, };
# Promise.<{encrStr: String}> getEncryptUserInfo
获取用户加密数据
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| mpId | String | 服务接入方渠道标识,spId 或 apiKey。注意:如果使用 apiKey,需要在后台增加 apiKey 和 spId 的映射 | --- | 否 |
| queryTypes | String | 要加密的用户信息类型集合,多个种类型用','连接;支持的类型见下文“用户信息类型标识” | --- | |
| propNameMap | Object | 加密数据对象中信息字段名映射 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Promise.<{encrStr: String}> | 加密字符串 |
# Promise.<String> getOuterOpenId
唯一标识 openId 用于服务接入方作为唯一标识、向腾讯出行服务同步订单等
服务接入
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| apiKey | String | 服务接入方渠道标识 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Promise.<String> | 返回 openId,失败时返回空 |
示例代码
Example
const { tms } = getApp({ allowDefault: true });
tms.getMpOpenId('xxx', '111')
# navbarUtils
获取导航高度的相关函数
点击查看源码
/**
* @desc: 获取导航高度的相关函数
*/
import { compareVersion } from './compareVersion';
let systemInfo = null;
/**
* 获取系统信息,有缓存, 返回数据同wx.getSystemInfoSync {@link https://developers.weixin.qq.com/miniprogram/dev/api/base/system/wx.getSystemInfoSync.html}
* @returns {Object} 系统信息
*/
const getSystemInfoSync = () => {
if (systemInfo)
return systemInfo;
let res;
try {
systemInfo = wx.getSystemInfoSync();
res = systemInfo;
}
catch (_) {
res = {};
}
return res;
};
/**
* 获取系统信息,有缓存, 返回数据同wx.getSystemInfoSync {@link https://developers.weixin.qq.com/miniprogram/dev/api/base/system/wx.getSystemInfoSync.html}
* @returns {Object} 系统信息
*/
const getSysInfo = () => {
let sysInfo = {};
try {
sysInfo = getSystemInfoSync();
sysInfo.getSystemInfoSuccess = true;
}
catch (e) {
// 获取系统信息失败时,使用默认数据
sysInfo = { getSystemInfoSuccess: false, statusBarHeight: 0, windowWidth: 0 };
}
return sysInfo;
};
/**
* 获取胶囊位置信息,返回数据同wx.getMenuButtonBoundingClientRect {@link https://developers.weixin.qq.com/miniprogram/dev/api/ui/menu/wx.getMenuButtonBoundingClientRect.html}
* @returns {Object} 胶囊位置信息
*/
const getMenuButtonRectInfo = () => {
let menuButtonRectInfo = {};
try {
menuButtonRectInfo = wx.getMenuButtonBoundingClientRect();
menuButtonRectInfo.getMenuRectInfoSuccess = true;
}
catch (e) {
// 获取胶囊位置信息失败,使用默认值
menuButtonRectInfo = { getMenuRectInfoSuccess: false };
}
return menuButtonRectInfo;
};
/**
* 胶囊高度适配,以兼容获取到的胶囊高度值非法的情况
* @private
* @param {Number} height 胶囊高度
* @param {Boolean} isIOS 是否是ios系统
* @returns {Number} 胶囊高度
*/
const formatMenuHeight = (height, isIOS) => {
if (height > 0) {
return height;
}
return isIOS ? 32 : 34;
};
/**
* 计算自定义导航栏布局信息
* @private
* @param {Boolean} isIOS 是否是ios平台
* @param {Number} statusBarHeight 状态栏高度
* @param {String} apiCategory API类别
* @returns {Object} data 导航栏布局信息
* @returns {Number} data.statusBarHeight 状态栏高度,单位rpx
* @returns {Number} data.navBarHeight 导航栏高度(不含状态栏),单位rpx
* @returns {Number} data.menuTop 菜单按钮上边距,单位rpx
* @returns {Number} data.menuRight 菜单按钮右边缘距离屏幕左边缘的距离,单位rpx
* @returns {Number} data.menuWidth 菜单按钮宽度,单位rpx
* @returns {Number} data.menuHeight 菜单按钮高度,单位rpx
*/
const calculateNavBarLayout = (isIOS, statusBarHeight, apiCategory) => {
const data = {};
const menuTopOnIos = statusBarHeight + 4;
const menuTopOnAndroid = statusBarHeight + 8; // statusBarHeight高度,不同机型差距较大,不适合写死
const menuRectInfo = getMenuButtonRectInfo(); // 插件下基础库2.15.0起支持
let { right, width, height = 0, top } = menuRectInfo;
// 数据有异常时使用默认值
if (top <= 0) {
top = isIOS ? menuTopOnIos : menuTopOnAndroid;
}
if (right <= 0) {
right = isIOS ? 368 : 383;
}
if (width < 0) {
width = isIOS ? 87 : 94;
}
height = formatMenuHeight(height, isIOS);
data.statusBarHeight = calStatusBarHeight(statusBarHeight, apiCategory); // 顶部状态栏高度
data.navBarHeight = calNavBarHeight(top, height, statusBarHeight, apiCategory); // 小程序导航栏高度
data.menuTop = top; // 菜单按钮上边距
data.menuRight = right; // 菜单按钮右边距离屏幕左边缘的距离
data.menuWidth = width; // 菜单按钮宽度
data.menuHeight = height; // 菜单按钮高度
return data;
};
const calStatusBarHeight = (statusBarHeight, apiCategory) => {
if (apiCategory !== 'embedded')
return statusBarHeight;
return 0;
};
const calNavBarHeight = (menuTop, menuHeight, statusBarHeight, apiCategory) => {
if (apiCategory !== 'embedded') {
return menuHeight + ((menuTop - statusBarHeight) * 2);
}
return menuHeight + (7 * 2);
};
/**
* 获取导航栏相关信息
* @returns {Object} data 导航栏布局信息
* @returns {Number} data.enable 是否支持自定义导航栏
* @returns {Number} data.statusBarHeight 状态栏高度,单位rpx
* @returns {Number} data.navBarHeight 导航栏高度(不含状态栏),单位rpx
* @returns {Number} data.menuTop 菜单按钮上边距,单位rpx
* @returns {Number} data.menuRight 菜单按钮右边缘距离屏幕左边缘的距离,单位rpx
* @returns {Number} data.menuWidth 菜单按钮宽度,单位rpx
* @returns {Number} data.menuHeight 菜单按钮高度,单位rpx
* @returns {Object} data.enterOptions 启动参数
* @returns {Object} data.enterOptions.apiCategory API 类别(不同apiCategory场景下的API有不同限制,UI也有不同)
*/
const getNavBarConfigData = () => {
const systemInfo = getSysInfo();
const { brand, getSystemInfoSuccess, platform } = systemInfo;
const isIOS = platform === 'ios';
const StatusBarHeightOnIOS = 44;
const StatusBarHeightOnAndroid = 24;
let { statusBarHeight, version } = systemInfo;
// 获取系统信息失败,给设置一个默认版本
if (!getSystemInfoSuccess || !version) {
version = '7.0.1';
}
// 获取状态栏高度失败 | 接口调用成功,但数据为0时,使用默认值
if (!getSystemInfoSuccess || (getSystemInfoSuccess && statusBarHeight <= 0)) {
statusBarHeight = isIOS ? StatusBarHeightOnIOS : StatusBarHeightOnAndroid;
}
const enable = compareVersion(version, '7.0.0') >= 0 || brand === 'devtools'; // 微信版本是否支持自定义顶栏,不支持时自动隐藏
const enterOptions = getEnterOptions();
if (enable) {
return { enable, ...calculateNavBarLayout(isIOS, statusBarHeight, enterOptions.apiCategory) };
}
return { enable };
};
// 获取需要对外暴露的启动参数
const getEnterOptions = () => {
const options = wx.getEnterOptionsSync();
const { apiCategory = 'default' } = options || {};
return { apiCategory };
};
export { getNavBarConfigData, };
# Object getSystemInfoSync
获取系统信息,有缓存, 返回数据同wx.getSystemInfoSync link (opens new window)
返回值
| 类型 | 说明 |
|---|---|
Object | 系统信息 |
# Object getSysInfo
获取系统信息,有缓存, 返回数据同wx.getSystemInfoSync link (opens new window)
返回值
| 类型 | 说明 |
|---|---|
Object | 系统信息 |
# Object getMenuButtonRectInfo
获取胶囊位置信息,返回数据同wx.getMenuButtonBoundingClientRect link (opens new window)
返回值
| 类型 | 说明 |
|---|---|
Object | 胶囊位置信息 |
# Object getNavBarConfigData
获取导航栏相关信息
返回值
| 类型 | 说明 |
|---|---|
Object | data 导航栏布局信息 |
Number | data.enable 是否支持自定义导航栏 |
Number | data.statusBarHeight 状态栏高度,单位rpx |
Number | data.navBarHeight 导航栏高度(不含状态栏),单位rpx |
Number | data.menuTop 菜单按钮上边距,单位rpx |
Number | data.menuRight 菜单按钮右边缘距离屏幕左边缘的距离,单位rpx |
Number | data.menuWidth 菜单按钮宽度,单位rpx |
Number | data.menuHeight 菜单按钮高度,单位rpx |
Object | data.enterOptions 启动参数 |
Object | data.enterOptions.apiCategory API 类别(不同apiCategory场景下的API有不同限制,UI也有不同) |
# navigator
小程序跳转相关方法
点击查看源码
/**
* @desc: 小程序跳转相关方法
* @author Davislu <davislu@tencent.com>
*/
/**
* DEFN 空方法
* @private
* @returns {undefined} 无返回值
*/
const DEFN = () => { };
/**
* navigateToWebview 方法 跳转到小程序的 web-view 容器打开 H5
* @param {object} setting 配置信息
* @param {string} setting.url 需要跳转的 H5 连接
* @param {function} setting.complete 跳转成功后的回调函数
* @param {function} setting.message 用于获取 H5 中的 postMessage 的数据
* @param {object} setting.share 页面分享信息
* @param {string} setting.share.title 页面分享标题
* @param {string} setting.share.image 页面分享图片
* @param {string} setting.share.disable 是否禁用页面分享
* @param {object} setting.navbar 页面导航栏设置
* @param {string} setting.navbar.frontColor 导航栏字体颜色
* @param {string} setting.navbar.backgroundColor 导航栏背景颜色
* @returns {void} 无返回值
*/
const navigateToWebview = ({ url: webUrl, complete = DEFN, message = DEFN, share = {}, navbar = {} }) => {
const page = '/modules/x/webcontainer/webcontainer';
let query = `url=${encodeURIComponent(webUrl)}`;
if (share.disable) {
query += '&disableShare=true';
}
else if (share.title) {
const image = share.image ? `&image=${encodeURIComponent(share.image)}` : '';
query += `&title=${encodeURIComponent(share.title)}${image}`;
}
if (navbar.frontColor)
query += `&navbarFront=${navbar.frontColor}`;
if (navbar.backgroundColor)
query += `&navbarBg=${navbar.backgroundColor}`;
const url = `${page}${/\?/.test(page) ? '&' : '?'}${query}`;
const navSetting = { url, complete };
navSetting.events = { onMessage: message };
wx.navigateTo(navSetting);
};
const obj = {
navigateToWebview,
};
export default obj;
# void navigateToWebview
navigateToWebview 方法 跳转到小程序的 web-view 容器打开 H5
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| setting | object | 配置信息 | --- | 否 |
| setting.url | string | 需要跳转的 H5 连接 | --- | 否 |
| setting.complete | function | 跳转成功后的回调函数 | --- | 否 |
| setting.message | function | 用于获取 H5 中的 postMessage 的数据 | --- | 否 |
| setting.share | object | 页面分享信息 | --- | 否 |
| setting.share.title | string | 页面分享标题 | --- | 否 |
| setting.share.image | string | 页面分享图片 | --- | 否 |
| setting.share.disable | string | 是否禁用页面分享 | --- | 否 |
| setting.navbar | object | 页面导航栏设置 | --- | 否 |
| setting.navbar.frontColor | string | 导航栏字体颜色 | --- | 否 |
| setting.navbar.backgroundColor | string | 导航栏背景颜色 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
void | 无返回值 |
# numUtils
数字处理相关函数
点击查看源码
/**
* @desc: 数字处理相关函数
*/
import { roundStr } from './stringUtils';
/**
* 四舍五入(支持保留n位小数,n>=0)
* @param {any} x 原数字, 如果n不是合法数字或者无法转换为合法数字,round结果返回NaN
* @param {any} n 保留几位小数,默认0;
1. 如果n不是合法数字或者无法转换为合法数字,round结果返回NaN;
2. 如果n小于0,round结果返回NaN;
3. 如果n的值包含小数部分,round处理时只关注n的整数部分值
* @return {number} 返回一个保留n位小数的数字,异常情况下可能是NaN
*/
const round = (x, n = 0) => parseFloat(roundStr(x, n, false));
export { round, };
# number round
四舍五入(支持保留n位小数,n>=0)
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| x | any | 原数字, 如果n不是合法数字或者无法转换为合法数字,round结果返回NaN | --- | 否 |
| n | any | 保留几位小数,默认0; |
- 如果n不是合法数字或者无法转换为合法数字,round结果返回NaN;
- 如果n小于0,round结果返回NaN;
- 如果n的值包含小数部分,round处理时只关注n的整数部分值 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
number | 返回一个保留n位小数的数字,异常情况下可能是NaN |
# objUtils
对象处理相关函数
点击查看源码
/**
* @desc: 对象处理相关函数
*/
/**
* @function
* @description 把对象拼接成 a=b&c=d 形式的字符串
* @param {Object} queryObj 需要进行序列化的对象
* @returns {String} 拼接后的字符串
*/
export const serialize = (queryObj = {}) => {
if (!queryObj) {
return '';
}
const queryArray = [];
Object.keys(queryObj).forEach((key) => {
queryArray.push(`${key}=${queryObj[key]}`);
});
return queryArray.join('&');
};
export class JsonParseError extends Error {
constructor(text, data) {
super(text);
this.data = data;
}
}
/**
* 安全的JSON.parse
* @param {object} data JSON.parse的对象
* @param {boolean} throwErrIfParseFail 如果解析失败,是否抛出错误
*/
export function safeJsonParse(data, throwErrIfParseFail = false) {
try {
return JSON.parse(data);
}
catch (e) {
if (throwErrIfParseFail) {
throw new JsonParseError('JSON.parse error', data);
}
}
return data;
}
# String serialize
把对象拼接成 a=b&c=d 形式的字符串
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| queryObj | Object | 需要进行序列化的对象 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
String | 拼接后的字符串 |
# safeJsonParse
安全的JSON.parse
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| data | object | JSON.parse的对象 | --- | 否 |
| throwErrIfParseFail | boolean | 如果解析失败,是否抛出错误 | --- | 否 |
# report
埋点上报
点击查看源码
/**
* @desc: 埋点上报
* @module: report
*/
// / <reference path='./types.ts'/>
import helper from './helper';
import sender from './sender';
import formatV1 from './formatV1';
import formatV2 from './formatV2';
const logger = wx.getLogManager({});
logger.log('report模块代码被装载');
/**
* 初始化
* @private
*/
function init(options) {
helper.init(options);
}
/**
* 旧埋点
* @param { object } data 上报对象
* @example
* report({ 27: xxxx, 34: xxx })
*/
function report(data = {}) {
if (helper.canReport()) {
logger.log('旧埋点:', data);
formatV1.formatData(data).then(arr => sender.queue(arr));
}
}
/**
* 旧埋点,快速上报,不依赖用户位置
* @param { object } data 上报对象
* @example
* fastReport({ 27: xxxx, 34: xxx })
*/
function fastReport(data = {}) {
if (helper.canReport()) {
logger.log('旧立即埋点:', data);
const arr = formatV1.formatFastData(data);
sender.send(arr);
}
}
/**
* 新埋点
* @param {string} 参数1 页面|组件的唯一标志
* @param {any} 参数2 埋点属性(会埋在埋点31个字段)
* @param {any} 参数3 埋点属性(会埋在埋点32个字段) 依次类推参数4、参数5,最多10个参数
* @returns {void}
* @example
* const { tms } = getApp({ allowDefault: true });
* const reporter = tms.getReporter();
* reporter.report2('user_security_level', {}, {});
*/
function report2(...data) {
if (helper.canReport()) {
logger.log('新埋点:', data);
formatV2.formatData(data).then(arr => sender.queue(arr));
}
}
/**
* 新埋点,埋点需要立即上报,不依赖用户位置,
* fastReport2()上报时携带的"省"、"市"等需要异步请求的基础字段会从缓存中读取,如果无缓存则为空,report2()则一定会携带这些字段
* @param {string} 参数1 页面|组件的唯一标志
* @param {any} 参数2 埋点属性(会埋在埋点31个字段)
* @param {any} 参数3 埋点属性(会埋在埋点32个字段) 依次类推参数4、参数5,最多10个参数
* @returns {void}
* @example
* const { tms } = getApp({ allowDefault: true });
* const reporter = tms.getReporter();
* reporter.fastReport2('user_security_level', {}, {});
*/
function fastReport2(...data) {
if (helper.canReport()) {
logger.log('新立即埋点:', data);
const arr = formatV2.formatFastData(data);
sender.send(arr);
}
}
export default {
init,
report,
fastReport,
report2,
fastReport2,
setSystemField: helper.setSystemField,
};
# report
旧埋点
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| data | object | 上报对象 | --- | 否 |
示例代码
Example
report({ 27: xxxx, 34: xxx })
# fastReport
旧埋点,快速上报,不依赖用户位置
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| data | object | 上报对象 | --- | 否 |
示例代码
Example
fastReport({ 27: xxxx, 34: xxx })
# void report2
新埋点
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| 参数1 | string | 页面 | 组件的唯一标志 | --- |
| 参数2 | any | 埋点属性(会埋在埋点31个字段) | --- | 否 |
| 参数3 | any | 埋点属性(会埋在埋点32个字段) 依次类推参数4、参数5,最多10个参数 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
void |
示例代码
Example
const { tms } = getApp({ allowDefault: true });
const reporter = tms.getReporter();
reporter.report2('user_security_level', {}, {});
# void fastReport2
新埋点,埋点需要立即上报,不依赖用户位置, fastReport2()上报时携带的"省"、"市"等需要异步请求的基础字段会从缓存中读取,如果无缓存则为空,report2()则一定会携带这些字段
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| 参数1 | string | 页面 | 组件的唯一标志 | --- |
| 参数2 | any | 埋点属性(会埋在埋点31个字段) | --- | 否 |
| 参数3 | any | 埋点属性(会埋在埋点32个字段) 依次类推参数4、参数5,最多10个参数 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
void |
示例代码
Example
const { tms } = getApp({ allowDefault: true });
const reporter = tms.getReporter();
reporter.fastReport2('user_security_level', {}, {});
# request
request模块作为基于 tms-core 的应用的公共请求模块。
点击查看源码
/**
* @copyright 2021-present, Tencent, Inc. All rights reserved.
* @brief request.js用于发起网络请求.
* @desc: request模块作为基于 tms-core 的应用的公共请求模块。
* 目前支持在出行服务小程序或基于出行服务的小程序中调用。在后续runtime支持公众号H5后,
* 将支持在H5中调用。
*
* 考虑到对不同运行环境的支持,强依赖运行环境的依赖,比如 wx.request,应通过注入的形式提供。
* 框架判断在不同的运行环境,切换调用不同运行环境提供的方法。
*/
import md5 from './md5';
import { getEnvInfo, getAuthInfo } from './env';
import { safeJsonParse } from './objUtils';
const logger = wx.getLogManager({});
/**
* 用于序列化需要签名的参数
* @private
* @param {object} param 需要序列化的参数
* @returns {string} 序列化之后的参数字符串
*/
const seriesParam = (param) => {
const keys = Object.keys(param)
.sort();
const series = keys.map((key) => {
const val = param[key];
return `${key}${typeof val === 'object' ? JSON.stringify(val) : val}`;
});
return series.join('');
};
/**
* 用于对request请求对象做签名
* @private
* @param {object} param 需要做签名的参数
* @param {string} secretKey 签名密钥
* @returns {object} 签名后的参数对象
*/
const sign = (param = {}, secretKey) => {
const signture = md5(seriesParam(param) + secretKey);
return { ...param, sign: signture };
};
/**
* 用于对request请求对象添加系统参数
* @private
* @param {object} param 接口调用传入的参数
* @param {Boolean} withAuth 是否需要登录参数
* @param {object} baseParam request实例定义的基础参数
* @returns {object} 全部参数对象
*/
const composeParam = async (param = {}, withAuth = true, baseParam = {}) => {
const version = '1.0';
const { appVersion, wxAppId, client } = getEnvInfo();
const nonce = Math.random()
.toString(36)
.substr(2, 10);
const timestamp = Date.now();
const random = Math.random()
.toString()
.slice(2, 7);
const sourceId = ['', 'sinan', 'mycar'].indexOf(client) + 7; // 6 未知 7 云函数 8 出行 9 我的车
const seqId = `${timestamp}${sourceId}${random}`;
const paramsWithAuth = await modifyAuthParam(param, withAuth);
const combinedParam = Object.assign({
version,
appVersion,
nonce,
timestamp,
seqId,
wxAppId,
}, { ...baseParam }, { ...paramsWithAuth });
// 清理undefined和NaN的参数
Object.keys(combinedParam)
.forEach((key) => {
if (typeof combinedParam[key] === 'number' && isNaN(combinedParam[key])) {
delete combinedParam[key];
}
if (typeof combinedParam[key] === 'undefined') {
delete combinedParam[key];
}
});
return combinedParam;
};
/**
* 用于保证业务参数的登录态参数,
* 若接口不依赖登录态 如 user/login,则保证参数中不包括userId & token,
* 若接口依赖登录态,则保证参数中填充userId & token,
* @private
* @param {object} param 要校验登录态的业务参数
* @param {boolean} withAuth 是否要校验登录态
* @returns {object} 增加登录态后的参数
*/
const modifyAuthParam = async (param, withAuth) => {
const requestParam = { ...param };
if (withAuth) {
const { userId, token } = await getAuthInfo();
requestParam.userId = userId;
requestParam.token = token;
return requestParam;
}
delete requestParam.userId;
delete requestParam.userid;
delete requestParam.token;
return requestParam;
};
let reqInstance = null;
/**
* @public
* @class Request
* @classdesc 网络请求类,对签名、鉴权等逻辑进行封装处理,用于向腾讯出行服务平台后台发送网络请求
*/
export default class Request {
/**
* 默认的request host域名
* defaultHost 在runtime初始化时进行设置,为出行服务接入层域名
* 具体业务模块 new Request() 使用时,不指定自定义 host ,将使用defaultHost
*/
static defaultHost = '';
/**
* 默认的request 签名密钥
* defaultSecretKey 在runtime初始化时进行设置,为当前小程序密钥
* 具体业务模块 new Request() 使用时,不指定自定义 secretKey ,将使用 defaultSecretKey
*/
static defaultSecretKey = '';
static getInstance() {
if (reqInstance === null) {
reqInstance = new Request();
}
return reqInstance;
}
host = '';
secretKey = '';
withAuth = true;
baseParam = {};
/**
* Request 构造函数
* @param {Object} config 构造参数
* @param {Object} config.withAuth 是否填充登录态参数
* @param {Object} config.host 自定义的host域名
* @param {Object} config.baseParam 默认携带的参数
*/
constructor(config = { withAuth: true }) {
if (config.host) {
this.host = config.host;
}
this.secretKey = config.secretKey || Request.defaultSecretKey;
if (typeof config.withAuth !== 'undefined') {
this.withAuth = !!config.withAuth;
}
this.baseParam = config.baseParam || {};
}
/**
* 格式化接口路径
* @private
* @param {string} path 需要格式化的接口路径
* @returns {string} 格式化后的接口路径
*/
makeUrl(path) {
if ((/^http/i).test(path))
return path;
const host = this.host || Request.defaultHost;
const validHost = (/^http/i).test(host) ? host : `https://${host}`;
return `${validHost}/${path}`;
}
;
/**
* @public
* @memberof Request
* @param {String} path 请求接口路径
* @param {Object} [param] 请求参数
* @param {Object} [header] 自定义请求头
* @returns {Promise} 接口响应
* @example
* const $ = getApp().tms.createRequest();
* $.get(apiPath)
* .then((data) => {
* // data {Object} 响应数据
* // {
* // errCode {Number} 接口响应状态码
* // errMsg {String} 接口响应状态信息
* // resData {Object} 接口返回数据
* // }
* })
* .catch((e) => {
* // e {Object} 错误信息
* });
*/
get(path, param, header) {
return this.doRequest(path, param, 'GET', header);
}
/**
* @public
* @memberof Request
* @param {String} path 请求接口路径
* @param {Object} [param] 请求参数
* @param {Object} [header] 自定义请求头
* @returns {Promise} 接口响应
* @example
* const $ = getApp().tms.createRequest();
* $.post(apiPath)
* .then((data) => {
* // data {Object} 响应数据
* // {
* // errCode {Number} 接口响应状态码
* // errMsg {String} 接口响应状态信息
* // resData {Object} 接口返回数据
* // }
* })
* .catch((e) => {
* // e {Object} 错误信息
* });
*/
post(path, param, header) {
return this.doRequest(path, param, 'POST', header);
}
/**
* 发送get方式的请求,该方法会返回wx.request全量的返回值(含data,header,cookies,statusCode)
* @memberof Request
* @param {string} path 请求接口路径
* @param {object} param 业务参数
* @param {object} header 自定义请求头
* @returns {promise} 接口请求promise
*/
execGet(path, param, header) {
return this.createRequestTask(path, param, 'GET', header);
}
/**
* 发送post方式的请求,该方法会返回wx.request全量的返回值(含data,header,cookies,statusCode等)
* @memberof Request
* @param {string} path 请求接口路径
* @param {object} param 业务参数
* @param {object} header 自定义请求头
* @returns {promise} 接口请求promise
*/
execPost(path, param, header) {
return this.createRequestTask(path, param, 'POST', header);
}
/**
* @memberof Request
* @param {String} path 请求接口路径
* @param {String} filePath 上传文件的本地路径
* @param {Object} param 需要携带的其他参数
* @param {Object} header 自定义的请求头
* @returns {Object} 接口返回结果
*/
async upload(path, filePath, param, header) {
const requestParam = await composeParam(param, this.withAuth, this.baseParam);
const res = await new Promise((resolve, reject) => {
wx.uploadFile({
name: 'content',
url: this.makeUrl(path),
filePath,
formData: sign(requestParam, this.secretKey),
header,
success: resolve,
fail: reject,
});
});
if (typeof res?.data === 'string') {
return safeJsonParse(res?.data);
}
return res?.data;
}
/**
* @memberof Request
* @param {string} path 请求接口路径
* @param {object} param 业务参数
* @param {string} method 请求方法 get/post
* @param {object} header 自定义的请求头
* @returns {object} 接口返回结果
*/
async doRequest(path, param = {}, method = 'POST', header = {}) {
const res = await this.createRequestTask(path, param, method, header);
if (typeof res?.data === 'string') {
return safeJsonParse(res?.data);
}
return res?.data;
}
/**
* 序列化一个 get 请求地址
* @memberof Request
* @param {string} path 请求接口路径
* @param {object} data 业务参数
* @returns {Promise} 返回序列化之后的 get 请求地址
*/
async serialize(path, data = {}) {
let url = this.makeUrl(path);
const signData = await composeParam(data, this.withAuth, this.baseParam);
const signature = sign(signData, this.secretKey);
const params = [];
Object.keys(signature).forEach((key) => {
const val = encodeURIComponent(signature[key]);
params.push(`${key}=${val}`);
});
if (params.length)
url += (/\?/.test(url) ? '&' : '?') + params.join('&');
return Promise.resolve({ url });
}
/**
* 创建发送请求任务
* @memberof Request
* @param {string} path 请求接口路径
* @param {string} param 业务参数
* @param {string} method 请求方法 get/post
* @param {object} header 自定义的请求头
* @returns {Promise} 接口返回结果
*/
async createRequestTask(path, param = {}, method = 'POST', header = {}) {
const requestParam = await composeParam(param, this.withAuth, this.baseParam);
const data = sign(requestParam, this.secretKey);
return new Promise((resolve, reject) => {
const requestTime = Date.now();
const printLog = (isSuccess, res) => {
// 埋点已经单独打日志了,接口请求数据日志太长影响分析
if (path.indexOf('basic/event/upload') !== -1) {
return;
}
// 打车、代驾日志不截断,方便用于回放
const fullApi = path.indexOf('/takecar/') !== -1 || path.indexOf('dd/api/v2/order') !== -1;
let result = JSON.stringify(res);
if (isSuccess && !fullApi && result.length > 500) {
result = `${result.substring(0, 500)} 内容太长被截断`;
}
const obj = {
path,
method,
header: JSON.stringify(header),
params: JSON.stringify(data),
duration: Date.now() - requestTime,
};
if (isSuccess) {
obj.res = result;
}
else {
obj.err = result;
}
const str = JSON.stringify(obj, null, ' ').replace(/\\"/ig, '\'');
if (isSuccess) {
logger.log(`接口请求成功:\n${str}`);
}
else {
logger.warn(`接口请求失败:\n${str}`);
}
};
wx.request({
url: this.makeUrl(path),
header,
method,
data,
enableHttp2: true,
success: (res) => {
printLog(true, res.data);
resolve(res);
},
fail: (err) => {
printLog(false, err);
reject(err);
},
});
});
}
}
# Request
网络请求类,对签名、鉴权等逻辑进行封装处理,用于向腾讯出行服务平台后台发送网络请求
# Promise get
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| path | String | 请求接口路径 | --- | 否 |
| param | Object | 请求参数 | --- | 是 |
| header | Object | 自定义请求头 | --- | 是 |
返回值
| 类型 | 说明 |
|---|---|
Promise | 接口响应 |
示例代码
Example
const $ = getApp().tms.createRequest();
$.get(apiPath)
.then((data) => {
// data {Object} 响应数据
// {
// errCode {Number} 接口响应状态码
// errMsg {String} 接口响应状态信息
// resData {Object} 接口返回数据
// }
})
.catch((e) => {
// e {Object} 错误信息
});
# Promise post
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| path | String | 请求接口路径 | --- | 否 |
| param | Object | 请求参数 | --- | 是 |
| header | Object | 自定义请求头 | --- | 是 |
返回值
| 类型 | 说明 |
|---|---|
Promise | 接口响应 |
示例代码
Example
const $ = getApp().tms.createRequest();
$.post(apiPath)
.then((data) => {
// data {Object} 响应数据
// {
// errCode {Number} 接口响应状态码
// errMsg {String} 接口响应状态信息
// resData {Object} 接口返回数据
// }
})
.catch((e) => {
// e {Object} 错误信息
});
# promise execGet
发送get方式的请求,该方法会返回wx.request全量的返回值(含data,header,cookies,statusCode)
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| path | string | 请求接口路径 | --- | 否 |
| param | object | 业务参数 | --- | 否 |
| header | object | 自定义请求头 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
promise | 接口请求promise |
# promise execPost
发送post方式的请求,该方法会返回wx.request全量的返回值(含data,header,cookies,statusCode等)
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| path | string | 请求接口路径 | --- | 否 |
| param | object | 业务参数 | --- | 否 |
| header | object | 自定义请求头 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
promise | 接口请求promise |
# Object upload
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| path | String | 请求接口路径 | --- | 否 |
| filePath | String | 上传文件的本地路径 | --- | 否 |
| param | Object | 需要携带的其他参数 | --- | 否 |
| header | Object | 自定义的请求头 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Object | 接口返回结果 |
# object doRequest
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| path | string | 请求接口路径 | --- | 否 |
| param | object | 业务参数 | --- | 否 |
| method | string | 请求方法 get/post | --- | 否 |
| header | object | 自定义的请求头 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
object | 接口返回结果 |
# Promise serialize
序列化一个 get 请求地址
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| path | string | 请求接口路径 | --- | 否 |
| data | object | 业务参数 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Promise | 返回序列化之后的 get 请求地址 |
# Promise createRequestTask
创建发送请求任务
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| path | string | 请求接口路径 | --- | 否 |
| param | string | 业务参数 | --- | 否 |
| method | string | 请求方法 get/post | --- | 否 |
| header | object | 自定义的请求头 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Promise | 接口返回结果 |
# exports
# rpx
pr\rpx互相转化
点击查看源码
/**
* @desc: pr\rpx互相转化
*/
import syncApi from './syncfnmanager';
/**
* @description rpx to px
* @param {Number} rpx 需要转换的rpx数值
* @returns {Number} 转换后的px数值
*/
const rpxToPx = (rpx) => {
const sys = syncApi.getSystemInfoSync();
const ww = sys.windowWidth;
const ratio = ww / 750;
return rpx * ratio;
};
/**
* @description px to rpx
* @param {Number} px 需要转换的px数值
* @returns {Number} 转换后的rpx数值
*/
const pxToRpx = (px) => {
const sys = syncApi.getSystemInfoSync();
const ww = sys.windowWidth;
const ratio = 750 / ww;
return px * ratio;
};
export { rpxToPx, pxToRpx, };
# Number rpxToPx
rpx to px
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| rpx | Number | 需要转换的rpx数值 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Number | 转换后的px数值 |
# Number pxToRpx
px to rpx
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| px | Number | 需要转换的px数值 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Number | 转换后的rpx数值 |
# runtime-car
车辆相关
点击查看源码
/**
* @module: runtime-car
* @desc: 车辆相关
*/
let carInfo = {}; // 当前车信息
let carList = []; // 缓存到的车列表信息
let getCarListProm = null;
/**
* @class CarService
* @classdesc 维护整个小程序逻辑中的当前车概念,提供当前车信息与获取车列表接口
*/
export default class CarService {
/**
* @memberof CarService
* getCarInfo 方法 获取当前车信息
* @returns {object} 车信息对象
*/
static getCarInfo() {
if (carInfo?.wecarId) {
return carInfo;
}
carInfo = wx.getStorageSync('currentCar') || {};
return carInfo;
}
/**
* @memberof CarService
* setCarInfo 方法 设置当前车信息,必传 param.wecarId
* @param {object} param 要设置的当前车信息
* @returns {void}
*/
static setCarInfo(param = {}) {
let info = { ...param };
if (info.wecarId) {
carList.some((item) => {
if (item.wecarId === info.wecarId) {
info = { ...item, ...info };
return true;
}
return false;
});
}
carInfo = { ...info };
wx.setStorageSync('currentCar', { ...carInfo });
}
/**
* @memberof CarService
* getCarList 方法 获取用户车列表数据
* @returns {promise} 获取车列表的promise
*/
static getCarList() {
// 防止并发请求车列表
if (getCarListProm) {
return getCarListProm;
}
getCarListProm = new Promise(async (resolve, reject) => {
const app = getApp({ allowDefault: true });
app.tms.createRequest().post('user/carlist', {})
.then((res) => {
getCarListProm = null;
if (res.errCode === 0) {
carList = res.resData.list.map(car => ({
...car,
register: car.vin !== '' && car.plate !== '' && car.engineNo !== '' && car.v2ModelName !== '' && car.v2BrandName !== '',
}));
carList.some((item) => {
if (item.wecarId === carInfo.wecarId) {
carInfo = { ...item, ...carInfo };
return true;
}
return false;
});
resolve(carList);
}
else {
reject(res);
}
})
.catch((res) => {
getCarListProm = null;
reject(res);
});
});
return getCarListProm;
}
}
# CarService
维护整个小程序逻辑中的当前车概念,提供当前车信息与获取车列表接口
# carInfo
# runtime-index
集成业务通用接口(登录、车辆、微信支付分)
点击查看源码
/* eslint no-underscore-dangle: 0 */
/**
* @copyright 2021-present, Tencent, Inc. All rights reserved.
* @module: runtime-index
* @desc: 集成业务通用接口(登录、车辆、微信支付分)
*/
import Login from './login';
import Car from './car';
import getOpenAppTrafficData from './getopenapptrafficdata';
const { loginFn, getOpenId, getMycarPubOpenId, getSinanPubOpenId, getPhone, registerPhone } = Login;
/**
* __resolver__ 用于维护 app.tms.fn 中数据的填充
* app.tms.fn 中的部分数据依赖初始化流程完成,属于异步获取,
* __resolver__ 属于内部变量,不应在运行时初始化流程外部使用,不做导出。
* @private
*/
const __resolver__ = {};
/**
* tms.getLoginInfo 用于提供给业务模块获取当前运行时的登录信息
* @private
*/
const loginInfoPromise = new Promise(resolver => __resolver__.getLoginInfo = resolver);
/**
* @public
* @description 获取用户userId等登录信息
* @returns {Promise<UserInfo>} 返回用户登录信息
* @returns {UserInfo.userId} 用户userId
* @returns {UserInfo.token} 用户token
* @returns {UserInfo.openId} 用户openId
* @returns {UserInfo.firstLogin} 用户登录类型:0-非首次登录,1-首次登录
* @example
* getApp().tms.getLoginInfo().then((res) => {
* // res {Object} 返回数据
* // {
* // userId {String} 用户userId
* // token {String} 用户token
* // openId {String} 用户openId
* // firstLogin {Boolean} 是否首次登录
* // }
* });
*/
const getLoginInfo = () => loginInfoPromise;
/**
* @public
* @description 获取用户userId等登录信息
* @param autoLogin 未登录时是否自动登录(小程序中启动时就已登录,该参数用于uniapp的h5页面)
*/
function checkLogin(autoLogin = false) {
// uniapp的h5会覆盖checkLogin函数实现检查和自动登录功能
return getLoginInfo().then(() => true);
}
/**
* tms.getCarManager 用户获取小程序统一维护的车信息管理器
* @private
* @returns {object} 车信息管理器
*/
const getCarManager = () => Car;
/**
* login方法用于完成小程序登录
* @private
* @returns {object} 登录成功后的用户信息
* 返回数据示意
* {
* userId: "1135608",
* token: "xxxxxx",
* firstLogin: 0,
* },
*/
const login = async () => {
const res = await loginFn();
const loginInfo = { ...res, userId: res.uid };
__resolver__.getLoginInfo(loginInfo);
return loginInfo;
};
/**
* 设置微信支付分授权
* @param {Object} params对象类型 以下是params的解释
* @param {Number}params.traceSource 请求来源: 1-订单 2-支付网关 3-车机网关 4-开放平台 6-分账
* @param {String } param.subMchId 商户id
* @param {Number} params.bussClassify 请求来源: 1-洗车 2-停车 12-代驾 24-租车 13-保养 3-加油
* @param {Number} params.payChannel 请求来源: 1-小程序 2-车机 3-小场景 4-开放平台 5-H5
* @param {String} params.wechaId 公众号id,支付分授权相关一般都用我的车的车公众号
* @param {String} params.subAppId 小程序的id,支付分授权相关一般都用我的车小程序id
* @param {String} params.openId 用户在我的车公众号下面的id
* @returns { object } { state: 1, errMsg: 200, errMsg: '', errData: '' }
*/
async function setPaypointAuth(param = {}) {
const request = getRequestInstnce();
const userInfo = await getLoginInfo();
const req = {
traceSource: param.traceSource || 1,
subMchId: param.subMchId || '1602483333',
bussClassify: param.bussClassify || 24,
payChannel: param.payChannel || 1,
subAppId: param.appId || 'wx735359beafd78b9f',
appId: param.wechaId || 'wx29d15d946a7f2351',
...userInfo,
openId: userInfo.mycarPubOpenId,
};
const res = await request.post('paypoint/setpayauth', req);
if (res.errCode === 0) {
try {
await wx.navigateToMiniProgram({
appId: 'wxd8f3793ea3b935b8',
path: 'pages/use/enable',
extraData: {
apply_permissions_token: res.resData.token,
},
});
}
catch (err) {
return { status: 0, err };
}
return { status: 1, ...res };
}
return { status: 0, ...res };
}
// 获取项目中初始化的request实例
function getRequestInstnce() {
const { tms } = getApp();
const request = tms.createRequest();
return request;
}
/**
* 获取微信支付分授权
* @param {Object} params对象类型 以下是params的解释
* @param {Number} params.traceSource 请求来源: 1-订单 2-支付网关 3-车机网关 4-开放平台 6-分账
* @param {Number} param.payChannel 请求来源: 1-小程序 2-车机 3-小场景 4-开放平台 5-H5
* @returns { object } { errMsg: 200, errMsg: '', errData: '' }
*/
async function getPermissionList(param = {}) {
const request = getRequestInstnce();
this.userInfo = await getLoginInfo();
const data = {
traceSource: param.traceSource || 1,
payChannel: param.payChannel || 1, // 请求来源
};
const res = await request.post('paypoint/getpermissionlist', data);
return res;
}
/**
* 获取微信支付分授权
* @param {Object} params对象类型 以下是params的解释
* @param {Number} params.traceSource 请求来源: 1-订单 2-支付网关 3-车机网关 4-开放平台 6-分账
* @param {String} params.subMchId 商户id
* @param {Number} params.bussClassify请求来源: 1-洗车 2-停车 12-代驾 24-租车 13-保养 3-加油
* @param {Number} params.payChannel请求来源: 1-小程序 2-车机 3-小场景 4-开放平台 5-H5
* @param {String} params.wechaId 公众号id,支付分授权相关一般都用我的车的车公众号
* @param {String} params.subAppId小程序的id,支付分授权相关一般都用我的车小程序id
* @param {String} params.openId用户在我的车公众号下面的id
* @returns { object } { state: 1, errMsg: 200, errMsg: '', errData: '' }
*/
async function terminatePaypointPermisson(param = {}) {
const request = getRequestInstnce();
const userInfo = await getLoginInfo();
const data = {
traceSource: param.traceSource || 1,
subMchId: param.subMchId || '1602483333',
bussClassify: param.bussClassify || 24,
payChannel: param.payChannel || 1,
subAppId: param.appId || 'wx735359beafd78b9f',
appId: param.wechaId || 'wx29d15d946a7f2351',
reason: param.reason || '其他',
serviceId: param.serviceId,
...userInfo,
openId: userInfo.mycarPubOpenId,
};
const res = await request.post('paypoint/terminatePaypointPermisson', data);
return res;
}
/**
* 获取用户在某个业务中是否授权
* @param {Object} params对象类型 以下是params的解释
* @param {Number} params.traceSource 请求来源: 1-订单 2-支付网关 3-车机网关 4-开放平台 6-分账
* @param {Number} params.bussClassify 请求来源: 1-洗车 2-停车 12-代驾 24-租车 13-保养 3-加油
* @param {Number} params.payChannel 请求来源: 1-小程序 2-车机 3-小场景 4-开放平台 5-H5
* @returns { object } { state: 1, errMsg: 200, errMsg: '', errData: '' }
*/
async function queryServicePermissions(param = {}) {
const request = getRequestInstnce();
this.userInfo = await getLoginInfo();
const data = {
traceSource: param.traceSource || 1,
payChannel: param.payChannel || 1,
bussClassify: param.payChannel || 24, // 业务类型
};
const res = await request.post('paypoint/queryServicePermissions', data);
return res;
}
/**
* @namespace runtimeapi
* @description 对外暴露的api
* @param {Function} getPhone 获取手机号
* @param {Function} login... 详见跳转[runtime-login](#runtime-login)
* @param {Function} getLoginInfo 返回 loginInfoPromise
* @param {Function} getOpenId 获取openId 详见跳转[runtime-login](#runtime-login)
* @param {Function} getMycarPubOpenId 获取用户在我的车公众号下的openId 详见跳转[runtime-login](#runtime-login)
* @param {Function} getSinanPubOpenId 获取用户在出行服务公众号下的openId 详见跳转[runtime-login](#runtime-login)
* @param {Function} getCarManager 用户获取小程序统一维护的车信息管理器,详见跳转 [runtimt-car](#runtime-car)
* @param {Function} setPaypointAuth 设置微信支付分授权
* @param {Function} getPermissionList 获取微信支付分授权
* @param {Function} terminatePaypointPermisson 获取微信支付分授权
* @param {Function} queryServicePermissions 获取用户在某个业务中是否授权
*/
const api = {
getPhone,
registerPhone,
login,
checkLogin,
getLoginInfo,
getOpenId,
getMycarPubOpenId,
getSinanPubOpenId,
getCarManager,
setPaypointAuth,
getPermissionList,
terminatePaypointPermisson,
getOpenAppTrafficData,
queryServicePermissions,
/**
* 对外暴露__resolver__变量,在主项目未完成迁移时,登录完成后,将登录态同步给runtime
* 注意这并不是架构升级的最终态,在完成主项目的迁移后,我们将不在对外暴露__resolver__
* @private
*/
__resolver__,
};
export default api;
# namespace runtimeapi
对外暴露的api
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| getPhone | function | 获取手机号 | --- | 否 |
| login... | function | 详见跳转runtime-login | --- | 否 |
| getLoginInfo | function | 返回 loginInfoPromise | --- | 否 |
| getOpenId | function | 获取openId 详见跳转runtime-login | --- | 否 |
| getMycarPubOpenId | function | 获取用户在我的车公众号下的openId 详见跳转runtime-login | --- | 否 |
| getSinanPubOpenId | function | 获取用户在出行服务公众号下的openId 详见跳转runtime-login | --- | 否 |
| getCarManager | function | 用户获取小程序统一维护的车信息管理器,详见跳转 runtimt-car | --- | 否 |
| setPaypointAuth | function | 设置微信支付分授权 | --- | 否 |
| getPermissionList | function | 获取微信支付分授权 | --- | 否 |
| terminatePaypointPermisson | function | 获取微信支付分授权 | --- | 否 |
| queryServicePermissions | function | 获取用户在某个业务中是否授权 | --- | 否 |
# Promise.<UserInfo> getLoginInfo
获取用户userId等登录信息
返回值
| 类型 | 说明 |
|---|---|
Promise.<UserInfo> | 返回用户登录信息 |
UserInfo.userId | 用户userId |
UserInfo.token | 用户token |
UserInfo.openId | 用户openId |
UserInfo.firstLogin | 用户登录类型:0-非首次登录,1-首次登录 |
示例代码
Example
getApp().tms.getLoginInfo().then((res) => {
// res {Object} 返回数据
// {
// userId {String} 用户userId
// token {String} 用户token
// openId {String} 用户openId
// firstLogin {Boolean} 是否首次登录
// }
});
# checkLogin
获取用户userId等登录信息
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| autoLogin | 未登录时是否自动登录(小程序中启动时就已登录,该参数用于uniapp的h5页面) | --- | 否 |
# object setPaypointAuth
设置微信支付分授权
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| params对象类型 | Object | 以下是params的解释 | --- | 否 |
| params.traceSource | Number | 请求来源: 1-订单 2-支付网关 3-车机网关 4-开放平台 6-分账 | --- | 否 |
| param.subMchId | String | 商户id | --- | 否 |
| params.bussClassify | Number | 请求来源: 1-洗车 2-停车 12-代驾 24-租车 13-保养 3-加油 | --- | 否 |
| params.payChannel | Number | 请求来源: 1-小程序 2-车机 3-小场景 4-开放平台 5-H5 | --- | 否 |
| params.wechaId | String | 公众号id,支付分授权相关一般都用我的车的车公众号 | --- | 否 |
| params.subAppId | String | 小程序的id,支付分授权相关一般都用我的车小程序id | --- | 否 |
| params.openId | String | 用户在我的车公众号下面的id | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
object | { state: 1, errMsg: 200, errMsg: '', errData: '' } |
# object getPermissionList
获取微信支付分授权
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| params对象类型 | Object | 以下是params的解释 | --- | 否 |
| params.traceSource | Number | 请求来源: 1-订单 2-支付网关 3-车机网关 4-开放平台 6-分账 | --- | 否 |
| param.payChannel | Number | 请求来源: 1-小程序 2-车机 3-小场景 4-开放平台 5-H5 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
object | { errMsg: 200, errMsg: '', errData: '' } |
# object terminatePaypointPermisson
获取微信支付分授权
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| params对象类型 | Object | 以下是params的解释 | --- | 否 |
| params.traceSource | Number | 请求来源: 1-订单 2-支付网关 3-车机网关 4-开放平台 6-分账 | --- | 否 |
| params.subMchId | String | 商户id | --- | 否 |
| params.bussClassify请求来源: | Number | 1-洗车 2-停车 12-代驾 24-租车 13-保养 3-加油 | --- | 否 |
| params.payChannel请求来源: | Number | 1-小程序 2-车机 3-小场景 4-开放平台 5-H5 | --- | 否 |
| params.wechaId | String | 公众号id,支付分授权相关一般都用我的车的车公众号 | --- | 否 |
| params.subAppId小程序的id,支付分授权相关一般都用我的车小程序id | String | --- | 否 | |
| params.openId用户在我的车公众号下面的id | String | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
object | { state: 1, errMsg: 200, errMsg: '', errData: '' } |
# object queryServicePermissions
获取用户在某个业务中是否授权
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| params对象类型 | Object | 以下是params的解释 | --- | 否 |
| params.traceSource | Number | 请求来源: 1-订单 2-支付网关 3-车机网关 4-开放平台 6-分账 | --- | 否 |
| params.bussClassify | Number | 请求来源: 1-洗车 2-停车 12-代驾 24-租车 13-保养 3-加油 | --- | 否 |
| params.payChannel | Number | 请求来源: 1-小程序 2-车机 3-小场景 4-开放平台 5-H5 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
object | { state: 1, errMsg: 200, errMsg: '', errData: '' } |
# runtime-login
登录相关
点击查看源码
/* eslint-disable valid-jsdoc */
/**
* @copyright 2021-present, Tencent, Inc. All rights reserved.
* @module: runtime-login
* @desc: 登录相关
* @brief login.js 用于维护 runtime 框架的用户登录流程,获取用户的登录的可信凭证.
* runtime初始化时,会调用登录流程,基于runtime的后续业务代码,不必关注用户的登录状态,
* 当前,登录流程仅支持【腾讯出行服务小程序】,我们计划支持出行服务公众号的H5开发,基于runtime的
* 微信H5开发,不必关注微信的授权登录流程(即重定向微信页面,换取token等),H5的登录流程也收敛到
* login.js中。
* 另外,runtime 对【我的车小程序】的支持,以及后续对其他基于出行服务架构的小程序支持,都在我们规划之内。
*
*
* getOpenAppTrafficData, 此方法用于获取登录用户来源,用于给用户打标签等,打标签耦合在登录接口中,
* 因此这部分代码迁移到了 tms-rumtine 中,
* 从 runtime 的角度考虑,登录流程应尽量纯粹,打标签应属于业务流程范畴,与 runtime 无关。
* 后续我们拓展 runtime,使之适用于不同小程序和H5项目时,其登录流程不一定有打标签这一动作。
*
*/
import getOpenAppTrafficData from './getopenapptrafficdata';
/**
* 调用wx.login获取登录code
* @private
*/
const getCode = () => new Promise((resolve, reject) => {
wx.login({ success: ({ code }) => resolve(code), fail: reject });
});
let loginProcessing = false; // 是否有进行中的登录流程
/**
* login 方法 获取用户信息
* @private
* @return {object} userInfo
*/
const loginFn = async () => {
if (loginProcessing)
return;
loginProcessing = true;
let userInfo = {};
try {
let userData = await login();
if (userData.errCode === 10 || !userData.resData.userInfo || !userData.resData.userInfo.uid) {
userData = await login();
}
if (userData.errCode === 0) {
wx.setStorageSync('userInfo', userData.resData.userInfo);
userInfo = userData.resData.userInfo;
}
loginProcessing = false;
}
catch (e) {
loginProcessing = false;
}
return userInfo;
};
/**
* 登录重试接口
* @private
* @returns { object } errCode 接口返回错误码
* @returns { object } resData 用户信息
*/
async function login() {
let userData = {};
try {
const code = await getCode();
// @ts-ignore
const { trafficEntrence: registerSource, scene: sceneId } = getOpenAppTrafficData();
userData = await getApp().tms.createRequest({ withAuth: false }).post('user/login', { code, registerSource, sceneId });
}
catch (e) {
console.error(e); // eslint-disable-line
}
return {
errCode: userData.errCode,
resData: userData.resData || {},
};
}
let getOpenIdProm; // 获取用户在小程序中的openId状态
/**
* @return {string} openId
* 获取成功时,返回resolved Promise,resolved数据为openId
* 获取失败时,返回rejected Promise,rejected数据为错误对象
* @description 获取用户在小程序中的openId
*/
const getOpenId = async () => {
if (getOpenIdProm) { // 避免重复获取
return getOpenIdProm;
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
getOpenIdProm = new Promise(async (resolve, reject) => {
getApp().tms.callCloudFunc('user', { $url: 'user/getOpenId' })
.then((res) => {
const openId = res?.result?.resData?.openId;
if (openId) {
resolve(openId);
}
getOpenIdProm = null;
reject(new Error('No openId found in cloud function res'));
})
.catch((res) => {
getOpenIdProm = null;
reject(res);
});
});
return getOpenIdProm;
};
/**
* 获取用户在指定公众号下的openId
* @private
* @return {Promise<String>}
* 获取成功时,返回resolved Promise,resolved数据为用户在指定公众号下的openId
* 获取失败时,返回rejected Promise,rejected数据为错误对象
*/
const getOaPubOpenId = oaName => getApp().tms.createRequest({ withAuth: true }).post('user/getpubopenid', { oaName })
.then((res) => {
const { errCode, resData } = res;
if (errCode !== 0) {
return Promise.reject(res);
}
const { mycarPubOpenId, sinanPubOpenId } = resData;
if (oaName === 'sinan') {
return sinanPubOpenId
|| Promise.reject(new Error(`No sinanPubOpenId found in response ${JSON.stringify(res)}`));
}
return mycarPubOpenId || Promise.reject(new Error(`No mycarPubOpenId found in response ${JSON.stringify(res)}`));
});
let getSinanPubOpenIdProm;
let getMycarPubOpenIdProm;
/**
* @description 获取用户在我的车公众号下的openId
* @return {Promise<String>}
* 获取成功时,返回resolved Promise,resolved数据为用户在我的车公众号下的openId
* 获取失败时,返回rejected Promise,rejected数据为错误对象
*/
const getMycarPubOpenId = () => {
if (getMycarPubOpenIdProm) { // 避免重复获取
return getMycarPubOpenIdProm;
}
getMycarPubOpenIdProm = getOaPubOpenId('mycar')
.then(openId => openId || Promise.reject(new Error('No mycarPubOpenId found')))
.catch((e) => {
getMycarPubOpenIdProm = null;
return Promise.reject(e);
});
return getMycarPubOpenIdProm;
};
/**
* @description 获取用户在出行服务公众号下的openId
* @return {Promise<String>}
* 获取成功时,返回resolved Promise,resolved数据为用户在出行服务公众号下的openId
* 获取失败时,返回rejected Promise,rejected数据为错误对象
*/
const getSinanPubOpenId = () => {
if (getSinanPubOpenIdProm) { // 避免重复获取
return getSinanPubOpenIdProm;
}
getSinanPubOpenIdProm = getOaPubOpenId('sinan')
.then(openId => openId || Promise.reject(new Error('No sinanPubOpenId found')))
.catch((e) => {
getSinanPubOpenIdProm = null;
return Promise.reject(e);
});
return getSinanPubOpenIdProm;
};
/**
* @public
* @description 获取用户手机号
* @return {Promise<String|Object>} 手机号
* @example
* getApp().tms.getPhone()
* .then((phone) => {
* // ...
* })
* .catch((e) => {
* // ...
* });
*/
const getPhone = () => getApp().tms.createRequest({ withAuth: true }).post('user/phone/fetch')
.then((res) => {
if (res && res.errCode === 0) {
return (res.resData?.phoneno) || '';
}
return Promise.reject(res);
});
/**
* @description 绑定手机号
* @param {Object} data params 加密数据对象
* @param {string} data.encryptedData button组件bindgetphonenumber事件返回的encryptedData字段
* @param {string} data.iv button组件bindgetphonenumber事件返回的iv字段
* @returns {Object} data 绑定结果回调
* @returns {number} data.success 是否绑定成功 boolean类型
* @returns {number} data.phone 绑定成功时的手机号,失败则为空
* @returns {number} data.errMsg 绑定结果描述信息
* @example
* const { tms } = getApp({ allowDefault: true });
* const res = await tms.registerPhone({ encryptedData, iv })
* if(res.success) {
* console.log('phone', res.phone)
* } else {
* // 注册失败处理逻辑
* console.log(res.errMsg)
* }
*/
async function registerPhone({ encryptedData, iv }) {
try {
const result = await getApp().tms.createRequest().post('user/phone/regist', {
iv,
isCipher: 1,
phoneno: encryptedData,
});
return {
success: result.errCode === 0,
phone: result.errCode === 0 ? result.resData.phoneno : '',
errMsg: result.errMsg,
};
}
catch (error) {
console.error(error);
return {
success: false,
phone: '',
errMsg: '绑定失败',
};
}
}
const runtimeObj = {
loginFn,
getOpenId,
getPhone,
registerPhone,
getMycarPubOpenId,
getSinanPubOpenId,
};
export default runtimeObj;
# string getOpenId
获取用户在小程序中的openId
返回值
| 类型 | 说明 |
|---|---|
string | openId获取成功时,返回resolved Promise,resolved数据为openId获取失败时,返回rejected Promise,rejected数据为错误对象 |
# Promise.<String> getMycarPubOpenId
获取用户在我的车公众号下的openId
返回值
| 类型 | 说明 |
|---|---|
Promise.<String> | 获取成功时,返回resolved Promise,resolved数据为用户在我的车公众号下的openId获取失败时,返回rejected Promise,rejected数据为错误对象 |
# Promise.<String> getSinanPubOpenId
获取用户在出行服务公众号下的openId
返回值
| 类型 | 说明 |
|---|---|
Promise.<String> | 获取成功时,返回resolved Promise,resolved数据为用户在出行服务公众号下的openId获取失败时,返回rejected Promise,rejected数据为错误对象 |
# Promise.<(String|Object)> getPhone
获取用户手机号
返回值
| 类型 | 说明 |
|---|---|
Promise.<(String|Object)> | 手机号 |
示例代码
Example
getApp().tms.getPhone()
.then((phone) => {
// ...
})
.catch((e) => {
// ...
});
# Object registerPhone
绑定手机号
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| data | Object | params 加密数据对象 | --- | 否 |
| data.encryptedData | string | button组件bindgetphonenumber事件返回的encryptedData字段 | --- | 否 |
| data.iv | string | button组件bindgetphonenumber事件返回的iv字段 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Object | data 绑定结果回调 |
number | data.success 是否绑定成功 boolean类型 |
number | data.phone 绑定成功时的手机号,失败则为空 |
number | data.errMsg 绑定结果描述信息 |
示例代码
Example
const { tms } = getApp({ allowDefault: true });
const res = await tms.registerPhone({ encryptedData, iv })
if(res.success) {
console.log('phone', res.phone)
} else {
// 注册失败处理逻辑
console.log(res.errMsg)
}
# storage
处理localstorage相关函数
点击查看源码
/**
* @desc: 处理localstorage相关函数
*/
/**
* 保存数据到localstorage
* @param key
* @param data
* @returns {boolean} true为成功,false为报错
*/
function setItem(key, data) {
try {
wx.setStorageSync(key, data);
return true;
}
catch {
return false;
}
}
/**
* 从localstorage取数据
* @param key
* @param defaultValue wx接口报错时返回默认值
* @returns {any}
*/
function getItem(key, defaultValue = null) {
try {
const val = wx.getStorageSync(key);
return val === '' ? defaultValue : val;
}
catch {
return defaultValue;
}
}
/**
* 从localstorage中删除
* @param key
* @returns {boolean}
*/
function removeItem(key) {
try {
wx.removeStorageSync(key);
return true;
}
catch {
return false;
}
}
const cleanQueue = [];
let cleanTimerId = 0;
function cleanTask() {
const { keys } = wx.getStorageInfoSync();
while (cleanQueue.length > 0) {
const { key, version } = cleanQueue.pop();
for (let i = version - 1; i > version - 10 && i > 0; i--) {
const str = `${key}_v${i}`;
if (keys.includes(str)) {
removeItem(str);
}
}
}
cleanTimerId = 0;
}
/**
* 缓存组件或页面的缓存data,会缓存到localStorage里
* @param {string} key localStorage的唯一标志
* @param {string} version 版本号,低于该版本号的缓存会被异步清除
* @param {object} data 页面缓存的数据
* @returns {boolean}
*/
function setCacheData(key, version, data) {
// 异步清理旧数据,不要阻塞当前线程
if (!cleanQueue.some(q => q.key === key && q.version === version)) {
cleanQueue.push({ key, version });
if (cleanTimerId === 0) {
cleanTimerId = setTimeout(cleanTask, 2000);
}
}
const str = `${key}_v${version}`;
return setItem(str, data);
}
/**
* 获取组件或页面的缓存data
* @param {string} key localStorage的唯一标志
* @param {string} version 版本号
* @param {object} defaultData 接口报错时返回默认值
* @returns {any}
*/
function getCacheData(key, version, defaultData = null) {
const str = `${key}_v${version}`;
return getItem(str, defaultData);
}
export { getItem, setItem, setCacheData, getCacheData, };
# boolean setItem
保存数据到localstorage
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| key | --- | 否 | ||
| data | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
boolean | true为成功,false为报错 |
# any getItem
从localstorage取数据
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| key | --- | 否 | ||
| defaultValue | wx接口报错时返回默认值 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
any |
# boolean removeItem
从localstorage中删除
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| key | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
boolean |
# boolean setCacheData
缓存组件或页面的缓存data,会缓存到localStorage里
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| key | string | localStorage的唯一标志 | --- | 否 |
| version | string | 版本号,低于该版本号的缓存会被异步清除 | --- | 否 |
| data | object | 页面缓存的数据 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
boolean |
# any getCacheData
获取组件或页面的缓存data
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| key | string | localStorage的唯一标志 | --- | 否 |
| version | string | 版本号 | --- | 否 |
| defaultData | object | 接口报错时返回默认值 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
any |
# stringUtils
字符串处理相关函数
点击查看源码
/**
* @desc: 字符串处理相关函数
*/
/**
* 字符串截断,处理时按可见字符长度进行截断
* 可见字符的含义是指:字母、数字、汉字、表情等均等价于一个字符
* @param {String} str - 原始字符串
* @param {Number} maxLen - 字符串截断后的最大长度
* @returns {String} 截断后的字符串;如果确实进行了截断,在最后面加'...';
*/
const subStr = (str, maxLen) => {
// 按照码点(codePoint),把原始字符串的前maxLen+1个字符放到chars数组中
const chars = [];
for (const codePoint of str) { // eslint-disable-line
chars.push(codePoint);
if (chars.length > maxLen)
break;
}
// 如果可见字符数量小于等于字符串截断后的最大长度,无需截断,返回原始字符串
if (chars.length <= maxLen) {
return str;
}
// 可见字符数量多于字符串截断后的最大长度,需要截断,并在末尾添加...
// 注意,此时返回的字符串是 maxLen-2个可见字符 + ...(maxLen-2是因为...占用2个汉字字符位置)
return `${chars.splice(0, maxLen - 2).join('')}...`;
};
/**
* 手机号处理,隐藏中间部分位数
* 隐藏规则:假设隐藏部分位数后,手机号由ABC构成,其中A、C是可见部分,B是隐藏部分
* 1. 隐藏部分(B)占手机号总位数的1/3,且隐藏部分的位数向上取整
* 2. 不被隐藏的部分(A、C)要均匀分布在隐藏部分(B)的两侧,即尽量使AC等长
* 3. 如果不隐藏部分(A、C)长度无法等长,则使C多显示一位
* @param {String} phone 手机号
* @returns {String} 隐藏后的手机号
*/
const hidePhoneCenter = (phone) => {
const len = phone && phone.length;
if (!len) {
return '';
}
// 各部分位数
const center = Math.ceil(len / 3);
const left = Math.floor((len - center) / 2);
const right = len - center - left;
// 各部分字符串
const centerStr = phone.substr(left, center).replace(/./g, '*');
const leftStr = phone.substr(0, left);
const rightStr = phone.substr(-right);
return `${leftStr}${centerStr}${rightStr}`;
};
/**
* 格式化车牌
* 例如:京A12345 -> 京A·12345
* @param {String} plate 车牌号
* @returns {String} 格式化后的车牌号
*/
const formatPlate = (plate) => {
if (!plate || typeof plate !== 'string') {
return '';
}
if (plate.length <= 2 || /·/.test(plate)) {
return plate;
}
return `${plate.substring(0, 2)}·${plate.substring(2)}`;
};
/**
* 检查手机号是否合法
* @param {String} phone 手机号
* @returns {Boolean} 手机号是否合法
*/
const isValidPhone = phone => /^1[\d]{10}$/.test(phone);
/**
* 检查验证码是否合法
* @param {String} code 验证码
* @returns {Boolean} 验证码是否合法
*/
const isValidAuthCode = code => /^[\d]{6}$/.test(code);
const validPlateFirstLetters = ['京', '津', '冀', '晋', '蒙', '辽', '吉', '黑', '沪', '苏', '浙', '皖', '闽', '赣', '鲁',
'豫', '鄂', '湘', '粤', '桂', '琼', '渝', '川', '贵', '云', '藏', '陕', '甘', '青', '宁', '新', '使', '领'];
/**
* 检查车牌是否合法
* @param {String} plate 车牌
* @returns {Boolean} 车牌是否合法
*/
const isValidPlate = (plate) => {
if (!plate || typeof plate !== 'string') {
return false;
}
// 检查首位是否是合法的汉字
const [firstLetter] = plate;
if (validPlateFirstLetters.indexOf(firstLetter) === -1) {
return false;
}
if (!/[使领警学]/.test(plate)) { // 普通车牌
const number = plate.substring(1);
return /^[a-zA-Z][0-9a-zA-Z]{5}$/.test(number) // 第一位是字母,后面5位是字母或数字
|| /^[a-zA-Z][a-zA-Z][0-9a-zA-Z]{5}$/.test(number) // 第一位是字母,第二位是字母,后面再跟5位字母或数字(新能源小车)
|| /^[a-zA-Z][0-9a-zA-Z]{5}[a-zA-Z]$/.test(number) // 第一位是字母,最后一位是字母,中间5位字母或数字(新能源大车)
|| /^粤Z[0-9a-zA-Z]{4,5}[港澳]{1}/.test(plate); // 广东港澳两地车牌
}
// return /^[^使领警学]{1}[a-zA-Z][0-9a-zA-Z]{3,4}[使领警学]{1}$/.test(plate) || // 使/领/警/学车牌
// /^[使领]{1}[0-9a-zA-Z]{5}$/.test(plate); // 使馆领事馆特殊车牌
return false;
};
/**
* 四舍五入,并返回格式化的字符串
* 支持保留n位小数,n>=0,如 roundStr(1.325, 2)=1.33
* 支持格式化字符串时取出末尾的0,如roundStr(1.109, 2, true)=1.1
* @param {any} x 原数字, 如果n不是合法数字或者无法转换为合法数字,roundStr结果返回''
* @param {any} n 保留几位小数,默认0
* @param {boolean} removeTrailingZero 是否移除字符串末尾的无效数字0
1. 如果n不是合法数字或者无法转换为合法数字,roundStr结果返回''
2. 如果n小于0,roundStr结果返回''
3. 如果n的值包含小数部分,roundStr处理时只关注n的整数部分值
* @returns {string} 返回四舍五入后的字符串,异常情况下返回空字符串''
*/
const roundStr = (x, n = 2, removeTrailingZero = false) => {
const nNum = Math.floor(Number(n)); // n转换为数字,且只保留整数部分
let xNum = Number(x); // x转换为数字
// 异常情况,返回''
if (isNaN(xNum) || isNaN(nNum) || nNum < 0) {
return '';
}
// 仅保留整数的情况
if (nNum === 0) {
return Math.round(xNum).toString();
}
const rexExp = new RegExp(`\\.\\d{${nNum}}5`);
// 保留n位小数的情况
// 1. 大部分情况下,四舍五入使用Number.toFixed即可
// 2. 然而,Number.toFixed方法在某些情况下对第n+1位是5的四舍五入存在问题,如1.325保留2小数时结果为1.32(期望为1.33)
// 对此种情况下,有两种处理方式:
// 2.1 先扩大10^n倍,舍掉小数部分取整数部分,然后加1,最后缩小10^n倍
// 但此种情况下,不能处理过大的数字,也不能处理保留小数位数过多的情况,会可能导致数字超过Infinity
// 2.2 Number.toFixed是四舍6入,对于第n+1位是5的情况,增加2*10^(-n-1),保证满足第n+1位>6
// 增加2*10^(-n-1)而不是增加1*10^(-n-1)是因为后者不能保证第n+1位>=6,例如1.325+0.001=1.32599999...第n+1位仍然为5
// 此处,采用2.2方式,解决Number.toFixed的问题,又能避免2.1方式中数字超过Infinity的问题
// 情况2,处理方式2.1:如果小数部分第n+1位是5,增加2*10^(-n-1)
if (rexExp.test(xNum.toString())) {
xNum += 2 * (10 ** (-nNum - 1));
}
const str = xNum.toFixed(nNum);
if (!removeTrailingZero) {
return str;
}
// 去除末尾的0
if (/^\d+\.0*$/.test(str)) { // 小数部分全是0
return str.replace(/^(\d+)(\.0*)$/, (_m, s1) => s1);
}
return str.replace(/^(\d+\.\d*[1-9])(0*)$/, (_m, s1) => s1);
};
export { formatPlate, subStr, hidePhoneCenter, isValidPhone, isValidPlate, isValidAuthCode, roundStr, };
# String subStr
字符串截断,处理时按可见字符长度进行截断 可见字符的含义是指:字母、数字、汉字、表情等均等价于一个字符
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| str | String | 原始字符串 | --- | 否 |
| maxLen | Number | 字符串截断后的最大长度 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
String | 截断后的字符串;如果确实进行了截断,在最后面加'...'; |
# String hidePhoneCenter
手机号处理,隐藏中间部分位数 隐藏规则:假设隐藏部分位数后,手机号由ABC构成,其中A、C是可见部分,B是隐藏部分
- 隐藏部分(B)占手机号总位数的1/3,且隐藏部分的位数向上取整
- 不被隐藏的部分(A、C)要均匀分布在隐藏部分(B)的两侧,即尽量使AC等长
- 如果不隐藏部分(A、C)长度无法等长,则使C多显示一位
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| phone | String | 手机号 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
String | 隐藏后的手机号 |
# String formatPlate
格式化车牌 例如:京A12345 -> 京A·12345
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| plate | String | 车牌号 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
String | 格式化后的车牌号 |
# Boolean isValidPhone
检查手机号是否合法
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| phone | String | 手机号 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Boolean | 手机号是否合法 |
# Boolean isValidAuthCode
检查验证码是否合法
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| code | String | 验证码 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Boolean | 验证码是否合法 |
# Boolean isValidPlate
检查车牌是否合法
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| plate | String | 车牌 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Boolean | 车牌是否合法 |
# string roundStr
四舍五入,并返回格式化的字符串 支持保留n位小数,n>=0,如 roundStr(1.325, 2)=1.33 支持格式化字符串时取出末尾的0,如roundStr(1.109, 2, true)=1.1
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| x | any | 原数字, 如果n不是合法数字或者无法转换为合法数字,roundStr结果返回'' | --- | 否 |
| n | any | 保留几位小数,默认0 | --- | 否 |
| removeTrailingZero | boolean | 是否移除字符串末尾的无效数字0 |
- 如果n不是合法数字或者无法转换为合法数字,roundStr结果返回''
- 如果n小于0,roundStr结果返回''
- 如果n的值包含小数部分,roundStr处理时只关注n的整数部分值 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
string | 返回四舍五入后的字符串,异常情况下返回空字符串'' |
# syncfnmanager
本文件负责对小程序调用wx同步方法的管理
点击查看源码
/**
* @desc: 本文件负责对小程序调用wx同步方法的管理
*/
let systemInfo = null; // 系统信息。
let launchOptions = null; // 启动参数
let accountInfo = null; // 小程序账号信息
/**
* 获取系统信息。同wx.getSystemInfoSync
* @returns {Object} 系统信息
*/
const getSystemInfoSync = () => {
if (systemInfo) {
return systemInfo;
}
try {
systemInfo = wx.getSystemInfoSync();
return systemInfo;
}
catch (_) {
return {};
}
};
/**
* 重置系统信息,仅用于单元测试
* @returns {void}
*/
const resetSystemInfoSync = () => {
systemInfo = null;
};
/**
* 获取启动参数。同wx.getLaunchOptionsSync
* @returns {Object} 启动参数
*/
const getLaunchOptionsSync = () => {
if (launchOptions) {
return launchOptions;
}
try {
launchOptions = wx.getLaunchOptionsSync();
return launchOptions;
}
catch (_) {
return {};
}
};
/**
* 获取客户端平台。同wx.getSystemInfoSync().platform
* @returns {String} 平台名称
*/
const getPlatform = () => {
const UNKNOWN = 'unknown';
try {
const systemInfo = getSystemInfoSync();
const { platform } = systemInfo;
return platform || UNKNOWN;
}
catch (_) {
return UNKNOWN;
}
};
/**
* 获取字符串类型的启动参数, 同wx.getLaunchOptionsSync进行JSON.stringify
* @returns {String} 序列化的参数字符串
*/
const getLaunchParamOfString = () => {
try {
const options = getLaunchOptionsSync() || {};
return JSON.stringify(options);
}
catch (_) {
return '';
}
};
/**
* @description 获取小程序账号信息
* @returns {Object} 小程序账号信息,返回内容同wx.getAccountInfoSync()
*/
export const getAccountInfoSync = () => {
if (accountInfo) {
return accountInfo;
}
try {
accountInfo = wx.getAccountInfoSync();
return accountInfo;
}
catch (_) {
return {};
}
};
const obj = {
getPlatform,
getSystemInfoSync,
getLaunchOptionsSync,
getLaunchParamOfString,
resetSystemInfoSync,
getAccountInfoSync,
};
export default obj;
# systemInfo
# Object getAccountInfoSync
获取小程序账号信息
返回值
| 类型 | 说明 |
|---|---|
Object | 小程序账号信息,返回内容同wx.getAccountInfoSync() |
# Object getSystemInfoSync
获取系统信息。同wx.getSystemInfoSync
返回值
| 类型 | 说明 |
|---|---|
Object | 系统信息 |
# void resetSystemInfoSync
重置系统信息,仅用于单元测试
返回值
| 类型 | 说明 |
|---|---|
void |
# Object getLaunchOptionsSync
获取启动参数。同wx.getLaunchOptionsSync
返回值
| 类型 | 说明 |
|---|---|
Object | 启动参数 |
# String getPlatform
获取客户端平台。同wx.getSystemInfoSync().platform
返回值
| 类型 | 说明 |
|---|---|
String | 平台名称 |
# String getLaunchParamOfString
获取字符串类型的启动参数, 同wx.getLaunchOptionsSync进行JSON.stringify
返回值
| 类型 | 说明 |
|---|---|
String | 序列化的参数字符串 |
# timeUtils
时间处理相关函数
点击查看源码
/**
* @desc: 时间处理相关函数
* @author:2017-07-26 @davislu modify.
*/
import { round } from './numUtils';
import { roundStr } from './stringUtils';
/**
* 将时间段进行聚合计算,得出不同时间单位的计数值,例如:3601秒 -> { hour: 1, minute: 0, second: 1 },即:1小时0分1秒
* @param {Number} seconds 时间段的总秒数
* @param {String} maxUnit 最大计数单位:second-秒,minute-分,hour-小时,day-天,month-月,year-年;默认 year
* @param {String} minUnit 最小计数单位:second-秒,minute-分,hour-小时,day-天,month-月,year-年;默认 second
* @param {Number} decimal 有不足最小单位数值的情况下,保留几位小数;默认 2
* @returns {Object} result 以下是各计数单位下的数值:
* @returns {Number} result.year - 年; 当maxUnit < 'year' 时,此字段永远为0;此字段为-1时,表示 seconds 参数非法 或 minUnit > maxUnit等异常情况
* @returns {Number} result.month - 月; 当maxUnit < 'month' 时,此字段永远为0;
* @returns {Number} result.day - 天; 当maxUnit < 'day' 时,此字段永远为0;
* @returns {Number} result.hour - 小时;当maxUnit < 'hour' 时,此字段永远为0;
* @returns {Number} result.minute - 分钟;当maxUnit < 'minute' 时,此字段永远为0;
* @returns {Number} result.second - 秒;
* @returns {Number} result.decimal - 不足最小单位的部分,具体含义与 minUnit 参数相关;例如 minUnit = day 时,此字段表示不足1天的部分
*/
const groupTimeDuration = (seconds, maxUnit = 'year', minUnit = 'second', decimal = 2) => {
// 时间参数(seconds)检查
const sec = typeof seconds === 'number' ? seconds : parseFloat(seconds);
if (isNaN(sec))
return { year: -1, month: 0, day: 0, hour: 0, minute: 0, second: 0, decimal: 0 };
// 各时间单位对应的秒数
const unitSeconds = { year: 31536000, month: 2592000, day: 86400, hour: 3600, minute: 60, second: 1 };
// 时间单位参数(minUnit,maxUnit)检查
const minSeconds = unitSeconds[minUnit] || unitSeconds.second;
const maxSeconds = unitSeconds[maxUnit] || unitSeconds.year;
if (maxSeconds < minSeconds)
return { year: -1, month: 0, day: 0, hour: 0, minute: 0, second: 0, decimal: 0 };
// 截取在 minUnit maxUnit 之间的计数单位,并按从大到小排序
const allUnitKeys = Object.keys(unitSeconds);
const groupUnits = allUnitKeys
.filter(unit => unitSeconds[unit] >= minSeconds && unitSeconds[unit] <= maxSeconds) // 过滤超出范围的单位
.sort((a, b) => unitSeconds[b] - unitSeconds[a]) // 排序
.map(unit => ({ name: unit, seconds: unitSeconds[unit] })); // 转换为 { name, seconds } 的形式,方便后面用
// 各返回字段初始赋值
const result = { decimal: 0 };
allUnitKeys.forEach(unit => result[unit] = 0);
// 从大到小逐个计算赋值
// const periodArr = [];
let restSeconds = sec; // 剩余待统计描述
groupUnits.forEach((unit) => {
const curUnitNum = Math.floor(restSeconds / unit.seconds); // 高于当前计数单位的数值
result[unit.name] = curUnitNum; // 例如 { year: 1 }
restSeconds %= unit.seconds;
if (unit.seconds === minSeconds)
result.decimal = round(restSeconds / minSeconds, decimal);
});
return result;
};
/**
* 将时间段进行聚合计算并格式化,得出方便人阅读的文案,例如:3601秒 -> 1小时0分1秒
* @param {Number} seconds 时间段的总秒数
* @param {String} maxUnit 最大计数单位:second-秒,minute-分,hour-小时,day-天,month-月,year-年;默认 year
* @param {String} minUnit 最小计数单位:second-秒,minute-分,hour-小时,day-天,month-月,year-年;默认 second
* @param {Number} decimal 有不足最小单位数值的情况下,保留几位小数;默认 2
* @returns {String}
*/
const formatTimeDuration = (seconds, maxUnit = 'year', minUnit = 'second', decimal = 2) => {
const result = groupTimeDuration(seconds, maxUnit, minUnit, decimal);
if (result.year === -1)
return ''; // seconds 不合法 或 maxUnit < minUnit
const unitWording = { year: '年', month: '月', day: '天', hour: '小时', minute: '分钟', second: '秒' };
const periodArr = [];
['year', 'month', 'day', 'hour', 'minute', 'second'].forEach((unit) => {
if (unit !== minUnit) {
if (result[unit] > 0)
periodArr.push(`${result[unit]}${unitWording[unit]}`); // 如2小时
}
else { // 当前unit是最小单位,处理不足最小单位的部分
const min = round(result[unit] + result.decimal, decimal);
if (min > 0)
periodArr.push(`${roundStr(min, 2, true)}${unitWording[unit]}`); // 如1.2,0.2
else if (periodArr.length === 0)
periodArr.push(`1${unitWording[unit]}内`); // 1 unit内
}
});
return periodArr.join('');
};
/**
* @function
* @description 格式化时间,>24小时的显示 【x天】;<1小时的显示 【x分钟】;<24小时&>1小时的显示 【x小时y分钟】
* @param {Number} seconds 秒数
* @returns {String} 格式化的时间
*/
const formatTime = (seconds) => {
if (typeof seconds !== 'number')
return seconds;
const { day, hour, minute, decimal } = groupTimeDuration(seconds, 'day', 'minute', 2);
// >24小时的显示 【x天】
if (day > 0)
return `${day}天`;
// <1小时的显示 【x分钟】 ,x取整数上限,最低为1分钟。
if (hour === 0)
return `${Math.ceil(minute + decimal)}分钟`;
// <24小时&>1小时的显示 【x小时y分钟】 ,分钟取整数上限
let cost = `${hour}小时`;
const min = Math.ceil(minute + decimal);
if (min > 0)
cost += `${min}分钟`;
return cost;
};
/**
* @function
* @description 将秒数格式化: 秒数多于1天显示【x天y小时z分钟】;秒数小于1小时【x分钟】;秒数介于1天和1小时之间显示【x小时y分钟】
* @param {Number} seconds 秒数
* @returns {String} 格式化后的文案
*/
const formatTimeWithDetails = (seconds) => {
// 非Number类型,直接返回,不进行处理
if (typeof seconds !== 'number')
return seconds;
// 参数为NaN类型,直接抛出异常
if (isNaN(seconds))
throw new Error(`formatTimeWithDetails方法的参数seconds必须时一个非NaN数字,现在的值为${seconds}`);
const { day, hour, minute, decimal } = groupTimeDuration(seconds, 'day', 'minute', 2);
let cost = '';
const min = Math.ceil(minute + decimal);
if (day > 0) { // 秒数多于1天
cost = `${day}天${hour}小时`;
// 这里大概率是有bug的,从测试用例里能看出:formatTime(86400) => 1天0小时0分钟
// 但为了不改变方法原有表现,先这样吧
if (min > 0 || hour === 0)
cost += `${min}分钟`;
}
else if (hour === 0) { // 秒数小于1小时
cost = `${min}分钟`;
}
else { // 秒数介于1天和1分钟之间
cost = `${hour}小时`;
if (min > 0)
cost += `${min}分钟`;
}
return cost;
};
/**
* @function
* @description 对原有时间字符串进行格式化
* @param {String} str - 原字符串
* @param {String} dateSeprator - 日期分隔符
* @param {Boolean} keepSeconds - 是否保留秒数
* @returns {String} 格式化后的文案
*/
const formatTimeStr = (str = '', dateSeprator = '.', keepSeconds = false) => {
if (typeof str !== 'string' || str === '') {
return '';
}
let s = str.replace(/-/g, dateSeprator).replace(/:/g, ':');
// 不保留秒的时候,如果有两个冒号,截取第二个冒号之前的部分
if (!keepSeconds && /[^:]*:[^:]*:/.test(s)) {
const firstIndex = s.indexOf(':');
const secondIndex = s.indexOf(':', firstIndex + 1);
if (secondIndex > -1) {
s = s.substring(0, secondIndex);
}
}
return s;
};
/**
* 格式化时间对象
* @param {Date|Number} date Date对象
* @param {String} fmt 目标格式,如:yyyy年MM月dd日,MM/dd/yyyy,yyyyMMdd,yyyy-MM-dd hh:mm:ss等
* @returns {String} 格式化结果;异常情况下返回空串
*/
const formatDateTime = (date, fmt = 'yyyy-MM-dd hh:mm:ss') => {
const dateObj = date instanceof Date ? date : new Date(date);
if (isNaN(dateObj.getTime()))
return '';
const obj = {
'M+': dateObj.getMonth() + 1,
'd+': dateObj.getDate(),
'h+': dateObj.getHours(),
'm+': dateObj.getMinutes(),
's+': dateObj.getSeconds(),
S: dateObj.getMilliseconds(), // 毫秒
};
let dateStr = fmt || 'yyyy-MM-dd hh:mm:ss';
if (/(y+)/.test(dateStr)) {
dateStr = dateStr.replace(RegExp.$1, (`${dateObj.getFullYear()}`).substr(4 - RegExp.$1.length));
}
Object.entries(obj).forEach(([key, value]) => {
if (new RegExp(`(${key})`).test(dateStr)) {
dateStr = dateStr.replace(RegExp.$1, (RegExp.$1.length === 1) ? (value) : (`${value}`.padStart(2, '0')));
}
});
return dateStr;
};
/**
* @description 格式化时间戳为 yyyy-MM-dd, yyyy-MM-dd hh:mm, yyyy-MM-dd hh:mm:ss
* @param {Date} date 日期
* @param {Boolean} withTime 是否带时间
* @param {Boolean} withSeconds 是否带秒数
* @param {String} join 连字符
* @returns {String} 格式化后的字符串,如 2021-03-18 或者 2021-03-18 10:11
*/
const dateToString = (date, withTime = false, withSeconds = false, join = '-') => {
let fmt = `yyyy${join}MM${join}dd`;
if (withTime)
fmt += withSeconds ? ' hh:mm:ss' : ' hh:mm';
return formatDateTime(date || new Date(), fmt);
};
/**
* 将时间字符串转换为Date对象
* @param {String} str 时间字符串,如:2022-03-24 20:02:05
1. 支持对日期完整但时间部分缺失的情况进行处理,如:
2. 2022-03-24 20:02 -> Date(1648123320000)
3. 2022-03-24 20: -> Date(1648123200000)
4. 2022-03-24 20 -> Date(1648123200000)
5. 2022-03-24 -> Date(1648051200000)
6. 小于10的数字可以省略0:
7. 2022-3-24 20:2 -> Date(1648123320000)
8. 支持任意分割符,如:
9. 2022年03月24日 20时02分 -> Date(1648123320000)
10. 2022/03/24 20:02:05 -> Date(1648123325000)
* @returns {Date} 时间对象;转换失败时返回null
*/
const parseDateTime = (str) => {
const arr = new RegExp(/(\d{4})[^\d]+(\d{1,2})[^\d]+(\d{1,2})(.*)/, 'g').exec(str); // 分割日期和时间
if (!(arr && arr.index > -1))
return null; // 日期不合法,转换失败
const timeArr = new RegExp(/[^\d]?(\d{1,2})([^\d]+(\d{1,2})([^\d]+(\d{1,2}))?)?/, 'g').exec(arr[4]); // 分割时间
const [, year = 0, month = 0, day = 0] = arr; // 解析日期
const [, hour = 0, , minute = 0, , second = 0] = timeArr || []; // 解析时间
const date = new Date();
date.setFullYear(year);
// 解决js setMonth bug,在setMonth前先将day设置为1号,保证setMonth结果正确
// https://www.google.com/search?q=js+setmonth+bug&oq=js+setMonth&aqs=chrome.1.69i57j0i512j0i8i30j0i5i30j69i61.4323j0j4&sourceid=chrome&ie=UTF-8
date.setDate(1);
date.setMonth(month - 1);
date.setDate(day);
date.setHours(hour);
date.setMinutes(minute);
date.setSeconds(second);
date.setMilliseconds(0);
return date;
};
export { groupTimeDuration, formatTimeDuration, formatDateTime, formatTime, formatTimeStr, formatTimeWithDetails, dateToString, parseDateTime, };
# Object groupTimeDuration
将时间段进行聚合计算,得出不同时间单位的计数值,例如:3601秒 -> { hour: 1, minute: 0, second: 1 },即:1小时0分1秒
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| seconds | Number | 时间段的总秒数 | --- | 否 |
| maxUnit | String | 最大计数单位:second-秒,minute-分,hour-小时,day-天,month-月,year-年;默认 year | --- | 否 |
| minUnit | String | 最小计数单位:second-秒,minute-分,hour-小时,day-天,month-月,year-年;默认 second | --- | 否 |
| decimal | Number | 有不足最小单位数值的情况下,保留几位小数;默认 2 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Object | result 以下是各计数单位下的数值: |
Number | result.year - 年; 当maxUnit < 'year' 时,此字段永远为0;此字段为-1时,表示 seconds 参数非法 或 minUnit > maxUnit等异常情况 |
Number | result.month - 月; 当maxUnit < 'month' 时,此字段永远为0; |
Number | result.day - 天; 当maxUnit < 'day' 时,此字段永远为0; |
Number | result.hour - 小时;当maxUnit < 'hour' 时,此字段永远为0; |
Number | result.minute - 分钟;当maxUnit < 'minute' 时,此字段永远为0; |
Number | result.second - 秒; |
Number | result.decimal - 不足最小单位的部分,具体含义与 minUnit 参数相关;例如 minUnit = day 时,此字段表示不足1天的部分 |
# String formatTimeDuration
将时间段进行聚合计算并格式化,得出方便人阅读的文案,例如:3601秒 -> 1小时0分1秒
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| seconds | Number | 时间段的总秒数 | --- | 否 |
| maxUnit | String | 最大计数单位:second-秒,minute-分,hour-小时,day-天,month-月,year-年;默认 year | --- | 否 |
| minUnit | String | 最小计数单位:second-秒,minute-分,hour-小时,day-天,month-月,year-年;默认 second | --- | 否 |
| decimal | Number | 有不足最小单位数值的情况下,保留几位小数;默认 2 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
String |
# String formatTime
格式化时间,>24小时的显示 【x天】;<1小时的显示 【x分钟】;<24小时&>1小时的显示 【x小时y分钟】
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| seconds | Number | 秒数 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
String | 格式化的时间 |
# String formatTimeWithDetails
将秒数格式化: 秒数多于1天显示【x天y小时z分钟】;秒数小于1小时【x分钟】;秒数介于1天和1小时之间显示【x小时y分钟】
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| seconds | Number | 秒数 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
String | 格式化后的文案 |
# String formatTimeStr
对原有时间字符串进行格式化
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| str | String | 原字符串 | --- | 否 |
| dateSeprator | String | 日期分隔符 | --- | 否 |
| keepSeconds | Boolean | 是否保留秒数 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
String | 格式化后的文案 |
# String formatDateTime
格式化时间对象
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| date | Date|Number | Date对象 | --- | 否 |
| fmt | String | 目标格式,如:yyyy年MM月dd日,MM/dd/yyyy,yyyyMMdd,yyyy-MM-dd hh:mm:ss等 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
String | 格式化结果;异常情况下返回空串 |
# String dateToString
格式化时间戳为 yyyy-MM-dd, yyyy-MM-dd hh:mm, yyyy-MM-dd hh:mm:ss
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| date | Date | 日期 | --- | 否 |
| withTime | Boolean | 是否带时间 | --- | 否 |
| withSeconds | Boolean | 是否带秒数 | --- | 否 |
| join | String | 连字符 | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
String | 格式化后的字符串,如 2021-03-18 或者 2021-03-18 10:11 |
# Date parseDateTime
将时间字符串转换为Date对象
参数
| 字段名 | 类型 | 说明 | 默认值 | 是否可选 |
|---|---|---|---|---|
| str | String | 时间字符串,如:2022-03-24 20:02:05 |
- 支持对日期完整但时间部分缺失的情况进行处理,如:
- 2022-03-24 20:02 -> Date(1648123320000)
- 2022-03-24 20: -> Date(1648123200000)
- 2022-03-24 20 -> Date(1648123200000)
- 2022-03-24 -> Date(1648051200000)
- 小于10的数字可以省略0:
- 2022-3-24 20:2 -> Date(1648123320000)
- 支持任意分割符,如:
- 2022年03月24日 20时02分 -> Date(1648123320000)
- 2022/03/24 20:02:05 -> Date(1648123325000) | --- | 否 |
返回值
| 类型 | 说明 |
|---|---|
Date | 时间对象;转换失败时返回null |