vimo-dt
Version:
A Vue2.x UI Project For Mobile & HyBrid
867 lines (768 loc) • 23 kB
JavaScript
/**
* @class Platform
* @classdesc **App平台级别**的初始化类
*
*
* ## 说明
*
* 这个类用于从设备中获取平台信息, 比如设备种类/运行平台/设备方向/文字方向等, 以此使得代码适配所有机型. 此外, 还支持平台方法的注册, 使业务与平台解耦.
*
*/
/**
* 结构体定义
*
* @typedef {Object} PlatformConfig
* {
* isEngine?: boolean;
* initialize?: Function;
* isMatch?: Function;
* superset?: string;
* subsets?: string[];
* settings?: any;
* versionParser?: any;
* }
*
* @typedef {Object} PlatformVersion
* {
* str?: string;
* num?: number;
* major?: number;
* minor?: number;
* }
*
* */
import { defaults } from '../util/util'
import PLATFORM_DEFAULT_CONFIGS from './platform-default-configs'
import css from '../util/get-css'
import { isFunction, isObject, isPresent } from '../util/type'
class Platform {
constructor () {
// Ready的promise;
this._readyPromise = new Promise((resolve, reject) => {
this._readyResolve = resolve
this._readyReject = reject
})
this._versions = {} // 当前平台的版本信息列表 PlatformVersion
this._lang = null // string 文字;
this._qp = null // QueryParams [[初始化时]]!!! 的url查询实例 {data:{}};
this._bPlt = null // string 当前的浏览器平台,差不多是设备的类型 navigator.platform , 例如MacIntel;
this._ua = null // string userAgent;
this._default = null // string 如果rootNode不存则使用默认的配置
this._platforms = [] // : string[] = []; 当前平台的key 例如: "mobile/ios/mobileweb"
this._registry = null // {[name:string] : PlatformConfig}; platform-registry中的config列表->登记处
this._pW = 0 // Portrait模式的设备Width
this._pH = 0 // Portrait模式的设备Height
this._lW = 0 // Landscape模式的设备Width
this._lH = 0 // Landscape模式的设备Height
this._isPortrait = null // boolean = null 横屏还是竖屏 Portrait=竖屏;
this._nt = null // 记录网络类型
this._networkChangeCallbacks = []
this.css = {
transform: null,
transition: null,
transitionDuration: null,
transitionDelay: null,
transitionTimingFn: null,
transitionStart: null,
transitionEnd: null,
transformOrigin: null,
animationDelay: null
}
}
/**
* 判断当前平台是否匹配
* 目前支持的平台判断有: mobile/ios/android/wechat/alipay/dingtalk/qq
* @param {string} platformName - 平台名称
* @return {boolean}
*/
is (platformName) {
return this._platforms.indexOf(platformName) > -1
}
/**
* 获取当前的平台信息, 大类为: 设备别(mobile)/操作系统(ios/andoid)/hybrid平台(wechat/alipay/..)
* 例如在ios上的微信, 则返回: ['mobile','ios','wechat']
* @returns {array} 平台的类别的数组
*/
platforms () {
return this._platforms
}
/**
* 返回当前平台的全部版本信息
* @returns {object}
*/
versions () {
return this._versions
}
/**
* 返回当前平台有值的版本信息
* @return {PlatformVersion}
* @private
*/
version () {
for (var platformName in this._versions) {
if (this._versions[platformName]) {
return this._versions[platformName]
}
}
return {}
}
// Ready相关
// **********************************************
/**
* 当平台准备完毕触发promise的resolve方法, 可以在业务中像下面这样使用.
* 例如微信, 当ready之后, 即可获取及配置config信息和bridge方法注册, 因为当前JSSdk都已加载完毕.
*
* ```
* this.$platform.ready().then((data) => {
* console.debug(data)
* })
* ```
* @returns {promise}
*/
ready () {
return this._readyPromise
}
/**
* 当平台准备完毕的时执行resolve方法, 这段函数执行后, 执行ready
* @param {string} readySource - resolve中传入的数据
* @private
*/
triggerReady (readySource) {
this._readyResolve(readySource)
}
/**
* 平台初始化失败的回调
* @param {string} rejectSource - reject中传入的数据
* @private
*/
triggerFail (rejectSource) {
this._readyReject(rejectSource)
}
/**
* 这个函数是默认函数, 当平台没有initialize函数改写prepareReady的时候, 将使用这个.
*
* 平台在initialize函数中改写prepareReady是为了在App运行环境中做一些处理,
* 比如必须的资源下载/请求后台给地址签名等. 等完毕后, 手动触发triggerReady函数
*
* 这部分不应该和业务相关, 比如获取用户数据才进入app, 这部分应该业务逻辑中处理
*
* !!!! platform配置中的initialize 只进行平台配置及注册签名等的代码, 而不进行和具体业务相关的代码!!!!
* !!!! platform配置中的initialize 只进行平台配置及注册签名等的代码, 而不进行和具体业务相关的代码!!!!
* !!!! platform配置中的initialize 只进行平台配置及注册签名等的代码, 而不进行和具体业务相关的代码!!!!
*
* @private
*/
beforeReady () {
this.triggerReady('H5 Initialization Process!')
}
// 平台方法
// **********************************************
/**
* 退出app
* @hidden
*/
exitApp () {
console.error('H5未实现此方法, 请检查调用!')
}
/**
* 设置网络类型
* @private
* */
setNetworkType (networkType) {
this._nt = networkType
}
/**
* 获取网络类型, 如果是在平台, 则使用平台方法
* */
networkType () {
return this._nt
}
/**
* 当网络环境发生变化时触发注册函数
* @param {Function} fn - 注册函数, 回调参数返回网络类型
* */
onNetworkChange (fn) {
if (
isPresent(fn) &&
isFunction(fn) &&
this._networkChangeCallbacks.indexOf(fn) === -1
) {
this._networkChangeCallbacks.push(fn)
}
}
/**
* @private
*/
setCssProps () {
this.css = css
return this.css
}
/**
* 获取UA
* @return {string}
*/
userAgent () {
return this._ua || ''
}
/**
* @param {string} userAgent
* @private
*/
setUserAgent (userAgent) {
this._ua = userAgent
}
/**
* Get the query string parameter
* @private
*/
getQueryParam (key) {
return this._qp.get(key)
}
/**
* @param {QueryParams} queryParams
* @private
*/
setQueryParams (queryParams) {
this._qp = queryParams
}
/**
* 获取浏览器信息
* @return {string}
*/
navigatorPlatform () {
return this._bPlt || ''
}
/**
* 设置浏览器平台的名称
* @param {string} navigatorPlatform
* @private
*/
setNavigatorPlatform (navigatorPlatform) {
this._bPlt = navigatorPlatform
}
/**
* 在html标签中设置app语言类型
* @param {string} language Examples: `en-US`, `en-GB`, `ar`, `de`, `zh`, `es-MX`
* @param {boolean} updateDocument
* @private
*/
setLang (language, updateDocument) {
this._lang = language
if (updateDocument) {
document.documentElement.setAttribute('lang', language)
}
}
/**
* 返回app的语言类型
* @returns {string}
*/
lang () {
return this._lang
}
// 屏幕尺寸及方向
// **********************************************
/**
* 获取当前viewport的宽度
* @return {number}
*/
width () {
this._calcDim()
return this._isPortrait ? this._pW : this._lW
}
/**
* 获取当前viewport的高度
* @return {number}
*/
height () {
this._calcDim()
return this._isPortrait ? this._pH : this._lH
}
/**
* 判断是否为纵向
* (landscape是横向,portrait是纵向)
* @return {boolean}
*/
isPortrait () {
this._calcDim()
return this._isPortrait
}
/**
* 判断是否为横向
* (landscape是横向,portrait是纵向)
* @return {boolean}
*/
isLandscape () {
return !this.isPortrait()
}
/**
* @private
*/
_calcDim () {
if (window.screen.width > 0 && window.screen.height > 0) {
if (window['innerWidth'] < window['innerHeight']) {
// the device is in portrait
if (this._pW <= window['innerWidth']) {
// console.debug('setting _isPortrait to true');
this._isPortrait = true
this._pW = window['innerWidth']
}
if (this._pH <= window['innerHeight']) {
// console.debug('setting _isPortrait to true');
this._isPortrait = true
this._pH = window['innerHeight']
}
} else {
if (this._lW > window['innerWidth']) {
// Special case: keyboard is open and device is in portrait
// console.debug('setting _isPortrait to true while keyboard is open and device is portrait');
this._isPortrait = true
}
// the device is in landscape
if (this._lW <= window['innerWidth']) {
// console.debug('setting _isPortrait to false');
this._isPortrait = false
this._lW = window['innerWidth']
}
if (this._lH <= window['innerHeight']) {
// console.debug('setting _isPortrait to false');
this._isPortrait = false
this._lH = window['innerHeight']
}
}
}
}
// Platform Registry
// **********************************************
/**
* 设置config的 登记列表, platform-registry中的config就登记在这个位置
* @param {PlatformConfig} platformConfigs {[key: string]: PlatformConfig}
* @private
*/
setPlatformConfigs (platformConfigs) {
this._registry = platformConfigs || {}
}
/**
* @param {string} platformName
* @return {PlatformConfig} platformConfigs - {[key: string]: PlatformConfig}
* @private
*/
getPlatformConfig (platformName) {
return this._registry[platformName] || {}
}
/**
* 获得当前的登记列表
* @private
*/
registry () {
return this._registry
}
/**
* 设置默认的登记config名称
* @param {string}
* @private
*/
setDefault (platformName) {
this._default = platformName
}
/**
* 判断 字符串是否在 长字符串中
* @param {string} queryValue ios;md;android;iphone
* @param {string} queryTestValue ios
* @return {boolean}
* @private
*/
testQuery (queryValue, queryTestValue) {
const valueSplit = queryValue.toLowerCase().split(';')
return valueSplit.indexOf(queryTestValue) > -1
}
/**
* 判断是否匹配当前的浏览器平台
* @param {RegExp} navigatorPlatformExpression
* @private
*/
testNavigatorPlatform (navigatorPlatformExpression) {
const rgx = new RegExp(navigatorPlatformExpression, 'i')
return rgx.test(this._bPlt)
}
/**
* 判断是否匹配当前的userAgent
* @param {RegExp} userAgentExpression
* @private
*/
matchUserAgentVersion (userAgentExpression) {
if (this._ua && userAgentExpression) {
const val = this._ua.match(userAgentExpression)
if (val) {
return {
major: val[1],
minor: val[2],
patch: val[3]
}
}
}
}
/**
* 判断是否匹配当前的userAgent
* @param {RegExp} expression
* @private
*/
testUserAgent (expression) {
if (this._ua) {
return this._ua.indexOf(expression) >= 0
}
return false
}
/**
* this._qp为地址栏参数查询对象,
* 1. 优先提取地址栏的platform值,判断是否匹配
* 2. 否则由useragent判断userAgentAtLeastHas中是否有而userAgentMustNotHave中没有
*
* @param {string} queryStringName - 通过地址栏的platform参数查询名称
* @param {array} userAgentAtLeastHas - 在useragent中查找的字段
* @param {array} userAgentMustNotHave - 在useragent中排除的字段
* @return {boolean}
* @private
*/
isPlatformMatch (queryStringName,
userAgentAtLeastHas,
userAgentMustNotHave = []) {
// platform可以取值的参数: ios/android/iphone/
const queryValue = this._qp.get('platform')
if (queryValue) {
return this.testQuery(queryValue, queryStringName)
}
userAgentAtLeastHas = userAgentAtLeastHas || [queryStringName]
const userAgent = this._ua.toLowerCase()
for (var i = 0; i < userAgentAtLeastHas.length; i++) {
if (userAgent.indexOf(userAgentAtLeastHas[i]) > -1) {
for (var j = 0; j < userAgentMustNotHave.length; j++) {
if (userAgent.indexOf(userAgentMustNotHave[j]) > -1) {
return false
}
}
return true
}
}
return false
}
/** @private */
init () {
// 计算屏幕尺寸
this._calcDim()
this._platforms = []
let rootPlatformNode // 根节点Node;
let enginePlatformNode // engine节点Node;
// figure out the most specific platform and active engine
let tmpPlatform // 临时缓存Node;
// 找到rootPlatformNode
// 找到enginePlatformNode
for (let platformName in this._registry) {
// 将platformName对用的配置转化为Node对象, 返回rootNode
tmpPlatform = this.matchPlatform(platformName)
if (tmpPlatform) {
// we found a platform match!
// check if its more specific than the one we already have
if (tmpPlatform.isEngine) {
// because it matched then this should be the active engine
// you cannot have more than one active engine
enginePlatformNode = tmpPlatform
} else if (
!rootPlatformNode ||
tmpPlatform.depth > rootPlatformNode.depth
) {
// only find the root node for platforms that are not engines
// set this node as the root since we either don't already
// have one, or this one is more specific that the current one
rootPlatformNode = tmpPlatform
}
}
}
// 如果没找到根rootNode则使用默认的_default
if (!rootPlatformNode) {
rootPlatformNode = new PlatformNode(this._registry, this._default)
}
// build a Platform instance filled with the
// hierarchy of active platforms and settings
if (rootPlatformNode) {
// check if we found an engine node (cordova/node-webkit/etc)
// 如果是在壳子中,则将壳子的节点放置为rootNode
if (enginePlatformNode) {
// add the engine to the first in the platform hierarchy
// the original rootPlatformNode now becomes a child
// of the engineNode, which is not the new root
enginePlatformNode.child = rootPlatformNode
rootPlatformNode.parent = enginePlatformNode
rootPlatformNode = enginePlatformNode
}
// 从根节点开始, 插入子Node
let platformNode = rootPlatformNode
while (platformNode) {
insertSuperset(this._registry, platformNode)
platformNode = platformNode.child
}
// make sure the root noot is actually the root
// in case a node was inserted before the root
platformNode = rootPlatformNode.parent
while (platformNode) {
rootPlatformNode = platformNode
platformNode = platformNode.parent
}
platformNode = rootPlatformNode
// 在这里初始化平台
while (platformNode) {
platformNode.beforeInitialize(this)
platformNode.initialize(this)
// 设置当前激活的平台信息, 最后一个是最重要的
this._platforms.push(platformNode.name)
// get the platforms version if a version parser was provided
this._versions[platformNode.name] = platformNode.version(this)
// go to the next platform child
platformNode = platformNode.child
}
}
}
/**
* 传入的名称匹配当前的平台,如果匹配则返回rootNode
* @param {string} platformName
* @return {PlatformNode}
* @private
*/
matchPlatform (platformName) {
// build a PlatformNode and assign config data to it
// use it's getRoot method to build up its hierarchy
// depending on which platforms match
let platformNode = new PlatformNode(this._registry, platformName)
let rootNode = platformNode.getRoot(this)
if (rootNode) {
rootNode.depth = 0
let childPlatform = rootNode.child
while (childPlatform) {
rootNode.depth++
childPlatform = childPlatform.child
}
}
return rootNode
}
}
/**
* @private
* */
class PlatformNode {
/**
* 读取c中的配置信息
* @param {PlatformConfig} registry
* @param {string} platformName
* */
constructor (registry, platformName) {
this.parent = null // 父节点
this.child = null // 子节点
this.depth = null // number 当前节点的深度;
this.registry = registry
this.c = registry[platformName] // platform-registry配置中的平台设置;
this.name = platformName // 当前节点的名称;
this.isEngine = this.c && this.c.isEngine // boolean; 是否是在壳子中
}
// 获取settings配置
settings () {
return this.c.settings || {}
}
// 获取父集的名称
superset () {
return this.c.superset
}
/**
* 执行配置的匹配函数, 判断现在的node是否匹配当前运行平台
* @param {Platform} p
* @return {boolean}
* */
isMatch (p) {
return this.c.isMatch && this.c.isMatch(p)
}
/**
* 初始化之前执行的函数
* @param {Platform} platform
* */
beforeInitialize (platform) {
this.c.beforeInitialize && this.c.beforeInitialize(platform)
}
/**
* 执行配置的初始化函数, 传入当前平台的参数
* @param {Platform} platform
* */
initialize (platform) {
this.c.initialize && this.c.initialize(platform)
}
/**
* 传入当前的平台信息, 获得版本信息
* @param {Platform} p
* @return {PlatformVersion}
* */
version (p) {
if (this.c.versionParser) {
const v = this.c.versionParser(p)
if (v) {
if (!v.major) v.major = '0'
if (!v.minor) v.minor = '0'
if (!v.patch) v.patch = '0'
const str = v.major + '.' + v.minor + (v.patch ? '.' + v.patch : '')
return {
str: str,
num: parseFloat(str),
major: parseInt(v.major, 10),
minor: parseInt(v.minor, 10),
patch: parseInt(v.patch, 10)
}
}
}
}
/**
* 获得当前node的根node
* @param {Platform} p
* @return {PlatformNode}
* */
getRoot (p) {
// 判断当前平台是否和当前的Node匹配
if (this.isMatch(p)) {
// 获得 父集名称 列表
let parents = this.getSubsetParents(this.name)
if (!parents.length) {
return this
}
let platformNode = null // PlatformNode
let rootPlatformNode = null // PlatformNode
for (let i = 0; i < parents.length; i++) {
platformNode = new PlatformNode(this.registry, parents[i])
platformNode.child = this
rootPlatformNode = platformNode.getRoot(p)
if (rootPlatformNode) {
this.parent = platformNode
return rootPlatformNode
}
}
}
return null
}
/**
* 获取 子集名称对应的父集列表
* @param {string} subsetPlatformName
* @return {array}
* */
getSubsetParents (subsetPlatformName) {
const parentPlatformNames = []
let platform = null // PlatformConfig
for (let platformName in this.registry) {
platform = this.registry[platformName]
if (
platform.subsets &&
platform.subsets.indexOf(subsetPlatformName) > -1
) {
parentPlatformNames.push(platformName)
}
}
return parentPlatformNames
}
}
/**
* @param {any} registry
* @param {PlatformNode} platformNode
* @private
* */
function insertSuperset (registry, platformNode) {
let supersetPlatformName = platformNode.superset()
if (supersetPlatformName) {
// add a platform in between two exist platforms
// so we can build the correct hierarchy of active platforms
let supersetPlatform = new PlatformNode(registry, supersetPlatformName)
supersetPlatform.parent = platformNode.parent
supersetPlatform.child = platformNode
if (supersetPlatform.parent) {
supersetPlatform.parent.child = supersetPlatform
}
platformNode.parent = supersetPlatform
}
}
/**
* 获取url参数的类
* @example
* import {QueryParams} from './platform/query-params'
* let a = (new QueryParams()).queryParams(location.href)
* console.log(a.data);
* => Object {a: "1", b: "3"}
* @private
*/
export class QueryParams {
/**
* @param {string} url
* */
constructor (url = window.location.href) {
this.data = {} // {[key: string]: any}
this.parseUrl(url)
}
/**
* @param {string} key
* */
get (key) {
return this.data[key.toLowerCase()]
}
/**
* @param {string} url
* */
parseUrl (url) {
if (url) {
const startIndex = url.indexOf('?')
if (startIndex > -1) {
const queries = url.slice(startIndex + 1).split('&')
for (var i = 0; i < queries.length; i++) {
if (queries[i].indexOf('=') > 0) {
var split = queries[i].split('=')
if (split.length > 1) {
this.data[split[0].toLowerCase()] = split[1].split('#')[0]
}
}
}
}
}
return this.data
}
}
/**
* @param {object} config - 用户在外面定义的平台配置, 需要和默认配置整合
* @private
* */
export function setupPlatform (config = {}) {
const p = new Platform()
let _finalConf = PLATFORM_DEFAULT_CONFIGS
for (let outerKey in config) {
if (_finalConf[outerKey] && isObject(_finalConf[outerKey])) {
let _cusConf = config[outerKey]
let _defConf = _finalConf[outerKey]
for (let innerKey in _cusConf) {
let _tmp = {}
_tmp = defaults(_cusConf[innerKey], _defConf[innerKey])
_defConf[innerKey] = _tmp
}
} else {
_finalConf[outerKey] = config[outerKey]
}
}
// 更新 IOS/Android下的subsets属性, 可能存在subsets中未定义的平台
let keys = Object.keys(_finalConf).filter(item => {
return item !== 'mobile' && item !== 'android' && item !== 'ios'
})
_finalConf.android.subsets = keys
_finalConf.ios.subsets = keys
p.setDefault('core')
p.setPlatformConfigs(_finalConf)
p.setQueryParams(new QueryParams())
!p.navigatorPlatform() && p.setNavigatorPlatform(window.navigator.platform)
!p.userAgent() && p.setUserAgent(window.navigator.userAgent)
!p.lang() && p.setLang('zh-CN', true)
// 设置css类型
p.setCssProps()
p.init()
// 触发ready, 一般情况下是dom ready,
// 如果平台改写了prepareReady方法,
// 则执行平台对应的ready处理
p.beforeReady()
return p
}