You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1895 lines
46 KiB
1895 lines
46 KiB
import Vue from 'vue';
|
|
|
|
const _toString = Object.prototype.toString;
|
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
|
|
function isFn (fn) {
|
|
return typeof fn === 'function'
|
|
}
|
|
|
|
function isStr (str) {
|
|
return typeof str === 'string'
|
|
}
|
|
|
|
function isPlainObject (obj) {
|
|
return _toString.call(obj) === '[object Object]'
|
|
}
|
|
|
|
function hasOwn (obj, key) {
|
|
return hasOwnProperty.call(obj, key)
|
|
}
|
|
|
|
function noop () {}
|
|
|
|
/**
|
|
* Create a cached version of a pure function.
|
|
*/
|
|
function cached (fn) {
|
|
const cache = Object.create(null);
|
|
return function cachedFn (str) {
|
|
const hit = cache[str];
|
|
return hit || (cache[str] = fn(str))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Camelize a hyphen-delimited string.
|
|
*/
|
|
const camelizeRE = /-(\w)/g;
|
|
const camelize = cached((str) => {
|
|
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
|
|
});
|
|
|
|
const HOOKS = [
|
|
'invoke',
|
|
'success',
|
|
'fail',
|
|
'complete',
|
|
'returnValue'
|
|
];
|
|
|
|
const globalInterceptors = {};
|
|
const scopedInterceptors = {};
|
|
|
|
function mergeHook (parentVal, childVal) {
|
|
const res = childVal
|
|
? parentVal
|
|
? parentVal.concat(childVal)
|
|
: Array.isArray(childVal)
|
|
? childVal : [childVal]
|
|
: parentVal;
|
|
return res
|
|
? dedupeHooks(res)
|
|
: res
|
|
}
|
|
|
|
function dedupeHooks (hooks) {
|
|
const res = [];
|
|
for (let i = 0; i < hooks.length; i++) {
|
|
if (res.indexOf(hooks[i]) === -1) {
|
|
res.push(hooks[i]);
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
function removeHook (hooks, hook) {
|
|
const index = hooks.indexOf(hook);
|
|
if (index !== -1) {
|
|
hooks.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
function mergeInterceptorHook (interceptor, option) {
|
|
Object.keys(option).forEach(hook => {
|
|
if (HOOKS.indexOf(hook) !== -1 && isFn(option[hook])) {
|
|
interceptor[hook] = mergeHook(interceptor[hook], option[hook]);
|
|
}
|
|
});
|
|
}
|
|
|
|
function removeInterceptorHook (interceptor, option) {
|
|
if (!interceptor || !option) {
|
|
return
|
|
}
|
|
Object.keys(option).forEach(hook => {
|
|
if (HOOKS.indexOf(hook) !== -1 && isFn(option[hook])) {
|
|
removeHook(interceptor[hook], option[hook]);
|
|
}
|
|
});
|
|
}
|
|
|
|
function addInterceptor (method, option) {
|
|
if (typeof method === 'string' && isPlainObject(option)) {
|
|
mergeInterceptorHook(scopedInterceptors[method] || (scopedInterceptors[method] = {}), option);
|
|
} else if (isPlainObject(method)) {
|
|
mergeInterceptorHook(globalInterceptors, method);
|
|
}
|
|
}
|
|
|
|
function removeInterceptor (method, option) {
|
|
if (typeof method === 'string') {
|
|
if (isPlainObject(option)) {
|
|
removeInterceptorHook(scopedInterceptors[method], option);
|
|
} else {
|
|
delete scopedInterceptors[method];
|
|
}
|
|
} else if (isPlainObject(method)) {
|
|
removeInterceptorHook(globalInterceptors, method);
|
|
}
|
|
}
|
|
|
|
function wrapperHook (hook) {
|
|
return function (data) {
|
|
return hook(data) || data
|
|
}
|
|
}
|
|
|
|
function isPromise (obj) {
|
|
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'
|
|
}
|
|
|
|
function queue (hooks, data) {
|
|
let promise = false;
|
|
for (let i = 0; i < hooks.length; i++) {
|
|
const hook = hooks[i];
|
|
if (promise) {
|
|
promise = Promise.resolve(wrapperHook(hook));
|
|
} else {
|
|
const res = hook(data);
|
|
if (isPromise(res)) {
|
|
promise = Promise.resolve(res);
|
|
}
|
|
if (res === false) {
|
|
return {
|
|
then () {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return promise || {
|
|
then (callback) {
|
|
return callback(data)
|
|
}
|
|
}
|
|
}
|
|
|
|
function wrapperOptions (interceptor, options = {}) {
|
|
['success', 'fail', 'complete'].forEach(name => {
|
|
if (Array.isArray(interceptor[name])) {
|
|
const oldCallback = options[name];
|
|
options[name] = function callbackInterceptor (res) {
|
|
queue(interceptor[name], res).then((res) => {
|
|
/* eslint-disable no-mixed-operators */
|
|
return isFn(oldCallback) && oldCallback(res) || res
|
|
});
|
|
};
|
|
}
|
|
});
|
|
return options
|
|
}
|
|
|
|
function wrapperReturnValue (method, returnValue) {
|
|
const returnValueHooks = [];
|
|
if (Array.isArray(globalInterceptors.returnValue)) {
|
|
returnValueHooks.push(...globalInterceptors.returnValue);
|
|
}
|
|
const interceptor = scopedInterceptors[method];
|
|
if (interceptor && Array.isArray(interceptor.returnValue)) {
|
|
returnValueHooks.push(...interceptor.returnValue);
|
|
}
|
|
returnValueHooks.forEach(hook => {
|
|
returnValue = hook(returnValue) || returnValue;
|
|
});
|
|
return returnValue
|
|
}
|
|
|
|
function getApiInterceptorHooks (method) {
|
|
const interceptor = Object.create(null);
|
|
Object.keys(globalInterceptors).forEach(hook => {
|
|
if (hook !== 'returnValue') {
|
|
interceptor[hook] = globalInterceptors[hook].slice();
|
|
}
|
|
});
|
|
const scopedInterceptor = scopedInterceptors[method];
|
|
if (scopedInterceptor) {
|
|
Object.keys(scopedInterceptor).forEach(hook => {
|
|
if (hook !== 'returnValue') {
|
|
interceptor[hook] = (interceptor[hook] || []).concat(scopedInterceptor[hook]);
|
|
}
|
|
});
|
|
}
|
|
return interceptor
|
|
}
|
|
|
|
function invokeApi (method, api, options, ...params) {
|
|
const interceptor = getApiInterceptorHooks(method);
|
|
if (interceptor && Object.keys(interceptor).length) {
|
|
if (Array.isArray(interceptor.invoke)) {
|
|
const res = queue(interceptor.invoke, options);
|
|
return res.then((options) => {
|
|
return api(wrapperOptions(interceptor, options), ...params)
|
|
})
|
|
} else {
|
|
return api(wrapperOptions(interceptor, options), ...params)
|
|
}
|
|
}
|
|
return api(options, ...params)
|
|
}
|
|
|
|
const promiseInterceptor = {
|
|
returnValue (res) {
|
|
if (!isPromise(res)) {
|
|
return res
|
|
}
|
|
return res.then(res => {
|
|
return res[1]
|
|
}).catch(res => {
|
|
return res[0]
|
|
})
|
|
}
|
|
};
|
|
|
|
const SYNC_API_RE =
|
|
/^\$|sendNativeEvent|restoreGlobal|getCurrentSubNVue|getMenuButtonBoundingClientRect|^report|interceptors|Interceptor$|getSubNVueById|requireNativePlugin|upx2px|hideKeyboard|canIUse|^create|Sync$|Manager$|base64ToArrayBuffer|arrayBufferToBase64/;
|
|
|
|
const CONTEXT_API_RE = /^create|Manager$/;
|
|
|
|
// Context例外情况
|
|
const CONTEXT_API_RE_EXC = ['createBLEConnection'];
|
|
|
|
// 同步例外情况
|
|
const ASYNC_API = ['createBLEConnection'];
|
|
|
|
const CALLBACK_API_RE = /^on|^off/;
|
|
|
|
function isContextApi (name) {
|
|
return CONTEXT_API_RE.test(name) && CONTEXT_API_RE_EXC.indexOf(name) === -1
|
|
}
|
|
function isSyncApi (name) {
|
|
return SYNC_API_RE.test(name) && ASYNC_API.indexOf(name) === -1
|
|
}
|
|
|
|
function isCallbackApi (name) {
|
|
return CALLBACK_API_RE.test(name) && name !== 'onPush'
|
|
}
|
|
|
|
function handlePromise (promise) {
|
|
return promise.then(data => {
|
|
return [null, data]
|
|
})
|
|
.catch(err => [err])
|
|
}
|
|
|
|
function shouldPromise (name) {
|
|
if (
|
|
isContextApi(name) ||
|
|
isSyncApi(name) ||
|
|
isCallbackApi(name)
|
|
) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
/* eslint-disable no-extend-native */
|
|
if (!Promise.prototype.finally) {
|
|
Promise.prototype.finally = function (callback) {
|
|
const promise = this.constructor;
|
|
return this.then(
|
|
value => promise.resolve(callback()).then(() => value),
|
|
reason => promise.resolve(callback()).then(() => {
|
|
throw reason
|
|
})
|
|
)
|
|
};
|
|
}
|
|
|
|
function promisify (name, api) {
|
|
if (!shouldPromise(name)) {
|
|
return api
|
|
}
|
|
return function promiseApi (options = {}, ...params) {
|
|
if (isFn(options.success) || isFn(options.fail) || isFn(options.complete)) {
|
|
return wrapperReturnValue(name, invokeApi(name, api, options, ...params))
|
|
}
|
|
return wrapperReturnValue(name, handlePromise(new Promise((resolve, reject) => {
|
|
invokeApi(name, api, Object.assign({}, options, {
|
|
success: resolve,
|
|
fail: reject
|
|
}), ...params);
|
|
})))
|
|
}
|
|
}
|
|
|
|
const EPS = 1e-4;
|
|
const BASE_DEVICE_WIDTH = 750;
|
|
let isIOS = false;
|
|
let deviceWidth = 0;
|
|
let deviceDPR = 0;
|
|
|
|
function checkDeviceWidth () {
|
|
const {
|
|
platform,
|
|
pixelRatio,
|
|
windowWidth
|
|
} = qa.getSystemInfoSync(); // uni=>qa runtime 编译目标是 uni 对象,内部不允许直接使用 uni
|
|
|
|
deviceWidth = windowWidth;
|
|
deviceDPR = pixelRatio;
|
|
isIOS = platform === 'ios';
|
|
}
|
|
|
|
function upx2px (number, newDeviceWidth) {
|
|
if (deviceWidth === 0) {
|
|
checkDeviceWidth();
|
|
}
|
|
|
|
number = Number(number);
|
|
if (number === 0) {
|
|
return 0
|
|
}
|
|
let result = (number / BASE_DEVICE_WIDTH) * (newDeviceWidth || deviceWidth);
|
|
if (result < 0) {
|
|
result = -result;
|
|
}
|
|
result = Math.floor(result + EPS);
|
|
if (result === 0) {
|
|
if (deviceDPR === 1 || !isIOS) {
|
|
result = 1;
|
|
} else {
|
|
result = 0.5;
|
|
}
|
|
}
|
|
return number < 0 ? -result : result
|
|
}
|
|
|
|
const interceptors = {
|
|
promiseInterceptor
|
|
};
|
|
|
|
var baseApi = /*#__PURE__*/Object.freeze({
|
|
__proto__: null,
|
|
upx2px: upx2px,
|
|
addInterceptor: addInterceptor,
|
|
removeInterceptor: removeInterceptor,
|
|
interceptors: interceptors
|
|
});
|
|
|
|
class EventChannel {
|
|
constructor (id, events) {
|
|
this.id = id;
|
|
this.listener = {};
|
|
this.emitCache = {};
|
|
if (events) {
|
|
Object.keys(events).forEach(name => {
|
|
this.on(name, events[name]);
|
|
});
|
|
}
|
|
}
|
|
|
|
emit (eventName, ...args) {
|
|
const fns = this.listener[eventName];
|
|
if (!fns) {
|
|
return (this.emitCache[eventName] || (this.emitCache[eventName] = [])).push(args)
|
|
}
|
|
fns.forEach(opt => {
|
|
opt.fn.apply(opt.fn, args);
|
|
});
|
|
this.listener[eventName] = fns.filter(opt => opt.type !== 'once');
|
|
}
|
|
|
|
on (eventName, fn) {
|
|
this._addListener(eventName, 'on', fn);
|
|
this._clearCache(eventName);
|
|
}
|
|
|
|
once (eventName, fn) {
|
|
this._addListener(eventName, 'once', fn);
|
|
this._clearCache(eventName);
|
|
}
|
|
|
|
off (eventName, fn) {
|
|
const fns = this.listener[eventName];
|
|
if (!fns) {
|
|
return
|
|
}
|
|
if (fn) {
|
|
for (let i = 0; i < fns.length;) {
|
|
if (fns[i].fn === fn) {
|
|
fns.splice(i, 1);
|
|
i--;
|
|
}
|
|
i++;
|
|
}
|
|
} else {
|
|
delete this.listener[eventName];
|
|
}
|
|
}
|
|
|
|
_clearCache (eventName) {
|
|
const cacheArgs = this.emitCache[eventName];
|
|
if (cacheArgs) {
|
|
for (; cacheArgs.length > 0;) {
|
|
this.emit.apply(this, [eventName].concat(cacheArgs.shift()));
|
|
}
|
|
}
|
|
}
|
|
|
|
_addListener (eventName, type, fn) {
|
|
(this.listener[eventName] || (this.listener[eventName] = [])).push({
|
|
fn,
|
|
type
|
|
});
|
|
}
|
|
}
|
|
|
|
const eventChannels = {};
|
|
|
|
const eventChannelStack = [];
|
|
|
|
let id = 0;
|
|
|
|
function initEventChannel (events, cache = true) {
|
|
id++;
|
|
const eventChannel = new EventChannel(id, events);
|
|
if (cache) {
|
|
eventChannels[id] = eventChannel;
|
|
eventChannelStack.push(eventChannel);
|
|
}
|
|
return eventChannel
|
|
}
|
|
|
|
function getEventChannel (id) {
|
|
if (id) {
|
|
const eventChannel = eventChannels[id];
|
|
delete eventChannels[id];
|
|
return eventChannel
|
|
}
|
|
return eventChannelStack.shift()
|
|
}
|
|
|
|
var navigateTo = {
|
|
args (fromArgs, toArgs) {
|
|
const id = initEventChannel(fromArgs.events).id;
|
|
if (fromArgs.url) {
|
|
fromArgs.url = fromArgs.url + (fromArgs.url.indexOf('?') === -1 ? '?' : '&') + '__id__=' + id;
|
|
}
|
|
},
|
|
returnValue (fromRes, toRes) {
|
|
fromRes.eventChannel = getEventChannel();
|
|
}
|
|
};
|
|
|
|
function findExistsPageIndex (url) {
|
|
const pages = getCurrentPages();
|
|
let len = pages.length;
|
|
while (len--) {
|
|
const page = pages[len];
|
|
if (page.$page && page.$page.fullPath === url) {
|
|
return len
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
var redirectTo = {
|
|
name (fromArgs) {
|
|
if (fromArgs.exists === 'back' && fromArgs.delta) {
|
|
return 'navigateBack'
|
|
}
|
|
return 'redirectTo'
|
|
},
|
|
args (fromArgs) {
|
|
if (fromArgs.exists === 'back' && fromArgs.url) {
|
|
const existsPageIndex = findExistsPageIndex(fromArgs.url);
|
|
if (existsPageIndex !== -1) {
|
|
const delta = getCurrentPages().length - 1 - existsPageIndex;
|
|
if (delta > 0) {
|
|
fromArgs.delta = delta;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var previewImage = {
|
|
args (fromArgs) {
|
|
let currentIndex = parseInt(fromArgs.current);
|
|
if (isNaN(currentIndex)) {
|
|
return
|
|
}
|
|
const urls = fromArgs.urls;
|
|
if (!Array.isArray(urls)) {
|
|
return
|
|
}
|
|
const len = urls.length;
|
|
if (!len) {
|
|
return
|
|
}
|
|
if (currentIndex < 0) {
|
|
currentIndex = 0;
|
|
} else if (currentIndex >= len) {
|
|
currentIndex = len - 1;
|
|
}
|
|
if (currentIndex > 0) {
|
|
fromArgs.current = urls[currentIndex];
|
|
fromArgs.urls = urls.filter(
|
|
(item, index) => index < currentIndex ? item !== urls[currentIndex] : true
|
|
);
|
|
} else {
|
|
fromArgs.current = urls[0];
|
|
}
|
|
return {
|
|
indicator: false,
|
|
loop: false
|
|
}
|
|
}
|
|
};
|
|
|
|
const protocols = {
|
|
navigateTo,
|
|
redirectTo,
|
|
previewImage
|
|
};
|
|
const todos = [
|
|
'preloadPage',
|
|
'unPreloadPage',
|
|
'loadSubPackage'
|
|
];
|
|
const canIUses = [];
|
|
|
|
const CALLBACKS = ['success', 'fail', 'cancel', 'complete'];
|
|
|
|
function processCallback (methodName, method, returnValue) {
|
|
return function (res) {
|
|
return method(processReturnValue(methodName, res, returnValue))
|
|
}
|
|
}
|
|
|
|
function processArgs (methodName, fromArgs, argsOption = {}, returnValue = {}, keepFromArgs = false) {
|
|
if (isPlainObject(fromArgs)) { // 一般 api 的参数解析
|
|
const toArgs = keepFromArgs === true ? fromArgs : {}; // returnValue 为 false 时,说明是格式化返回值,直接在返回值对象上修改赋值
|
|
if (isFn(argsOption)) {
|
|
argsOption = argsOption(fromArgs, toArgs) || {};
|
|
}
|
|
for (const key in fromArgs) {
|
|
if (hasOwn(argsOption, key)) {
|
|
let keyOption = argsOption[key];
|
|
if (isFn(keyOption)) {
|
|
keyOption = keyOption(fromArgs[key], fromArgs, toArgs);
|
|
}
|
|
if (!keyOption) { // 不支持的参数
|
|
console.warn(`快应用(Webview)版 ${methodName}暂不支持${key}`);
|
|
} else if (isStr(keyOption)) { // 重写参数 key
|
|
toArgs[keyOption] = fromArgs[key];
|
|
} else if (isPlainObject(keyOption)) { // {name:newName,value:value}可重新指定参数 key:value
|
|
toArgs[keyOption.name ? keyOption.name : key] = keyOption.value;
|
|
}
|
|
} else if (CALLBACKS.indexOf(key) !== -1) {
|
|
if (isFn(fromArgs[key])) {
|
|
toArgs[key] = processCallback(methodName, fromArgs[key], returnValue);
|
|
}
|
|
} else {
|
|
if (!keepFromArgs) {
|
|
toArgs[key] = fromArgs[key];
|
|
}
|
|
}
|
|
}
|
|
return toArgs
|
|
} else if (isFn(fromArgs)) {
|
|
fromArgs = processCallback(methodName, fromArgs, returnValue);
|
|
}
|
|
return fromArgs
|
|
}
|
|
|
|
function processReturnValue (methodName, res, returnValue, keepReturnValue = false) {
|
|
if (isFn(protocols.returnValue)) { // 处理通用 returnValue
|
|
res = protocols.returnValue(methodName, res);
|
|
}
|
|
return processArgs(methodName, res, returnValue, {}, keepReturnValue)
|
|
}
|
|
|
|
function wrapper (methodName, method) {
|
|
if (hasOwn(protocols, methodName)) {
|
|
const protocol = protocols[methodName];
|
|
if (!protocol) { // 暂不支持的 api
|
|
return function () {
|
|
console.error(`快应用(Webview)版 暂不支持${methodName}`);
|
|
}
|
|
}
|
|
return function (arg1, arg2) { // 目前 api 最多两个参数
|
|
let options = protocol;
|
|
if (isFn(protocol)) {
|
|
options = protocol(arg1);
|
|
}
|
|
|
|
arg1 = processArgs(methodName, arg1, options.args, options.returnValue);
|
|
|
|
const args = [arg1];
|
|
if (typeof arg2 !== 'undefined') {
|
|
args.push(arg2);
|
|
}
|
|
if (isFn(options.name)) {
|
|
methodName = options.name(arg1);
|
|
} else if (isStr(options.name)) {
|
|
methodName = options.name;
|
|
}
|
|
const returnValue = qa[methodName].apply(qa, args);
|
|
if (isSyncApi(methodName)) { // 同步 api
|
|
return processReturnValue(methodName, returnValue, options.returnValue, isContextApi(methodName))
|
|
}
|
|
return returnValue
|
|
}
|
|
}
|
|
return method
|
|
}
|
|
|
|
const todoApis = Object.create(null);
|
|
|
|
const TODOS = [
|
|
'onTabBarMidButtonTap',
|
|
'subscribePush',
|
|
'unsubscribePush',
|
|
'onPush',
|
|
'offPush',
|
|
'share'
|
|
];
|
|
|
|
function createTodoApi (name) {
|
|
return function todoApi ({
|
|
fail,
|
|
complete
|
|
}) {
|
|
const res = {
|
|
errMsg: `${name}:fail:暂不支持 ${name} 方法`
|
|
};
|
|
isFn(fail) && fail(res);
|
|
isFn(complete) && complete(res);
|
|
}
|
|
}
|
|
|
|
TODOS.forEach(function (name) {
|
|
todoApis[name] = createTodoApi(name);
|
|
});
|
|
|
|
const providers = {
|
|
oauth: [],
|
|
share: [],
|
|
payment: [],
|
|
push: []
|
|
};
|
|
|
|
if (qa.canIUse('getAccountProvider')) {
|
|
providers.oauth.push(qa.getAccountProvider());
|
|
}
|
|
|
|
if (qa.canIUse('getVendorPaymentProvider')) {
|
|
providers.payment.push(qa.getVendorPaymentProvider());
|
|
}
|
|
|
|
function getProvider ({
|
|
service,
|
|
success,
|
|
fail,
|
|
complete
|
|
}) {
|
|
let res = false;
|
|
if (providers[service]) {
|
|
res = {
|
|
errMsg: 'getProvider:ok',
|
|
service,
|
|
provider: providers[service]
|
|
};
|
|
isFn(success) && success(res);
|
|
} else {
|
|
res = {
|
|
errMsg: 'getProvider:fail:服务[' + service + ']不存在'
|
|
};
|
|
isFn(fail) && fail(res);
|
|
}
|
|
isFn(complete) && complete(res);
|
|
}
|
|
|
|
var extraApi = /*#__PURE__*/Object.freeze({
|
|
__proto__: null,
|
|
getProvider: getProvider
|
|
});
|
|
|
|
const getEmitter = (function () {
|
|
let Emitter;
|
|
return function getUniEmitter () {
|
|
if (!Emitter) {
|
|
Emitter = new Vue();
|
|
}
|
|
return Emitter
|
|
}
|
|
})();
|
|
|
|
function apply (ctx, method, args) {
|
|
return ctx[method].apply(ctx, args)
|
|
}
|
|
|
|
function $on () {
|
|
return apply(getEmitter(), '$on', [...arguments])
|
|
}
|
|
function $off () {
|
|
return apply(getEmitter(), '$off', [...arguments])
|
|
}
|
|
function $once () {
|
|
return apply(getEmitter(), '$once', [...arguments])
|
|
}
|
|
function $emit () {
|
|
return apply(getEmitter(), '$emit', [...arguments])
|
|
}
|
|
|
|
var eventApi = /*#__PURE__*/Object.freeze({
|
|
__proto__: null,
|
|
$on: $on,
|
|
$off: $off,
|
|
$once: $once,
|
|
$emit: $emit
|
|
});
|
|
|
|
var api = /*#__PURE__*/Object.freeze({
|
|
__proto__: null
|
|
});
|
|
|
|
const MPPage = Page;
|
|
const MPComponent = Component;
|
|
|
|
const customizeRE = /:/g;
|
|
|
|
const customize = cached((str) => {
|
|
return camelize(str.replace(customizeRE, '-'))
|
|
});
|
|
|
|
function initTriggerEvent (mpInstance) {
|
|
const oldTriggerEvent = mpInstance.triggerEvent;
|
|
mpInstance.triggerEvent = function (event, ...args) {
|
|
return oldTriggerEvent.apply(mpInstance, [customize(event), ...args])
|
|
};
|
|
}
|
|
|
|
function initHook (name, options) {
|
|
const oldHook = options[name];
|
|
if (!oldHook) {
|
|
options[name] = function () {
|
|
initTriggerEvent(this);
|
|
};
|
|
} else {
|
|
options[name] = function (...args) {
|
|
initTriggerEvent(this);
|
|
return oldHook.apply(this, args)
|
|
};
|
|
}
|
|
}
|
|
|
|
Page = function (options = {}) {
|
|
initHook('onLoad', options);
|
|
return MPPage(options)
|
|
};
|
|
|
|
Component = function (options = {}) {
|
|
initHook('created', options);
|
|
return MPComponent(options)
|
|
};
|
|
|
|
const PAGE_EVENT_HOOKS = [
|
|
'onPullDownRefresh',
|
|
'onReachBottom',
|
|
'onAddToFavorites',
|
|
'onShareTimeline',
|
|
'onShareAppMessage',
|
|
'onPageScroll',
|
|
'onResize',
|
|
'onTabItemTap'
|
|
];
|
|
|
|
function initMocks (vm, mocks) {
|
|
const mpInstance = vm.$mp[vm.mpType];
|
|
mocks.forEach(mock => {
|
|
if (hasOwn(mpInstance, mock)) {
|
|
vm[mock] = mpInstance[mock];
|
|
}
|
|
});
|
|
}
|
|
|
|
function hasHook (hook, vueOptions) {
|
|
if (!vueOptions) {
|
|
return true
|
|
}
|
|
|
|
if (Vue.options && Array.isArray(Vue.options[hook])) {
|
|
return true
|
|
}
|
|
|
|
vueOptions = vueOptions.default || vueOptions;
|
|
|
|
if (isFn(vueOptions)) {
|
|
if (isFn(vueOptions.extendOptions[hook])) {
|
|
return true
|
|
}
|
|
if (vueOptions.super &&
|
|
vueOptions.super.options &&
|
|
Array.isArray(vueOptions.super.options[hook])) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
if (isFn(vueOptions[hook])) {
|
|
return true
|
|
}
|
|
const mixins = vueOptions.mixins;
|
|
if (Array.isArray(mixins)) {
|
|
return !!mixins.find(mixin => hasHook(hook, mixin))
|
|
}
|
|
}
|
|
|
|
function initHooks (mpOptions, hooks, vueOptions) {
|
|
hooks.forEach(hook => {
|
|
if (hasHook(hook, vueOptions)) {
|
|
mpOptions[hook] = function (args) {
|
|
return this.$vm && this.$vm.__call_hook(hook, args)
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
function initVueComponent (Vue, vueOptions) {
|
|
vueOptions = vueOptions.default || vueOptions;
|
|
let VueComponent;
|
|
if (isFn(vueOptions)) {
|
|
VueComponent = vueOptions;
|
|
} else {
|
|
VueComponent = Vue.extend(vueOptions);
|
|
}
|
|
vueOptions = VueComponent.options;
|
|
return [VueComponent, vueOptions]
|
|
}
|
|
|
|
function initSlots (vm, vueSlots) {
|
|
if (Array.isArray(vueSlots) && vueSlots.length) {
|
|
const $slots = Object.create(null);
|
|
vueSlots.forEach(slotName => {
|
|
$slots[slotName] = true;
|
|
});
|
|
vm.$scopedSlots = vm.$slots = $slots;
|
|
}
|
|
}
|
|
|
|
function initVueIds (vueIds, mpInstance) {
|
|
vueIds = (vueIds || '').split(',');
|
|
const len = vueIds.length;
|
|
|
|
if (len === 1) {
|
|
mpInstance._$vueId = vueIds[0];
|
|
} else if (len === 2) {
|
|
mpInstance._$vueId = vueIds[0];
|
|
mpInstance._$vuePid = vueIds[1];
|
|
}
|
|
}
|
|
|
|
function initData (vueOptions, context) {
|
|
let data = vueOptions.data || {};
|
|
const methods = vueOptions.methods || {};
|
|
|
|
if (typeof data === 'function') {
|
|
try {
|
|
data = data.call(context); // 支持 Vue.prototype 上挂的数据
|
|
} catch (e) {
|
|
if (process.env.VUE_APP_DEBUG) {
|
|
console.warn('根据 Vue 的 data 函数初始化小程序 data 失败,请尽量确保 data 函数中不访问 vm 对象,否则可能影响首次数据渲染速度。', data);
|
|
}
|
|
}
|
|
} else {
|
|
try {
|
|
// 对 data 格式化
|
|
data = JSON.parse(JSON.stringify(data));
|
|
} catch (e) {}
|
|
}
|
|
|
|
if (!isPlainObject(data)) {
|
|
data = {};
|
|
}
|
|
|
|
Object.keys(methods).forEach(methodName => {
|
|
if (context.__lifecycle_hooks__.indexOf(methodName) === -1 && !hasOwn(data, methodName)) {
|
|
data[methodName] = methods[methodName];
|
|
}
|
|
});
|
|
|
|
return data
|
|
}
|
|
|
|
const PROP_TYPES = [String, Number, Boolean, Object, Array, null];
|
|
|
|
function createObserver (name) {
|
|
return function observer (newVal, oldVal) {
|
|
if (this.$vm) {
|
|
this.$vm[name] = newVal; // 为了触发其他非 render watcher
|
|
}
|
|
}
|
|
}
|
|
|
|
function initBehaviors (vueOptions, initBehavior) {
|
|
const vueBehaviors = vueOptions.behaviors;
|
|
const vueExtends = vueOptions.extends;
|
|
const vueMixins = vueOptions.mixins;
|
|
|
|
let vueProps = vueOptions.props;
|
|
|
|
if (!vueProps) {
|
|
vueOptions.props = vueProps = [];
|
|
}
|
|
|
|
const behaviors = [];
|
|
if (Array.isArray(vueBehaviors)) {
|
|
vueBehaviors.forEach(behavior => {
|
|
behaviors.push(behavior.replace('uni://', `${"qa"}://`));
|
|
if (behavior === 'uni://form-field') {
|
|
if (Array.isArray(vueProps)) {
|
|
vueProps.push('name');
|
|
vueProps.push('value');
|
|
} else {
|
|
vueProps.name = {
|
|
type: String,
|
|
default: ''
|
|
};
|
|
vueProps.value = {
|
|
type: [String, Number, Boolean, Array, Object, Date],
|
|
default: ''
|
|
};
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (isPlainObject(vueExtends) && vueExtends.props) {
|
|
behaviors.push(
|
|
initBehavior({
|
|
properties: initProperties(vueExtends.props, true)
|
|
})
|
|
);
|
|
}
|
|
if (Array.isArray(vueMixins)) {
|
|
vueMixins.forEach(vueMixin => {
|
|
if (isPlainObject(vueMixin) && vueMixin.props) {
|
|
behaviors.push(
|
|
initBehavior({
|
|
properties: initProperties(vueMixin.props, true)
|
|
})
|
|
);
|
|
}
|
|
});
|
|
}
|
|
return behaviors
|
|
}
|
|
|
|
function parsePropType (key, type, defaultValue, file) {
|
|
// [String]=>String
|
|
if (Array.isArray(type) && type.length === 1) {
|
|
return type[0]
|
|
}
|
|
return type
|
|
}
|
|
|
|
function initProperties (props, isBehavior = false, file = '') {
|
|
const properties = {};
|
|
if (!isBehavior) {
|
|
properties.vueId = {
|
|
type: String,
|
|
value: ''
|
|
};
|
|
// 用于字节跳动小程序模拟抽象节点
|
|
properties.generic = {
|
|
type: Object,
|
|
value: null
|
|
};
|
|
properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots
|
|
type: null,
|
|
value: [],
|
|
observer: function (newVal, oldVal) {
|
|
const $slots = Object.create(null);
|
|
newVal.forEach(slotName => {
|
|
$slots[slotName] = true;
|
|
});
|
|
this.setData({
|
|
$slots
|
|
});
|
|
}
|
|
};
|
|
}
|
|
if (Array.isArray(props)) { // ['title']
|
|
props.forEach(key => {
|
|
properties[key] = {
|
|
type: null,
|
|
observer: createObserver(key)
|
|
};
|
|
});
|
|
} else if (isPlainObject(props)) { // {title:{type:String,default:''},content:String}
|
|
Object.keys(props).forEach(key => {
|
|
const opts = props[key];
|
|
if (isPlainObject(opts)) { // title:{type:String,default:''}
|
|
let value = opts.default;
|
|
if (isFn(value)) {
|
|
value = value();
|
|
}
|
|
|
|
opts.type = parsePropType(key, opts.type);
|
|
|
|
properties[key] = {
|
|
type: PROP_TYPES.indexOf(opts.type) !== -1 ? opts.type : null,
|
|
value,
|
|
observer: createObserver(key)
|
|
};
|
|
} else { // content:String
|
|
const type = parsePropType(key, opts);
|
|
properties[key] = {
|
|
type: PROP_TYPES.indexOf(type) !== -1 ? type : null,
|
|
observer: createObserver(key)
|
|
};
|
|
}
|
|
});
|
|
}
|
|
return properties
|
|
}
|
|
|
|
function wrapper$1 (event) {
|
|
// TODO 又得兼容 mpvue 的 mp 对象
|
|
try {
|
|
event.mp = JSON.parse(JSON.stringify(event));
|
|
} catch (e) {}
|
|
|
|
event.stopPropagation = noop;
|
|
event.preventDefault = noop;
|
|
|
|
event.target = event.target || {};
|
|
|
|
if (!hasOwn(event, 'detail')) {
|
|
event.detail = {};
|
|
}
|
|
|
|
if (hasOwn(event, 'markerId')) {
|
|
event.detail = typeof event.detail === 'object' ? event.detail : {};
|
|
event.detail.markerId = event.markerId;
|
|
}
|
|
|
|
if (isPlainObject(event.detail)) {
|
|
event.target = Object.assign({}, event.target, event.detail);
|
|
}
|
|
|
|
return event
|
|
}
|
|
|
|
function getExtraValue (vm, dataPathsArray) {
|
|
let context = vm;
|
|
dataPathsArray.forEach(dataPathArray => {
|
|
const dataPath = dataPathArray[0];
|
|
const value = dataPathArray[2];
|
|
if (dataPath || typeof value !== 'undefined') { // ['','',index,'disable']
|
|
const propPath = dataPathArray[1];
|
|
const valuePath = dataPathArray[3];
|
|
|
|
let vFor;
|
|
if (Number.isInteger(dataPath)) {
|
|
vFor = dataPath;
|
|
} else if (!dataPath) {
|
|
vFor = context;
|
|
} else if (typeof dataPath === 'string' && dataPath) {
|
|
if (dataPath.indexOf('#s#') === 0) {
|
|
vFor = dataPath.substr(3);
|
|
} else {
|
|
vFor = vm.__get_value(dataPath, context);
|
|
}
|
|
}
|
|
|
|
if (Number.isInteger(vFor)) {
|
|
context = value;
|
|
} else if (!propPath) {
|
|
context = vFor[value];
|
|
} else {
|
|
if (Array.isArray(vFor)) {
|
|
context = vFor.find(vForItem => {
|
|
return vm.__get_value(propPath, vForItem) === value
|
|
});
|
|
} else if (isPlainObject(vFor)) {
|
|
context = Object.keys(vFor).find(vForKey => {
|
|
return vm.__get_value(propPath, vFor[vForKey]) === value
|
|
});
|
|
} else {
|
|
console.error('v-for 暂不支持循环数据:', vFor);
|
|
}
|
|
}
|
|
|
|
if (valuePath) {
|
|
context = vm.__get_value(valuePath, context);
|
|
}
|
|
}
|
|
});
|
|
return context
|
|
}
|
|
|
|
function processEventExtra (vm, extra, event) {
|
|
const extraObj = {};
|
|
|
|
if (Array.isArray(extra) && extra.length) {
|
|
/**
|
|
*[
|
|
* ['data.items', 'data.id', item.data.id],
|
|
* ['metas', 'id', meta.id]
|
|
*],
|
|
*[
|
|
* ['data.items', 'data.id', item.data.id],
|
|
* ['metas', 'id', meta.id]
|
|
*],
|
|
*'test'
|
|
*/
|
|
extra.forEach((dataPath, index) => {
|
|
if (typeof dataPath === 'string') {
|
|
if (!dataPath) { // model,prop.sync
|
|
extraObj['$' + index] = vm;
|
|
} else {
|
|
if (dataPath === '$event') { // $event
|
|
extraObj['$' + index] = event;
|
|
} else if (dataPath === 'arguments') {
|
|
if (event.detail && event.detail.__args__) {
|
|
extraObj['$' + index] = event.detail.__args__;
|
|
} else {
|
|
extraObj['$' + index] = [event];
|
|
}
|
|
} else if (dataPath.indexOf('$event.') === 0) { // $event.target.value
|
|
extraObj['$' + index] = vm.__get_value(dataPath.replace('$event.', ''), event);
|
|
} else {
|
|
extraObj['$' + index] = vm.__get_value(dataPath);
|
|
}
|
|
}
|
|
} else {
|
|
extraObj['$' + index] = getExtraValue(vm, dataPath);
|
|
}
|
|
});
|
|
}
|
|
|
|
return extraObj
|
|
}
|
|
|
|
function getObjByArray (arr) {
|
|
const obj = {};
|
|
for (let i = 1; i < arr.length; i++) {
|
|
const element = arr[i];
|
|
obj[element[0]] = element[1];
|
|
}
|
|
return obj
|
|
}
|
|
|
|
function processEventArgs (vm, event, args = [], extra = [], isCustom, methodName) {
|
|
let isCustomMPEvent = false; // wxcomponent 组件,传递原始 event 对象
|
|
if (isCustom) { // 自定义事件
|
|
isCustomMPEvent = event.currentTarget &&
|
|
event.currentTarget.dataset &&
|
|
event.currentTarget.dataset.comType === 'wx';
|
|
if (!args.length) { // 无参数,直接传入 event 或 detail 数组
|
|
if (isCustomMPEvent) {
|
|
return [event]
|
|
}
|
|
return event.detail.__args__ || event.detail
|
|
}
|
|
}
|
|
|
|
const extraObj = processEventExtra(vm, extra, event);
|
|
|
|
const ret = [];
|
|
args.forEach(arg => {
|
|
if (arg === '$event') {
|
|
if (methodName === '__set_model' && !isCustom) { // input v-model value
|
|
ret.push(event.target.value);
|
|
} else {
|
|
if (isCustom && !isCustomMPEvent) {
|
|
ret.push(event.detail.__args__[0]);
|
|
} else { // wxcomponent 组件或内置组件
|
|
ret.push(event);
|
|
}
|
|
}
|
|
} else {
|
|
if (Array.isArray(arg) && arg[0] === 'o') {
|
|
ret.push(getObjByArray(arg));
|
|
} else if (typeof arg === 'string' && hasOwn(extraObj, arg)) {
|
|
ret.push(extraObj[arg]);
|
|
} else {
|
|
ret.push(arg);
|
|
}
|
|
}
|
|
});
|
|
|
|
return ret
|
|
}
|
|
|
|
const ONCE = '~';
|
|
const CUSTOM = '^';
|
|
|
|
function isMatchEventType (eventType, optType) {
|
|
return (eventType === optType) ||
|
|
(
|
|
optType === 'regionchange' &&
|
|
(
|
|
eventType === 'begin' ||
|
|
eventType === 'end'
|
|
)
|
|
)
|
|
}
|
|
|
|
function getContextVm (vm) {
|
|
let $parent = vm.$parent;
|
|
// 父组件是 scoped slots 或者其他自定义组件时继续查找
|
|
while ($parent && $parent.$parent && ($parent.$options.generic || $parent.$parent.$options.generic || $parent.$scope._$vuePid)) {
|
|
$parent = $parent.$parent;
|
|
}
|
|
return $parent && $parent.$parent
|
|
}
|
|
|
|
function handleEvent (event) {
|
|
event = wrapper$1(event);
|
|
|
|
// [['tap',[['handle',[1,2,a]],['handle1',[1,2,a]]]]]
|
|
const dataset = (event.currentTarget || event.target).dataset;
|
|
if (!dataset) {
|
|
return console.warn('事件信息不存在')
|
|
}
|
|
const eventOpts = dataset.eventOpts || dataset['event-opts']; // 支付宝 web-view 组件 dataset 非驼峰
|
|
if (!eventOpts) {
|
|
return console.warn('事件信息不存在')
|
|
}
|
|
|
|
// [['handle',[1,2,a]],['handle1',[1,2,a]]]
|
|
const eventType = event.type;
|
|
|
|
const ret = [];
|
|
|
|
eventOpts.forEach(eventOpt => {
|
|
let type = eventOpt[0];
|
|
const eventsArray = eventOpt[1];
|
|
|
|
const isCustom = type.charAt(0) === CUSTOM;
|
|
type = isCustom ? type.slice(1) : type;
|
|
const isOnce = type.charAt(0) === ONCE;
|
|
type = isOnce ? type.slice(1) : type;
|
|
|
|
if (eventsArray && isMatchEventType(eventType, type)) {
|
|
eventsArray.forEach(eventArray => {
|
|
const methodName = eventArray[0];
|
|
if (methodName) {
|
|
let handlerCtx = this.$vm;
|
|
if (handlerCtx.$options.generic) { // mp-weixin,mp-toutiao 抽象节点模拟 scoped slots
|
|
handlerCtx = getContextVm(handlerCtx) || handlerCtx;
|
|
}
|
|
if (methodName === '$emit') {
|
|
handlerCtx.$emit.apply(handlerCtx,
|
|
processEventArgs(
|
|
this.$vm,
|
|
event,
|
|
eventArray[1],
|
|
eventArray[2],
|
|
isCustom,
|
|
methodName
|
|
));
|
|
return
|
|
}
|
|
const handler = handlerCtx[methodName];
|
|
if (!isFn(handler)) {
|
|
throw new Error(` _vm.${methodName} is not a function`)
|
|
}
|
|
if (isOnce) {
|
|
if (handler.once) {
|
|
return
|
|
}
|
|
handler.once = true;
|
|
}
|
|
const params = processEventArgs(
|
|
this.$vm,
|
|
event,
|
|
eventArray[1],
|
|
eventArray[2],
|
|
isCustom,
|
|
methodName
|
|
);
|
|
// 参数尾部增加原始事件对象用于复杂表达式内获取额外数据
|
|
// eslint-disable-next-line no-sparse-arrays
|
|
ret.push(handler.apply(handlerCtx, (Array.isArray(params) ? params : []).concat([, , , , , , , , , , event])));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
if (
|
|
eventType === 'input' &&
|
|
ret.length === 1 &&
|
|
typeof ret[0] !== 'undefined'
|
|
) {
|
|
return ret[0]
|
|
}
|
|
}
|
|
|
|
const hooks = [
|
|
'onShow',
|
|
'onHide',
|
|
'onError',
|
|
'onPageNotFound',
|
|
'onThemeChange',
|
|
'onUnhandledRejection'
|
|
];
|
|
|
|
function parseBaseApp (vm, {
|
|
mocks,
|
|
initRefs
|
|
}) {
|
|
if (vm.$options.store) {
|
|
Vue.prototype.$store = vm.$options.store;
|
|
}
|
|
|
|
Vue.prototype.mpHost = "quickapp-webview";
|
|
|
|
Vue.mixin({
|
|
beforeCreate () {
|
|
if (!this.$options.mpType) {
|
|
return
|
|
}
|
|
|
|
this.mpType = this.$options.mpType;
|
|
|
|
this.$mp = {
|
|
data: {},
|
|
[this.mpType]: this.$options.mpInstance
|
|
};
|
|
|
|
this.$scope = this.$options.mpInstance;
|
|
|
|
delete this.$options.mpType;
|
|
delete this.$options.mpInstance;
|
|
|
|
if (this.mpType !== 'app') {
|
|
initRefs(this);
|
|
initMocks(this, mocks);
|
|
}
|
|
}
|
|
});
|
|
|
|
const appOptions = {
|
|
onLaunch (args) {
|
|
if (this.$vm) { // 已经初始化过了,主要是为了百度,百度 onShow 在 onLaunch 之前
|
|
return
|
|
}
|
|
|
|
this.$vm = vm;
|
|
|
|
this.$vm.$mp = {
|
|
app: this
|
|
};
|
|
|
|
this.$vm.$scope = this;
|
|
// vm 上也挂载 globalData
|
|
this.$vm.globalData = this.globalData;
|
|
|
|
this.$vm._isMounted = true;
|
|
this.$vm.__call_hook('mounted', args);
|
|
|
|
this.$vm.__call_hook('onLaunch', args);
|
|
}
|
|
};
|
|
|
|
// 兼容旧版本 globalData
|
|
appOptions.globalData = vm.$options.globalData || {};
|
|
// 将 methods 中的方法挂在 getApp() 中
|
|
const methods = vm.$options.methods;
|
|
if (methods) {
|
|
Object.keys(methods).forEach(name => {
|
|
appOptions[name] = methods[name];
|
|
});
|
|
}
|
|
|
|
initHooks(appOptions, hooks);
|
|
|
|
return appOptions
|
|
}
|
|
|
|
const mocks = ['nodeId', 'componentName', '_componentId', 'uniquePrefix'];
|
|
|
|
function isPage () {
|
|
return !this.ownerId
|
|
}
|
|
|
|
function findVmByVueId (vm, vuePid) {
|
|
const $children = vm.$children;
|
|
// 优先查找直属(反向查找:https://github.com/dcloudio/uni-app/issues/1200)
|
|
for (let i = $children.length - 1; i >= 0; i--) {
|
|
const childVm = $children[i];
|
|
if (childVm.$scope._$vueId === vuePid) {
|
|
return childVm
|
|
}
|
|
}
|
|
// 反向递归查找
|
|
let parentVm;
|
|
for (let i = $children.length - 1; i >= 0; i--) {
|
|
parentVm = findVmByVueId($children[i], vuePid);
|
|
if (parentVm) {
|
|
return parentVm
|
|
}
|
|
}
|
|
}
|
|
|
|
function initBehavior (options) {
|
|
return Behavior(options)
|
|
}
|
|
|
|
function initRefs (vm) {
|
|
const mpInstance = vm.$scope;
|
|
Object.defineProperty(vm, '$refs', {
|
|
get () {
|
|
const $refs = {};
|
|
const components = mpInstance.selectAllComponents('.vue-ref');
|
|
components.forEach(component => {
|
|
const ref = component.dataset.ref;
|
|
$refs[ref] = component.$vm || component;
|
|
});
|
|
const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for');
|
|
forComponents.forEach(component => {
|
|
const ref = component.dataset.ref;
|
|
if (!$refs[ref]) {
|
|
$refs[ref] = [];
|
|
}
|
|
$refs[ref].push(component.$vm || component);
|
|
});
|
|
return $refs
|
|
}
|
|
});
|
|
}
|
|
|
|
function handleLink (event) {
|
|
const {
|
|
vuePid,
|
|
vueOptions
|
|
} = event.detail || event.value; // detail 是微信,value 是百度(dipatch)
|
|
|
|
let parentVm;
|
|
|
|
if (vuePid) {
|
|
parentVm = findVmByVueId(this.$vm, vuePid);
|
|
}
|
|
|
|
if (!parentVm) {
|
|
parentVm = this.$vm;
|
|
}
|
|
|
|
vueOptions.parent = parentVm;
|
|
}
|
|
|
|
const instances = Object.create(null);
|
|
|
|
function initRelation ({
|
|
vuePid,
|
|
mpInstance
|
|
}) {
|
|
// triggerEvent 后,接收事件时机特别晚,已经到了 ready 之后
|
|
const nodeId = mpInstance.nodeId + '';
|
|
const webviewId = mpInstance.pageinstance.__pageId__ + '';
|
|
|
|
instances[webviewId + '_' + nodeId] = mpInstance.$vm;
|
|
|
|
this.triggerEvent('__l', {
|
|
vuePid,
|
|
nodeId,
|
|
webviewId
|
|
});
|
|
}
|
|
|
|
function handleLink$1 ({
|
|
detail: {
|
|
nodeId,
|
|
webviewId
|
|
}
|
|
}) {
|
|
const vm = instances[webviewId + '_' + nodeId];
|
|
if (!vm) {
|
|
return
|
|
}
|
|
let parentVm = instances[webviewId + '_' + vm.$scope.ownerId];
|
|
if (!parentVm) {
|
|
parentVm = this.$vm;
|
|
}
|
|
|
|
vm.$parent = parentVm;
|
|
vm.$root = parentVm.$root;
|
|
parentVm.$children.push(vm);
|
|
|
|
const createdVm = function () {
|
|
vm.__call_hook('created');
|
|
};
|
|
const mountedVm = function () {
|
|
// 处理当前 vm 子
|
|
if (vm._$childVues) {
|
|
vm._$childVues.forEach(([createdVm]) => createdVm());
|
|
vm._$childVues.forEach(([, mountedVm]) => mountedVm());
|
|
delete vm._$childVues;
|
|
}
|
|
vm.__call_hook('beforeMount');
|
|
vm._isMounted = true;
|
|
vm.__call_hook('mounted');
|
|
vm.__call_hook('onReady');
|
|
};
|
|
// 当 parentVm 已经 mounted 时,直接触发,否则延迟
|
|
if (!parentVm || parentVm._isMounted) {
|
|
createdVm();
|
|
mountedVm();
|
|
} else {
|
|
(parentVm._$childVues || (parentVm._$childVues = [])).push([createdVm, mountedVm]);
|
|
}
|
|
}
|
|
|
|
function parseApp (vm) {
|
|
Vue.prototype._$fallback = true; // 降级(调整原 vue 的部分生命周期,如 created,beforeMount,inject,provide)
|
|
|
|
Vue.mixin({
|
|
created () { // 处理 injections, triggerEvent 是异步,且触发时机很慢,故延迟 relation 设置
|
|
if (this.mpType !== 'app') {
|
|
initRefs(this);
|
|
this.__init_injections(this);
|
|
this.__init_provide(this);
|
|
}
|
|
}
|
|
});
|
|
|
|
return parseBaseApp(vm, {
|
|
mocks,
|
|
initRefs: function () {} // attached 时,可能查询不到
|
|
})
|
|
}
|
|
|
|
function createApp (vm) {
|
|
Vue.prototype.getOpenerEventChannel = function () {
|
|
if (!this.__eventChannel__) {
|
|
this.__eventChannel__ = new EventChannel();
|
|
}
|
|
return this.__eventChannel__
|
|
};
|
|
const callHook = Vue.prototype.__call_hook;
|
|
Vue.prototype.__call_hook = function (hook, args) {
|
|
if (hook === 'onLoad' && args && args.__id__) {
|
|
this.__eventChannel__ = getEventChannel(args.__id__);
|
|
delete args.__id__;
|
|
}
|
|
return callHook.call(this, hook, args)
|
|
};
|
|
App(parseApp(vm));
|
|
return vm
|
|
}
|
|
|
|
const encodeReserveRE = /[!'()*]/g;
|
|
const encodeReserveReplacer = c => '%' + c.charCodeAt(0).toString(16);
|
|
const commaRE = /%2C/g;
|
|
|
|
// fixed encodeURIComponent which is more conformant to RFC3986:
|
|
// - escapes [!'()*]
|
|
// - preserve commas
|
|
const encode = str => encodeURIComponent(str)
|
|
.replace(encodeReserveRE, encodeReserveReplacer)
|
|
.replace(commaRE, ',');
|
|
|
|
function stringifyQuery (obj, encodeStr = encode) {
|
|
const res = obj ? Object.keys(obj).map(key => {
|
|
const val = obj[key];
|
|
|
|
if (val === undefined) {
|
|
return ''
|
|
}
|
|
|
|
if (val === null) {
|
|
return encodeStr(key)
|
|
}
|
|
|
|
if (Array.isArray(val)) {
|
|
const result = [];
|
|
val.forEach(val2 => {
|
|
if (val2 === undefined) {
|
|
return
|
|
}
|
|
if (val2 === null) {
|
|
result.push(encodeStr(key));
|
|
} else {
|
|
result.push(encodeStr(key) + '=' + encodeStr(val2));
|
|
}
|
|
});
|
|
return result.join('&')
|
|
}
|
|
|
|
return encodeStr(key) + '=' + encodeStr(val)
|
|
}).filter(x => x.length > 0).join('&') : null;
|
|
return res ? `?${res}` : ''
|
|
}
|
|
|
|
function parseBaseComponent (vueComponentOptions, {
|
|
isPage,
|
|
initRelation
|
|
} = {}) {
|
|
const [VueComponent, vueOptions] = initVueComponent(Vue, vueComponentOptions);
|
|
|
|
const options = {
|
|
multipleSlots: true,
|
|
addGlobalClass: true,
|
|
...(vueOptions.options || {})
|
|
};
|
|
|
|
const componentOptions = {
|
|
options,
|
|
data: initData(vueOptions, Vue.prototype),
|
|
behaviors: initBehaviors(vueOptions, initBehavior),
|
|
properties: initProperties(vueOptions.props, false, vueOptions.__file),
|
|
lifetimes: {
|
|
attached () {
|
|
const properties = this.properties;
|
|
|
|
const options = {
|
|
mpType: isPage.call(this) ? 'page' : 'component',
|
|
mpInstance: this,
|
|
propsData: properties
|
|
};
|
|
|
|
initVueIds(properties.vueId, this);
|
|
|
|
// 处理父子关系
|
|
initRelation.call(this, {
|
|
vuePid: this._$vuePid,
|
|
vueOptions: options
|
|
});
|
|
|
|
// 初始化 vue 实例
|
|
this.$vm = new VueComponent(options);
|
|
|
|
// 处理$slots,$scopedSlots(暂不支持动态变化$slots)
|
|
initSlots(this.$vm, properties.vueSlots);
|
|
|
|
// 触发首次 setData
|
|
this.$vm.$mount();
|
|
},
|
|
ready () {
|
|
// 当组件 props 默认值为 true,初始化时传入 false 会导致 created,ready 触发, 但 attached 不触发
|
|
// https://developers.weixin.qq.com/community/develop/doc/00066ae2844cc0f8eb883e2a557800
|
|
if (this.$vm) {
|
|
this.$vm._isMounted = true;
|
|
this.$vm.__call_hook('mounted');
|
|
this.$vm.__call_hook('onReady');
|
|
}
|
|
},
|
|
detached () {
|
|
this.$vm && this.$vm.$destroy();
|
|
}
|
|
},
|
|
pageLifetimes: {
|
|
show (args) {
|
|
this.$vm && this.$vm.__call_hook('onPageShow', args);
|
|
},
|
|
hide () {
|
|
this.$vm && this.$vm.__call_hook('onPageHide');
|
|
},
|
|
resize (size) {
|
|
this.$vm && this.$vm.__call_hook('onPageResize', size);
|
|
}
|
|
},
|
|
methods: {
|
|
__l: handleLink,
|
|
__e: handleEvent
|
|
}
|
|
};
|
|
// externalClasses
|
|
if (vueOptions.externalClasses) {
|
|
componentOptions.externalClasses = vueOptions.externalClasses;
|
|
}
|
|
|
|
if (Array.isArray(vueOptions.wxsCallMethods)) {
|
|
vueOptions.wxsCallMethods.forEach(callMethod => {
|
|
componentOptions.methods[callMethod] = function (args) {
|
|
return this.$vm[callMethod](args)
|
|
};
|
|
});
|
|
}
|
|
|
|
if (isPage) {
|
|
return componentOptions
|
|
}
|
|
return [componentOptions, VueComponent]
|
|
}
|
|
|
|
function parseComponent (vueOptions) {
|
|
const [componentOptions, VueComponent] = parseBaseComponent(vueOptions);
|
|
|
|
componentOptions.lifetimes.attached = function attached () {
|
|
const properties = this.properties;
|
|
|
|
const options = {
|
|
mpType: isPage.call(this) ? 'page' : 'component',
|
|
mpInstance: this,
|
|
propsData: properties
|
|
};
|
|
|
|
initVueIds(properties.vueId, this);
|
|
|
|
// 初始化 vue 实例
|
|
this.$vm = new VueComponent(options);
|
|
|
|
// 处理$slots,$scopedSlots(暂不支持动态变化$slots)
|
|
initSlots(this.$vm, properties.vueSlots);
|
|
|
|
// 处理父子关系
|
|
initRelation.call(this, {
|
|
vuePid: this._$vuePid,
|
|
mpInstance: this
|
|
});
|
|
|
|
// 触发首次 setData
|
|
this.$vm.$mount();
|
|
};
|
|
|
|
// ready 比 handleLink 还早,初始化逻辑放到 handleLink 中
|
|
delete componentOptions.lifetimes.ready;
|
|
|
|
componentOptions.methods.__l = handleLink$1;
|
|
|
|
return componentOptions
|
|
}
|
|
|
|
const hooks$1 = [
|
|
'onShow',
|
|
'onHide',
|
|
'onUnload'
|
|
];
|
|
|
|
hooks$1.push(...PAGE_EVENT_HOOKS);
|
|
|
|
function parseBasePage (vuePageOptions, {
|
|
isPage,
|
|
initRelation
|
|
}) {
|
|
const pageOptions = parseComponent(vuePageOptions);
|
|
|
|
initHooks(pageOptions.methods, hooks$1, vuePageOptions);
|
|
|
|
pageOptions.methods.onLoad = function (query) {
|
|
this.options = query;
|
|
const copyQuery = Object.assign({}, query);
|
|
delete copyQuery.__id__;
|
|
this.$page = {
|
|
fullPath: '/' + (this.route || this.is) + stringifyQuery(copyQuery)
|
|
};
|
|
this.$vm.$mp.query = query; // 兼容 mpvue
|
|
this.$vm.__call_hook('onLoad', query);
|
|
};
|
|
|
|
return pageOptions
|
|
}
|
|
|
|
function parsePage (vuePageOptions) {
|
|
const pageOptions = parseBasePage(vuePageOptions, {
|
|
isPage,
|
|
initRelation
|
|
});
|
|
// 页面需要在 ready 中触发,其他组件是在 handleLink 中触发
|
|
pageOptions.lifetimes.ready = function ready () {
|
|
if (this.$vm && this.$vm.mpType === 'page') {
|
|
this.$vm.__call_hook('created');
|
|
this.$vm.__call_hook('beforeMount');
|
|
this.$vm._isMounted = true;
|
|
this.$vm.__call_hook('mounted');
|
|
this.$vm.__call_hook('onReady');
|
|
} else {
|
|
this.is && console.warn(this.is + ' is not ready');
|
|
}
|
|
};
|
|
|
|
pageOptions.lifetimes.detached = function detached () {
|
|
this.$vm && this.$vm.$destroy();
|
|
// 清理
|
|
const pageId = this.pageinstance.__pageId__;
|
|
Object.keys(instances).forEach(key => {
|
|
if (key.indexOf(pageId + '_') === 0) {
|
|
delete instances[key];
|
|
}
|
|
});
|
|
};
|
|
|
|
return pageOptions
|
|
}
|
|
|
|
function createPage (vuePageOptions) {
|
|
{
|
|
return Component(parsePage(vuePageOptions))
|
|
}
|
|
}
|
|
|
|
function createComponent (vueOptions) {
|
|
{
|
|
return Component(parseComponent(vueOptions))
|
|
}
|
|
}
|
|
|
|
todos.forEach(todoApi => {
|
|
protocols[todoApi] = false;
|
|
});
|
|
|
|
canIUses.forEach(canIUseApi => {
|
|
const apiName = protocols[canIUseApi] && protocols[canIUseApi].name ? protocols[canIUseApi].name
|
|
: canIUseApi;
|
|
if (!qa.canIUse(apiName)) {
|
|
protocols[canIUseApi] = false;
|
|
}
|
|
});
|
|
|
|
let uni = {};
|
|
|
|
if (typeof Proxy !== 'undefined' && "quickapp-webview" !== 'app-plus') {
|
|
uni = new Proxy({}, {
|
|
get (target, name) {
|
|
if (hasOwn(target, name)) {
|
|
return target[name]
|
|
}
|
|
if (baseApi[name]) {
|
|
return baseApi[name]
|
|
}
|
|
if (api[name]) {
|
|
return promisify(name, api[name])
|
|
}
|
|
{
|
|
if (extraApi[name]) {
|
|
return promisify(name, extraApi[name])
|
|
}
|
|
if (todoApis[name]) {
|
|
return promisify(name, todoApis[name])
|
|
}
|
|
}
|
|
if (eventApi[name]) {
|
|
return eventApi[name]
|
|
}
|
|
if (!hasOwn(qa, name) && !hasOwn(protocols, name)) {
|
|
return
|
|
}
|
|
return promisify(name, wrapper(name, qa[name]))
|
|
},
|
|
set (target, name, value) {
|
|
target[name] = value;
|
|
return true
|
|
}
|
|
});
|
|
} else {
|
|
Object.keys(baseApi).forEach(name => {
|
|
uni[name] = baseApi[name];
|
|
});
|
|
|
|
{
|
|
Object.keys(todoApis).forEach(name => {
|
|
uni[name] = promisify(name, todoApis[name]);
|
|
});
|
|
Object.keys(extraApi).forEach(name => {
|
|
uni[name] = promisify(name, todoApis[name]);
|
|
});
|
|
}
|
|
|
|
Object.keys(eventApi).forEach(name => {
|
|
uni[name] = eventApi[name];
|
|
});
|
|
|
|
Object.keys(api).forEach(name => {
|
|
uni[name] = promisify(name, api[name]);
|
|
});
|
|
|
|
Object.keys(qa).forEach(name => {
|
|
if (hasOwn(qa, name) || hasOwn(protocols, name)) {
|
|
uni[name] = promisify(name, wrapper(name, qa[name]));
|
|
}
|
|
});
|
|
}
|
|
|
|
qa.createApp = createApp;
|
|
qa.createPage = createPage;
|
|
qa.createComponent = createComponent;
|
|
|
|
var uni$1 = uni;
|
|
|
|
export default uni$1;
|
|
export { createApp, createComponent, createPage };
|
|
|