This commit is contained in:
2025-11-14 11:39:33 +08:00
parent 6e5d892992
commit 1ba633ba45
7143 changed files with 922330 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
export function setStorageSync (key, data) {
return my.setStorageSync({
key,
data
})
}
export function getStorageSync (key) {
const result = my.getStorageSync({
key
})
// 支付宝平台会返回一个 success 值,但是目前测试的结果这个始终是 true。当没有存储数据的时候其它平台会返回空字符串。
return result.data !== null ? result.data : ''
}
export function removeStorageSync (key) {
return my.removeStorageSync({
key
})
}

View File

@@ -0,0 +1,43 @@
import { getStorageSync } from './storage'
import { addSafeAreaInsets, populateParameters } from '../../mp-weixin/helpers/enhance-system-info'
const UUID_KEY = '__DC_STAT_UUID'
let deviceId
function addUuid (result) {
deviceId = deviceId || getStorageSync(UUID_KEY)
if (!deviceId) {
deviceId = Date.now() + '' + Math.floor(Math.random() * 1e7)
__GLOBAL__.setStorage({
key: UUID_KEY,
data: deviceId
})
}
result.deviceId = deviceId
}
function normalizePlatform (result) {
let platform = result.platform ? result.platform.toLowerCase() : 'devtools'
if (my.canIUse('isIDE')) {
// @ts-expect-error Property 'isIDE' does not exist on type 'typeof my'
platform = my.isIDE ? 'devtools' : platform
}
result.platform = platform
}
function reviseScreenSize (result) {
// 支付宝: 10.2.0+ 修正屏幕宽度和高度 https://opendocs.alipay.com/mini/api/gawhvz
if (result.screen) {
result.screenWidth = result.screen.width
result.screenHeight = result.screen.height
}
}
export default {
returnValue: function (result) {
reviseScreenSize(result)
addUuid(result)
addSafeAreaInsets(result)
populateParameters(result)
normalizePlatform(result)
}
}

View File

@@ -0,0 +1,132 @@
import createMediaQueryObserver from '../../../mp-weixin/helpers/create-media-query-observer'
import {
isFn,
hasOwn
} from 'uni-shared'
import { $on, $off } from 'uni-core/runtime/event-bus'
let onKeyboardHeightChangeCallback
export {
setStorageSync,
getStorageSync,
removeStorageSync
}
from '../../helpers/storage'
export {
getPushClientId,
onPushMessage,
offPushMessage,
invokePushCallback
}
from 'uni-core/service/api/plugin/push'
export {
__f__
}
from 'uni-core/service/api/plugin/__f__'
export function startGyroscope (params) {
if (hasOwn(params, 'interval')) {
console.warn('支付宝小程序 startGyroscope暂不支持interval')
}
params.success && params.success({
errMsg: 'startGyroscope:ok'
})
params.complete && params.complete({
errMsg: 'startGyroscope:ok'
})
}
function createExecCallback (execCallback) {
return function wrapperExecCallback (res) {
this.actions.forEach((action, index) => {
(action._$callbacks || []).forEach(callback => {
callback(res[index])
})
})
if (isFn(execCallback)) {
execCallback(res)
}
}
}
function addCallback (callback) {
if (isFn(callback)) {
const action = this.actions[this.actions.length - 1]
if (action) {
(action._$callbacks || (action._$callbacks = [])).push(callback)
}
}
}
export function createSelectorQuery () {
const query = my.createSelectorQuery()
const oldExec = query.exec
const oldScrollOffset = query.scrollOffset
const oldBoundingClientRect = query.boundingClientRect
query.exec = function exec (callback) {
return oldExec.call(this, createExecCallback(callback).bind(this))
}
query.scrollOffset = function scrollOffset (callback) {
const ret = oldScrollOffset.call(this)
addCallback.call(this, callback)
return ret
}
query.boundingClientRect = function boundingClientRect (callback) {
const ret = oldBoundingClientRect.call(this)
addCallback.call(this, callback)
return ret
}
if (!query.fields) {
query.fields = function ({
rect,
size,
scrollOffset
} = {}, callback) {
if (rect || size) {
this.boundingClientRect()
}
if (scrollOffset) {
this.scrollOffset()
}
addCallback.call(this, callback)
return this
}
}
if (!query.in) {
query.in = function () {
return this
}
}
return query
}
export function createIntersectionObserver (component, options) {
if (options && options.observeAll) {
options.selectAll = options.observeAll
delete options.observeAll
}
return my.createIntersectionObserver(options)
}
export function onKeyboardHeightChange (callback) {
// 与微信小程序一致仅保留最后一次监听
if (onKeyboardHeightChangeCallback) {
$off('uni:keyboardHeightChange', onKeyboardHeightChangeCallback)
}
onKeyboardHeightChangeCallback = callback
$on('uni:keyboardHeightChange', onKeyboardHeightChangeCallback)
}
export function offKeyboardHeightChange () {
// 与微信小程序一致移除最后一次监听
$off('uni:keyboardHeightChange', onKeyboardHeightChangeCallback)
onKeyboardHeightChangeCallback = null
}
export {
createMediaQueryObserver
}

View File

@@ -0,0 +1,508 @@
import {
isPlainObject,
hasOwn
} from 'uni-shared'
import navigateTo from 'uni-helpers/navigate-to'
import redirectTo from '../../../mp-weixin/helpers/redirect-to'
import getSystemInfo from '../../helpers/system-info'
// 不支持的 API 列表
const todos = [
'preloadPage',
'unPreloadPage',
'loadSubPackage'
// 'getRecorderManager',
// 'getBackgroundAudioManager',
// 'createInnerAudioContext',
// 'createCameraContext',
// 'createLivePlayerContext',
// 'startAccelerometer',
// 'startCompass',
// 'authorize',
// 'chooseInvoiceTitle',
// 'addTemplate',
// 'deleteTemplate',
// 'getTemplateLibraryById',
// 'getTemplateLibraryList',
// 'getTemplateList',
// 'sendTemplateMessage',
// 'setEnableDebug',
// 'getExtConfig',
// 'getExtConfigSync',
// 'onWindowResize',
// 'offWindowResize'
]
// 存在兼容性的 API 列表
const canIUses = [
'startPullDownRefresh',
'setTabBarItem',
'setTabBarStyle',
'hideTabBar',
'showTabBar',
'setTabBarBadge',
'removeTabBarBadge',
'showTabBarRedDot',
'hideTabBarRedDot',
'openSetting',
'getSetting',
'createIntersectionObserver',
'getUpdateManager',
'setBackgroundColor',
'setBackgroundTextStyle',
'checkIsSupportSoterAuthentication',
'startSoterAuthentication',
'checkIsSoterEnrolledInDevice',
'openDocument',
'createVideoContext',
'onMemoryWarning',
'addPhoneContact'
]
function _handleNetworkInfo (result) {
switch (result.networkType) {
case 'NOTREACHABLE':
result.networkType = 'none'
break
case 'WWAN':
// TODO ?
result.networkType = '3g'
break
default:
result.networkType = result.networkType.toLowerCase()
break
}
return {}
}
const protocols = { // 需要做转换的 API 列表
navigateTo: navigateTo(),
redirectTo,
returnValue (methodName, res = {}) { // 通用 returnValue 解析
if (res.error || res.errorMessage) {
res.errMsg = `${methodName}:fail ${res.errorMessage || res.error}`
delete res.error
delete res.errorMessage
} else {
res.errMsg = `${methodName}:ok`
}
return res
},
request: {
name: my.canIUse('request') ? 'request' : 'httpRequest',
args (fromArgs) {
const method = fromArgs.method || 'GET'
if (!fromArgs.header) { // 默认增加 header 参数,方便格式化 content-type
fromArgs.header = {}
}
const headers = {
'content-type': 'application/json'
}
Object.keys(fromArgs.header).forEach(key => {
headers[key.toLocaleLowerCase()] = fromArgs.header[key]
})
return {
header (header = {}, toArgs) {
return {
name: 'headers',
value: headers
}
},
data (data) {
// 钉钉小程序在content-type为application/json时需上传字符串形式data使用my.dd在真机运行钉钉小程序时不能正确判断
if (my.canIUse('saveFileToDingTalk') && method.toUpperCase() === 'POST' && headers['content-type'].indexOf(
'application/json') === 0 && isPlainObject(data)) {
return {
name: 'data',
value: JSON.stringify(data)
}
}
return {
name: 'data',
value: data
}
},
method: 'method', // TODO 支付宝小程序仅支持 get,post
responseType: false
}
},
returnValue: {
status: 'statusCode',
headers: 'header'
}
},
setNavigationBarColor: {
name: 'setNavigationBar',
args: {
frontColor: false,
animation: false
}
},
setNavigationBarTitle: {
name: 'setNavigationBar'
},
showModal ({
showCancel = true
} = {}) {
if (showCancel) {
return {
name: 'confirm',
args: {
cancelColor: false,
confirmColor: false,
cancelText: 'cancelButtonText',
confirmText: 'confirmButtonText'
},
returnValue (fromRes, toRes) {
toRes.confirm = fromRes.confirm
toRes.cancel = !fromRes.confirm
}
}
}
return {
name: 'alert',
args: {
confirmColor: false,
confirmText: 'buttonText'
},
returnValue (fromRes, toRes) {
toRes.confirm = true
toRes.cancel = false
}
}
},
showToast ({
icon = 'success'
} = {}) {
const args = {
title: 'content',
icon: 'type',
image: false,
mask: false
}
if (icon === 'loading') {
return {
name: 'showLoading',
args
}
}
return {
name: 'showToast',
args
}
},
showActionSheet: {
name: 'showActionSheet',
args: {
itemList: 'items',
itemColor: false
},
returnValue: {
index: 'tapIndex'
}
},
showLoading: {
args: {
title: 'content'
}
},
uploadFile: {
args: {
name: 'fileName'
}
// 从测试结果看,是有返回对象的,文档上没有说明。
},
downloadFile: {
returnValue: {
apFilePath: 'tempFilePath'
}
},
getFileInfo: {
args: {
filePath: 'apFilePath'
}
},
compressImage: {
args (fromArgs) {
fromArgs.compressLevel = 4
if (fromArgs && fromArgs.quality) {
fromArgs.compressLevel = Math.floor(fromArgs.quality / 26)
}
fromArgs.apFilePaths = [fromArgs.src]
},
returnValue (result) {
if (result.apFilePaths && result.apFilePaths.length) {
result.tempFilePath = result.apFilePaths[0]
}
}
},
chooseVideo: {
// 支付宝小程序文档中未找到仅在getSetting处提及但实际可用
returnValue: {
apFilePath: 'tempFilePath'
}
},
connectSocket: {
args: {
method: false,
protocols: false
}
// TODO 有没有返回值还需要测试下
},
chooseImage: {
returnValue (result) {
const hasTempFilePaths = hasOwn(result, 'tempFilePaths') && result.tempFilePaths
if (hasOwn(result, 'apFilePaths') && !hasTempFilePaths) {
result.tempFilePaths = result.apFilePaths
delete result.apFilePaths
}
if (!hasOwn(result, 'tempFiles') && hasTempFilePaths) {
result.tempFiles = []
result.tempFilePaths.forEach(tempFilePath => result.tempFiles.push({ path: tempFilePath }))
}
return {}
}
},
previewImage: {
args (fromArgs) {
// 支付宝小程序的 current 是索引值,而非图片地址。
const currentIndex = Number(fromArgs.current)
if (isNaN(currentIndex)) {
if (fromArgs.current && Array.isArray(fromArgs.urls)) {
const index = fromArgs.urls.indexOf(fromArgs.current)
fromArgs.current = ~index ? index : 0
}
} else {
fromArgs.current = currentIndex
}
return {
indicator: false,
loop: false
}
}
},
saveFile: {
args: {
tempFilePath: 'apFilePath'
},
returnValue: {
apFilePath: 'savedFilePath'
}
},
getSavedFileInfo: {
args: {
filePath: 'apFilePath'
}
},
getSavedFileList: {
returnValue (result) {
if (result.fileList && result.fileList.length) {
result.fileList.forEach(file => {
file.filePath = file.apFilePath
delete file.apFilePath
})
}
return {}
}
},
removeSavedFile: {
args: {
filePath: 'apFilePath'
}
},
getLocation: {
args: {
type: false,
altitude: false
}
},
openLocation: {
args: {
// TODO address 参数在阿里上是必传的
}
},
getNetworkType: {
returnValue: _handleNetworkInfo
},
onNetworkStatusChange: {
returnValue: _handleNetworkInfo
},
stopAccelerometer: {
name: 'offAccelerometerChange'
},
stopCompass: {
name: 'offCompassChange'
},
scanCode: {
name: 'scan',
args (fromArgs) {
if (fromArgs.scanType) {
switch (fromArgs.scanType[0]) {
case 'qrCode':
fromArgs.type = 'qr'
break
case 'barCode':
fromArgs.type = 'bar'
break
}
}
return {
onlyFromCamera: 'hideAlbum'
}
},
returnValue: {
code: 'result'
}
},
setClipboardData: {
name: 'setClipboard',
args: {
data: 'text'
}
},
getClipboardData: {
name: 'getClipboard',
returnValue: {
text: 'data'
}
},
pageScrollTo: {
args (fromArgs, toArgs) {
if (fromArgs.duration === undefined) {
toArgs.duration = 300
}
}
},
login: {
name: 'getAuthCode',
returnValue (result) {
result.code = result.authCode
}
},
getUserInfo: {
name: my.canIUse('getOpenUserInfo') ? 'getOpenUserInfo' : 'getAuthUserInfo',
returnValue (result) {
if (my.canIUse('getOpenUserInfo')) {
let response = {}
try {
response = JSON.parse(result.response).response
} catch (e) {}
result.nickName = response.nickName
result.avatar = response.avatar
}
result.userInfo = {
nickName: result.nickName,
avatarUrl: result.avatar
}
}
},
getUserProfile: {
name: my.canIUse('getOpenUserInfo') ? 'getOpenUserInfo' : 'getAuthUserInfo',
returnValue (result) {
if (my.canIUse('getOpenUserInfo')) {
let response = {}
try {
response = JSON.parse(result.response).response
} catch (e) {}
result.nickName = response.nickName
result.avatar = response.avatar
}
result.userInfo = {
nickName: result.nickName,
avatarUrl: result.avatar
}
}
},
requestPayment: {
name: 'tradePay',
args: {
orderInfo: 'tradeNO'
}
},
getBLEDeviceServices: {
returnValue (result) {
result.services.forEach((item) => {
item.uuid = item.serviceId
})
}
},
createBLEConnection: {
name: 'connectBLEDevice',
args: {
timeout: false
}
},
closeBLEConnection: {
name: 'disconnectBLEDevice'
},
onBLEConnectionStateChange: {
name: 'onBLEConnectionStateChanged'
},
makePhoneCall: {
args: {
phoneNumber: 'number'
}
},
stopGyroscope: {
name: 'offGyroscopeChange'
},
getSystemInfo: getSystemInfo,
getSystemInfoSync: getSystemInfo,
// 文档没提到,但是实测可用。
canvasToTempFilePath: {
returnValue (result) {
// 真机的情况下会有 tempFilePath 这个值,因此需要主动修改。
result.tempFilePath = result.apFilePath
}
},
setScreenBrightness: {
args: {
value: 'brightness'
}
},
getScreenBrightness: {
returnValue: {
brightness: 'value'
}
},
showShareMenu: {
name: 'showSharePanel'
},
hideHomeButton: {
name: 'hideBackHome'
},
saveVideoToPhotosAlbum: {
args: {
filePath: 'src'
}
},
chooseAddress: {
name: 'getAddress',
returnValue (result) {
const info = result.result || {}
result.userName = info.fullname
result.provinceName = info.prov
result.cityName = info.city
result.countyName = info.area
result.detailInfo = info.address
result.telNumber = info.mobilePhone
result.errMsg = result.resultStatus
}
}
}
// 钉钉小程序处理
if (!my.canIUse('saveImageToPhotosAlbum')) {
protocols.saveImageToPhotosAlbum = {
name: 'saveImage',
args: {
filePath: 'url'
}
}
}
export {
protocols,
todos,
canIUses
}

View File

@@ -0,0 +1,6 @@
export default {
oauth: ['alipay'],
share: ['alipay'],
payment: ['alipay'],
push: ['alipay']
}

View File

@@ -0,0 +1,25 @@
import { handleProps, markMPComponent } from './wrapper/util'
const MPComponent = Component
function initHook (name, options) {
const oldHook = options[name]
options[name] = function (...args) {
markMPComponent(this)
const props = this.props
if (props && props['data-com-type'] === 'wx') {
handleProps(this)
}
if (oldHook) {
return oldHook.apply(this, args)
}
}
}
if (!MPComponent.__$wrappered) {
MPComponent.__$wrappered = true
Component = function (options = {}) {
initHook('onInit', options)
return MPComponent(options)
}
}

View File

@@ -0,0 +1,27 @@
const AC = ['t', 'n', 'e', 'i', 'l', 'C', 'y', 'a', 'p', 'i', 'l', 'A']
const isAlipay = window.my && navigator.userAgent.indexOf(AC.reverse().join('')) > -1
export function initWebviewApi (readyCallback) {
if (!isAlipay) {
return
}
document.addEventListener('DOMContentLoaded', readyCallback)
const {
navigateTo,
navigateBack,
switchTab,
reLaunch,
redirectTo,
postMessage,
getEnv
} = window.my
return {
navigateTo,
navigateBack,
switchTab,
reLaunch,
redirectTo,
postMessage,
getEnv
}
}

View File

@@ -0,0 +1,39 @@
import Vue from 'vue'
import parseBaseApp from '../../../mp-weixin/runtime/wrapper/app-base-parser'
import {
mocks,
initRefs
} from './util'
export default function parseApp (vm) {
Vue.prototype.$onAliGetAuthorize = function onAliGetAuthorize (method, $event) {
my.getPhoneNumber({
success: (res) => {
$event.type = 'getphonenumber'
const response = JSON.parse(res.response)
$event.detail.errMsg = 'getPhoneNumber:ok'
$event.detail.encryptedData = response.response
$event.detail.sign = response.sign
this[method]($event)
},
fail: (res) => {
$event.type = 'getphonenumber'
$event.detail.errMsg = 'getPhoneNumber:fail Error: ' + JSON.stringify(res)
this[method]($event)
}
})
}
Vue.prototype.$onAliAuthError = function $onAliAuthError (method, $event) {
$event.type = 'getphonenumber'
$event.detail.errMsg = 'getPhoneNumber:fail Error: ' + $event.detail.errorMessage
this[method]($event)
}
return parseBaseApp(vm, {
mocks,
initRefs
})
}

View File

@@ -0,0 +1,174 @@
import Vue from 'vue'
import {
initData,
initVueIds,
handleEvent,
initBehaviors,
initProperties,
initVueComponent
} from 'uni-wrapper/util'
import {
handleRef,
handleLink,
handleWrap,
initBehavior,
initRelation,
triggerEvent,
createObserver,
isComponent2,
initChildVues,
initSpecialMethods
} from './util'
function initSlots (vm, vueSlots) {
const $slots = Object.create(null)
// 未启用小程序基础库 2.0 时,组件实例支持支持访问 $slots、$scopedSlots
Object.defineProperty(vm, '$slots', {
get () {
const $scope = this.$scope
return ($scope && $scope.props.$slots) || ($scope && $scope.props.$scopedSlots ? {} : $slots)
}
})
Object.defineProperty(vm, '$scopedSlots', {
get () {
const $scope = this.$scope
return ($scope && $scope.props.$scopedSlots) || ($scope && $scope.props.$slots ? {} : $slots)
}
})
// 处理$slots,$scopedSlots暂不支持动态变化$slots
if (Array.isArray(vueSlots) && vueSlots.length) {
vueSlots.forEach(slotName => {
$slots[slotName] = true
})
}
}
function initVm (VueComponent) {
if (this.$vm) {
return
}
const properties = this.props
const options = {
mpType: 'component',
mpInstance: this,
propsData: properties
}
initVueIds(properties.vueId, this)
if (isComponent2) {
// 处理父子关系
initRelation.call(this, {
vuePid: this._$vuePid,
vueOptions: options
})
// 初始化 vue 实例
this.$vm = new VueComponent(options)
initSlots(this.$vm, properties.vueSlots)
// 触发首次 setData
this.$vm.$mount()
} else {
// 处理父子关系
initRelation.call(this, {
vuePid: this._$vuePid,
vueOptions: options,
VueComponent,
mpInstance: this
})
if (options.parent) {
// 父组件已经初始化,直接初始化子,否则放到父组件的 didMount 中处理
// 初始化 vue 实例
this.$vm = new VueComponent(options)
handleRef.call(options.parent.$scope, this)
initSlots(this.$vm, properties.vueSlots)
// 触发首次 setData
this.$vm.$mount()
initChildVues(this)
this.$vm._isMounted = true
this.$vm.__call_hook('mounted')
this.$vm.__call_hook('onReady')
}
}
}
export default function parseComponent (vueComponentOptions, needVueOptions) {
const [VueComponent, vueOptions] = initVueComponent(Vue, vueComponentOptions)
const properties = initProperties(vueOptions.props, false, vueOptions.__file)
const props = {
onVueInit: function () {}
}
Object.keys(properties).forEach(key => {
props[key] = properties[key].value
})
const componentOptions = {
mixins: initBehaviors(vueOptions, initBehavior),
data: initData(vueOptions, Vue.prototype),
props,
didMount () {
if (my.dd) {
// 钉钉小程序底层基础库有 bug,组件嵌套使用时,在 didMount 中无法及时调用 props 中的方法
setTimeout(() => {
initVm.call(this, VueComponent)
}, 4)
} else {
initVm.call(this, VueComponent)
}
initSpecialMethods(this)
if (isComponent2) {
this.$vm._isMounted = true
this.$vm.__call_hook('mounted')
this.$vm.__call_hook('onReady')
}
},
didUnmount () {
this.$vm && this.$vm.$destroy()
},
methods: {
__r: handleRef,
__e: handleEvent,
__l: handleLink,
__w: handleWrap,
triggerEvent
}
}
if (vueOptions.options) {
componentOptions.options = vueOptions.options
}
if (isComponent2) {
componentOptions.onInit = function onInit () {
initVm.call(this, VueComponent)
}
componentOptions.deriveDataFromProps = createObserver()
} else {
componentOptions.didUpdate = createObserver(true)
}
if (Array.isArray(vueOptions.wxsCallMethods)) {
vueOptions.wxsCallMethods.forEach(callMethod => {
componentOptions.methods[callMethod] = function (args) {
return this.$vm[callMethod](args)
}
})
}
return needVueOptions ? [componentOptions, vueOptions] : componentOptions
}

View File

@@ -0,0 +1,52 @@
import {
hasOwn
} from 'uni-shared'
const isArray = Array.isArray
const keyList = Object.keys
export default function equal (a, b) {
if (a === b) return true
if (a && b && typeof a === 'object' && typeof b === 'object') {
const arrA = isArray(a)
const arrB = isArray(b)
let i, length, key
if (arrA && arrB) {
length = a.length
if (length !== b.length) return false
for (i = length; i-- !== 0;) {
if (!equal(a[i], b[i])) return false
}
return true
}
if (arrA !== arrB) return false
const dateA = a instanceof Date
const dateB = b instanceof Date
if (dateA !== dateB) return false
if (dateA && dateB) return a.getTime() === b.getTime()
const regexpA = a instanceof RegExp
const regexpB = b instanceof RegExp
if (regexpA !== regexpB) return false
if (regexpA && regexpB) return a.toString() === b.toString()
const keys = keyList(a)
length = keys.length
if (length !== keyList(b).length) {
return false
}
for (i = length; i-- !== 0;) {
if (!hasOwn.call(b, keys[i])) return false
}
for (i = length; i-- !== 0;) {
key = keys[i]
if (!equal(a[key], b[key])) return false
}
return true
}
return false
}

View File

@@ -0,0 +1,115 @@
import Vue from 'vue'
import {
stringifyQuery
} from 'uni-shared/query'
import {
initData,
initHooks,
initUnknownHooks,
handleEvent,
initBehaviors,
initVueComponent,
PAGE_EVENT_HOOKS
} from 'uni-wrapper/util'
import {
handleRef,
handleLink,
handleWrap,
initBehavior,
triggerEvent,
initChildVues,
initSpecialMethods
} from './util'
import { $emit } from 'uni-core/runtime/event-bus'
const hooks = [
'onShow',
'onHide',
// mp-alipay 特有
'onTitleClick',
'onOptionMenuClick',
'onPopMenuClick',
'onPullIntercept'
]
hooks.push(...PAGE_EVENT_HOOKS)
export default function parsePage (vuePageOptions) {
const [VueComponent, vueOptions] = initVueComponent(Vue, vuePageOptions)
const pageOptions = {
mixins: initBehaviors(vueOptions, initBehavior),
data: initData(vueOptions, Vue.prototype),
onLoad (query) {
const properties = this.props
const options = {
mpType: 'page',
mpInstance: this,
propsData: properties
}
// 初始化 vue 实例
this.$vm = new VueComponent(options)
initSpecialMethods(this)
// 触发首次 setData
this.$vm.$mount()
const copyQuery = Object.assign({}, query)
delete copyQuery.__id__
this.$page = {
fullPath: '/' + this.route + stringifyQuery(copyQuery)
}
this.options = query
this.$vm.$mp.query = query // 兼容 mpvue
this.$vm.__call_hook('onLoad', query)
},
onReady () {
initChildVues(this)
this.$vm._isMounted = true
this.$vm.__call_hook('mounted')
this.$vm.__call_hook('onReady')
},
onUnload () {
this.$vm.__call_hook('onUnload')
this.$vm.$destroy()
},
events: {
// 支付宝小程序有些页面事件只能放在events下
onBack () {
this.$vm.__call_hook('onBackPress')
},
onKeyboardHeight (res) {
$emit('uni:keyboardHeightChange', res)
}
},
__r: handleRef,
__e: handleEvent,
__l: handleLink,
__w: handleWrap,
triggerEvent
}
Object.assign(pageOptions.events, vueOptions.events || {})
initHooks(pageOptions, hooks, vueOptions)
initUnknownHooks(pageOptions, vueOptions, ['onReady'])
if (Array.isArray(vueOptions.wxsCallMethods)) {
vueOptions.wxsCallMethods.forEach(callMethod => {
pageOptions[callMethod] = function (args) {
return this.$vm[callMethod](args)
}
})
}
return pageOptions
}

View File

@@ -0,0 +1,242 @@
import {
isFn,
cached,
camelize,
hasOwn
} from 'uni-shared'
import {
handleLink as handleBaseLink,
toSkip
} from '../../../mp-weixin/runtime/wrapper/util'
import deepEqual from './deep-equal'
export { markMPComponent } from '../../../mp-weixin/runtime/wrapper/util'
const customizeRE = /:/g
const customize = cached((str) => {
return camelize(str.replace(customizeRE, '-'))
})
export const isComponent2 = my.canIUse('component2')
export const mocks = ['$id']
export function initRefs () {
}
export function initBehavior ({
properties
}) {
const props = {}
Object.keys(properties).forEach(key => {
props[key] = properties[key].value
})
return {
props
}
}
export function initRelation (detail) {
this.props.onVueInit(detail)
}
export function initSpecialMethods (mpInstance) {
if (!mpInstance.$vm) {
return
}
let path = mpInstance.is || mpInstance.route
if (!path) {
return
}
if (path.indexOf('/') === 0) {
path = path.substr(1)
}
const specialMethods = my.specialMethods && my.specialMethods[path]
if (specialMethods) {
specialMethods.forEach(method => {
if (isFn(mpInstance.$vm[method])) {
mpInstance[method] = function (event) {
if (hasOwn(event, 'markerId')) {
event.detail = typeof event.detail === 'object' ? event.detail : {}
event.detail.markerId = event.markerId
}
// TODO normalizeEvent
mpInstance.$vm[method](event)
}
}
})
}
}
export function initChildVues (mpInstance) {
// 此时需保证当前 mpInstance 已经存在 $vm
if (!mpInstance.$vm) {
return
}
mpInstance._$childVues && mpInstance._$childVues.forEach(({
vuePid,
vueOptions,
VueComponent,
mpInstance: childMPInstance
}) => {
// 父子关系
handleBaseLink.call(mpInstance, {
detail: {
vuePid,
vueOptions
}
})
childMPInstance.$vm = new VueComponent(vueOptions)
initSpecialMethods(childMPInstance)
handleRef.call(vueOptions.parent.$scope, childMPInstance)
childMPInstance.$vm.$mount()
initChildVues(childMPInstance)
childMPInstance.$vm._isMounted = true
childMPInstance.$vm.__call_hook('mounted')
childMPInstance.$vm.__call_hook('onReady')
})
delete mpInstance._$childVues
}
export function handleProps (ref) {
const eventProps = {}
let refProps = ref.props
const eventList = (refProps['data-event-list'] || '').split(',')
// 初始化支付宝小程序组件事件
eventList.forEach(key => {
const handler = refProps[key]
const res = key.match(/^on([A-Z])(\S*)/)
const event = res && (res[1].toLowerCase() + res[2])
refProps[key] = eventProps[key] = function () {
const props = Object.assign({}, refProps)
props[key] = handler
// 由于支付宝事件可能包含多个参数,不使用微信小程序事件格式
delete props['data-com-type']
triggerEvent.bind({ props })(event, {
__args__: [...arguments]
})
}
})
// 处理 props 重写
Object.defineProperty(ref, 'props', {
get () {
return refProps
},
set (value) {
refProps = Object.assign(value, eventProps)
}
})
}
export function handleRef (ref) {
if (!(ref && this.$vm)) {
return
}
const refName = ref.props['data-ref']
const refInForName = ref.props['data-ref-in-for']
if (refName) {
this.$vm.$refs[refName] = ref.$vm || toSkip(ref)
} else if (refInForName) {
(this.$vm.$refs[refInForName] || (this.$vm.$refs[refInForName] = [])).push(ref.$vm || toSkip(ref))
}
}
export function triggerEvent (type, detail, options) {
const handler = this.props && this.props[customize('on-' + type)]
if (!handler) {
return
}
const eventOpts = this.props['data-event-opts']
const eventParams = this.props['data-event-params']
const comType = this.props['data-com-type']
const target = {
dataset: {
eventOpts,
eventParams,
comType
}
}
handler({
type: customize(type),
target,
currentTarget: target,
detail
})
}
const IGNORES = ['$slots', '$scopedSlots']
export function createObserver (isDidUpdate) {
return function observe (props) {
const prevProps = isDidUpdate ? props : this.props
const nextProps = isDidUpdate ? this.props : props
if (deepEqual(prevProps, nextProps)) {
return
}
Object.keys(prevProps).forEach(name => {
if (IGNORES.indexOf(name) === -1) {
const prevValue = prevProps[name]
const nextValue = nextProps[name]
if (!isFn(prevValue) && !isFn(nextValue) && !deepEqual(prevValue, nextValue)) {
this.$vm[name] = nextProps[name]
}
}
})
}
}
export const handleLink = (function () {
if (isComponent2) {
return function handleLink (detail) {
return handleBaseLink.call(this, {
detail
})
}
}
return function handleLink (detail) {
if (this.$vm && this.$vm._isMounted) { // 父已初始化
return handleBaseLink.call(this, {
detail: {
vuePid: detail.vuePid,
vueOptions: detail.vueOptions
}
})
}
// 支付宝通过 didMount 来实现,先子后父,故等父 ready 之后,统一初始化
(this._$childVues || (this._$childVues = [])).unshift(detail)
}
})()
export const handleWrap = function (mp, destory) {
const vueId = mp.props.vueId
const list = (mp.props['data-event-list'] || '').split(',')
list.forEach(eventName => {
const key = `${eventName}${vueId}`
if (destory) {
delete this[key]
} else {
this[key] = function () {
mp.props[eventName].apply(this, arguments)
}
}
})
if (!destory) {
handleProps(mp)
}
}