diff --git a/packages/shims-uni-app.d.ts b/packages/shims-uni-app.d.ts index 4bea03a21d551eb84168761f26ef63ee9c84c5fd..350429d6914111fd2e25a5c28cfde98daa0b03d0 100644 --- a/packages/shims-uni-app.d.ts +++ b/packages/shims-uni-app.d.ts @@ -8,6 +8,7 @@ declare namespace Page { options: Record meta: UniApp.PageRouteMeta openType: UniApp.OpenType + eventChannel: unknown statusBarStyle?: 'dark' | 'light' } } diff --git a/packages/shims-vue-runtime.d.ts b/packages/shims-vue-runtime.d.ts index 801550c7d2451181bc5b1d2b07da85b1d6623372..f31c20568c07132de21f3de43bf17d2ba1ff252f 100644 --- a/packages/shims-vue-runtime.d.ts +++ b/packages/shims-vue-runtime.d.ts @@ -1,3 +1,4 @@ +import { EventChannel } from '@dcloudio/uni-shared' import { UniLifecycleHooks } from '@dcloudio/uni-vue/src/apiLifecycle' import { ComponentCustomProperties, ComponentInternalInstance } from 'vue' declare module '@vue/runtime-core' { @@ -8,6 +9,7 @@ declare module '@vue/runtime-core' { } // 目前 H5,APP 平台 getCurrentPages 中获取的 page 对象调整为 vm 对象 $getAppWebview?: () => PlusWebviewWebviewObject + getOpenerEventChannel: () => EventChannel $page: Page.PageInstance['$page'] $mpType?: 'app' | 'page' __isTabBar: boolean diff --git a/packages/uni-app-plus/dist/uni-app-service.es.js b/packages/uni-app-plus/dist/uni-app-service.es.js index 9d0ef5ce071295f72f917f1d8d4974d83d6fed5a..97986adff45359dd3b724d49f5b6c268e04dcfcd 100644 --- a/packages/uni-app-plus/dist/uni-app-service.es.js +++ b/packages/uni-app-plus/dist/uni-app-service.es.js @@ -1160,7 +1160,70 @@ var serviceContext = (function (vue) { const ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED = 'onNavigationBarSearchInputFocusChanged'; // framework const ON_APP_ENTER_FOREGROUND = 'onAppEnterForeground'; - const ON_APP_ENTER_BACKGROUND = 'onAppEnterBackground'; + const ON_APP_ENTER_BACKGROUND = 'onAppEnterBackground'; + + 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, ...cacheArgs.shift()]); + } + } + } + _addListener(eventName, type, fn) { + (this.listener[eventName] || (this.listener[eventName] = [])).push({ + fn, + type, + }); + } + } const isObject = (val) => val !== null && typeof val === 'object'; class BaseFormatter { @@ -1842,7 +1905,7 @@ var serviceContext = (function (vue) { } return pullToRefresh; } - function initPageInternalInstance(openType, url, pageQuery, meta) { + function initPageInternalInstance(openType, url, pageQuery, meta, eventChannel) { const { id, route } = meta; return { id: id, @@ -1852,6 +1915,7 @@ var serviceContext = (function (vue) { options: pageQuery, meta, openType, + eventChannel, statusBarStyle: meta.navigationBar.titleColor === '#000000' ? 'dark' : 'light', }; } @@ -10571,7 +10635,7 @@ var serviceContext = (function (vue) { const instance = vue.getCurrentInstance(); const pageVm = instance.proxy; initPageVm(pageVm, __pageInstance); - addCurrentPage(initScope(__pageId, pageVm)); + addCurrentPage(initScope(__pageId, pageVm, __pageInstance)); vue.onMounted(() => { invokeHook(pageVm, ON_READY); // TODO preloadSubPackages @@ -10585,7 +10649,7 @@ var serviceContext = (function (vue) { }; return component; } - function initScope(pageId, vm) { + function initScope(pageId, vm, pageInstance) { const $getAppWebview = () => { return plus.webview.getWebviewById(pageId + ''); }; @@ -10593,6 +10657,12 @@ var serviceContext = (function (vue) { vm.$scope = { $getAppWebview, }; + vm.getOpenerEventChannel = () => { + if (!pageInstance.eventChannel) { + pageInstance.eventChannel = new EventChannel(pageId); + } + return pageInstance.eventChannel; + }; return vm; } @@ -10693,7 +10763,7 @@ var serviceContext = (function (vue) { return preloadWebviews[url]; } - function registerPage({ url, path, query, openType, webview, }) { + function registerPage({ url, path, query, openType, webview, eventChannel, }) { // fast 模式,nvue 首页时,会在nvue中主动调用registerPage并传入首页webview,此时初始化一下首页(因为此时可能还未调用registerApp) if (webview) { initEntry(); @@ -10710,7 +10780,9 @@ var serviceContext = (function (vue) { webview = undefined; } else { - // TODO eventChannel + if (eventChannel) { + _webview.__page__.$page.eventChannel = eventChannel; + } addCurrentPage(_webview.__page__); if ((process.env.NODE_ENV !== 'production')) { console.log(formatLog('uni-app', `reuse preloadWebview(${path},${_webview.id})`)); @@ -10738,11 +10810,11 @@ var serviceContext = (function (vue) { initWebview(webview, path, query, routeOptions.meta); const route = path.substr(1); webview.__uniapp_route = route; - const pageInstance = initPageInternalInstance(openType, url, query, routeOptions.meta); + const pageInstance = initPageInternalInstance(openType, url, query, routeOptions.meta, eventChannel); initNVueEntryPage(webview); if (webview.nvue) { // nvue 时,先启用一个占位 vm - const fakeNVueVm = createNVueVm(webview, pageInstance); + const fakeNVueVm = createNVueVm(parseInt(webview.id), webview, pageInstance); initPageVm(fakeNVueVm, pageInstance); addCurrentPage(fakeNVueVm); } @@ -10787,12 +10859,13 @@ var serviceContext = (function (vue) { }); } } - function createNVueVm(webview, pageInstance) { + function createNVueVm(pageId, webview, pageInstance) { return { $: {}, onNVuePageCreated(vm, curNVuePage) { vm.$ = {}; // 补充一个 nvue 的 $ 对象,模拟 vue3 的,不然有部分地方访问了 $ vm.$getAppWebview = () => webview; + vm.getOpenerEventChannel = curNVuePage.getOpenerEventChannel; // 替换真实的 nvue 的 vm initPageVm(vm, pageInstance); const pages = getAllPages(); @@ -10807,11 +10880,17 @@ var serviceContext = (function (vue) { $getAppWebview() { return webview; }, + getOpenerEventChannel() { + if (!pageInstance.eventChannel) { + pageInstance.eventChannel = new EventChannel(pageId); + } + return pageInstance.eventChannel; + }, }; } const $navigateTo = (args, { resolve, reject }) => { - const { url, animationType, animationDuration } = args; + const { url, events, animationType, animationDuration } = args; const { path, query } = parseUrl(url); const [aniType, aniDuration] = initAnimation(path, animationType, animationDuration); navigate(path, () => { @@ -10819,6 +10898,7 @@ var serviceContext = (function (vue) { url, path, query, + events, aniType, aniDuration, }) @@ -10827,13 +10907,13 @@ var serviceContext = (function (vue) { }, args.openType === 'appLaunch'); }; const navigateTo = defineAsyncApi(API_NAVIGATE_TO, $navigateTo, NavigateToProtocol, NavigateToOptions); - function _navigateTo({ url, path, query, aniType, aniDuration, }) { - // TODO eventChannel + function _navigateTo({ url, path, query, events, aniType, aniDuration, }) { // 当前页面触发 onHide invokeHook(ON_HIDE); + const eventChannel = new EventChannel(getWebviewId() + 1, events); return new Promise((resolve) => { - showWebview(registerPage({ url, path, query, openType: 'navigateTo' }), aniType, aniDuration, () => { - resolve(undefined); + showWebview(registerPage({ url, path, query, openType: 'navigateTo', eventChannel }), aniType, aniDuration, () => { + resolve({ eventChannel }); }); }); } diff --git a/packages/uni-app-plus/dist/uni-app-view.umd.js b/packages/uni-app-plus/dist/uni-app-view.umd.js index 82b7ea49b645183a1fd166fa8537fe479042dabc..392dabc4c4bc799f8d05e20902c0ff069a2fbac8 100644 --- a/packages/uni-app-plus/dist/uni-app-view.umd.js +++ b/packages/uni-app-plus/dist/uni-app-view.umd.js @@ -736,8 +736,8 @@ function normalizeViewMethodName(pageId, name) { return pageId + "." + name; } - function subscribeViewMethod(pageId) { - UniViewJSBridge.subscribe(normalizeViewMethodName(pageId, INVOKE_VIEW_API), onInvokeViewMethod); + function subscribeViewMethod(pageId, wrapper2) { + UniViewJSBridge.subscribe(normalizeViewMethodName(pageId, INVOKE_VIEW_API), wrapper2 ? wrapper2(onInvokeViewMethod) : onInvokeViewMethod); } function registerViewMethod(pageId, name, fn) { name = normalizeViewMethodName(pageId, name); @@ -16835,21 +16835,20 @@ }); } const pageVm = { $el: document.body }; - function wrapperViewMethod(fn) { - return (...args) => { - onPageReady(() => { - fn.apply(null, args); - }); - }; - } function initViewMethods() { const pageId = getCurrentPageId(); - subscribeViewMethod(pageId); - registerViewMethod(pageId, "requestComponentInfo", wrapperViewMethod((args, publish) => { + subscribeViewMethod(pageId, (fn) => { + return (...args) => { + onPageReady(() => { + fn.apply(null, args); + }); + }; + }); + registerViewMethod(pageId, "requestComponentInfo", (args, publish) => { requestComponentInfo(pageVm, args.reqs, publish); - })); - registerViewMethod(pageId, API_PAGE_SCROLL_TO, wrapperViewMethod(pageScrollTo)); - registerViewMethod(pageId, API_LOAD_FONT_FACE, wrapperViewMethod(loadFontFace)); + }); + registerViewMethod(pageId, API_PAGE_SCROLL_TO, pageScrollTo); + registerViewMethod(pageId, API_LOAD_FONT_FACE, loadFontFace); } window.uni = uni$1; window.UniViewJSBridge = UniViewJSBridge$1; diff --git a/packages/uni-app-plus/src/service/api/route/navigateTo.ts b/packages/uni-app-plus/src/service/api/route/navigateTo.ts index 95b7add50d998947c18589dfc3fde49bdbbf8674..33424abbfa1091de8f02624d13cf67f319ced84a 100644 --- a/packages/uni-app-plus/src/service/api/route/navigateTo.ts +++ b/packages/uni-app-plus/src/service/api/route/navigateTo.ts @@ -1,4 +1,4 @@ -import { ON_HIDE, parseUrl } from '@dcloudio/uni-shared' +import { EventChannel, ON_HIDE, parseUrl } from '@dcloudio/uni-shared' import { getRouteMeta, invokeHook } from '@dcloudio/uni-core' import { API_NAVIGATE_TO, @@ -13,12 +13,13 @@ import { ANI_DURATION, ANI_SHOW } from '../../constants' import { navigate, RouteOptions } from './utils' import { showWebview } from './webview' import { registerPage } from '../../framework/page' +import { getWebviewId } from '../../framework/webview/utils' export const $navigateTo: DefineAsyncApiFn = ( args, { resolve, reject } ) => { - const { url, animationType, animationDuration } = args + const { url, events, animationType, animationDuration } = args const { path, query } = parseUrl(url) const [aniType, aniDuration] = initAnimation( path, @@ -32,6 +33,7 @@ export const $navigateTo: DefineAsyncApiFn = ( url, path, query, + events, aniType, aniDuration, }) @@ -50,6 +52,7 @@ export const navigateTo = defineAsyncApi( ) interface NavigateToOptions extends RouteOptions { + events: Record aniType: string aniDuration: number } @@ -58,19 +61,20 @@ function _navigateTo({ url, path, query, + events, aniType, aniDuration, -}: NavigateToOptions): Promise { - // TODO eventChannel +}: NavigateToOptions): Promise { // 当前页面触发 onHide invokeHook(ON_HIDE) + const eventChannel = new EventChannel(getWebviewId() + 1, events) return new Promise((resolve) => { showWebview( - registerPage({ url, path, query, openType: 'navigateTo' }), + registerPage({ url, path, query, openType: 'navigateTo', eventChannel }), aniType, aniDuration, () => { - resolve(undefined) + resolve({ eventChannel }) } ) }) diff --git a/packages/uni-app-plus/src/service/framework/page/register.ts b/packages/uni-app-plus/src/service/framework/page/register.ts index 8b287c206bed7af5d2c7020658f638a464918b9b..faedb834f234a3d57f81806a7b4e326e7330756d 100644 --- a/packages/uni-app-plus/src/service/framework/page/register.ts +++ b/packages/uni-app-plus/src/service/framework/page/register.ts @@ -1,6 +1,7 @@ import { ComponentPublicInstance } from 'vue' import { hasOwn } from '@vue/shared' import { + EventChannel, formatLog, NAVBAR_HEIGHT, ON_REACH_BOTTOM_DISTANCE, @@ -25,7 +26,7 @@ interface RegisterPageOptions { query: Record openType: UniApp.OpenType webview?: PlusWebviewWebviewObject - // eventChannel: unknown + eventChannel?: EventChannel } export function registerPage({ @@ -34,6 +35,7 @@ export function registerPage({ query, openType, webview, + eventChannel, }: RegisterPageOptions) { // fast 模式,nvue 首页时,会在nvue中主动调用registerPage并传入首页webview,此时初始化一下首页(因为此时可能还未调用registerApp) if (webview) { @@ -56,7 +58,9 @@ export function registerPage({ } webview = undefined } else { - // TODO eventChannel + if (eventChannel) { + _webview.__page__.$page.eventChannel = eventChannel + } addCurrentPage(_webview.__page__) if (__DEV__) { console.log( @@ -97,14 +101,19 @@ export function registerPage({ openType, url, query, - routeOptions.meta + routeOptions.meta, + eventChannel ) initNVueEntryPage(webview) if ((webview as any).nvue) { // nvue 时,先启用一个占位 vm - const fakeNVueVm = createNVueVm(webview, pageInstance) + const fakeNVueVm = createNVueVm( + parseInt(webview.id!), + webview, + pageInstance + ) initPageVm(fakeNVueVm, pageInstance) addCurrentPage(fakeNVueVm) } else { @@ -162,6 +171,7 @@ function initNVueEntryPage(webview: PlusWebviewWebviewObject) { } function createNVueVm( + pageId: number, webview: PlusWebviewWebviewObject, pageInstance: Page.PageInstance['$page'] ) { @@ -170,6 +180,7 @@ function createNVueVm( onNVuePageCreated(vm: ComponentPublicInstance, curNVuePage: unknown) { ;(vm as any).$ = {} // 补充一个 nvue 的 $ 对象,模拟 vue3 的,不然有部分地方访问了 $ vm.$getAppWebview = () => webview + vm.getOpenerEventChannel = (curNVuePage as any).getOpenerEventChannel // 替换真实的 nvue 的 vm initPageVm(vm, pageInstance) const pages = getAllPages() @@ -184,5 +195,11 @@ function createNVueVm( $getAppWebview() { return webview }, + getOpenerEventChannel() { + if (!pageInstance.eventChannel) { + pageInstance.eventChannel = new EventChannel(pageId) + } + return pageInstance.eventChannel as EventChannel + }, } as unknown as ComponentPublicInstance } diff --git a/packages/uni-app-plus/src/service/framework/page/setup.ts b/packages/uni-app-plus/src/service/framework/page/setup.ts index ba702146cd688992bb11cb4818c7638cce0f2265..5cc0c46569b232cb02c02228c17d58af3c50f94c 100644 --- a/packages/uni-app-plus/src/service/framework/page/setup.ts +++ b/packages/uni-app-plus/src/service/framework/page/setup.ts @@ -1,5 +1,10 @@ import { initPageVm, invokeHook } from '@dcloudio/uni-core' -import { formatLog, ON_READY, ON_UNLOAD } from '@dcloudio/uni-shared' +import { + EventChannel, + formatLog, + ON_READY, + ON_UNLOAD, +} from '@dcloudio/uni-shared' import { ComponentPublicInstance, getCurrentInstance, @@ -22,7 +27,13 @@ export function setupPage(component: VuePageComponent) { const instance = getCurrentInstance()! const pageVm = instance.proxy! initPageVm(pageVm, __pageInstance as Page.PageInstance['$page']) - addCurrentPage(initScope(__pageId as number, pageVm)) + addCurrentPage( + initScope( + __pageId as number, + pageVm, + __pageInstance as Page.PageInstance['$page'] + ) + ) onMounted(() => { invokeHook(pageVm, ON_READY) // TODO preloadSubPackages @@ -37,7 +48,11 @@ export function setupPage(component: VuePageComponent) { return component } -function initScope(pageId: number, vm: ComponentPublicInstance) { +function initScope( + pageId: number, + vm: ComponentPublicInstance, + pageInstance: Page.PageInstance['$page'] +) { const $getAppWebview = () => { return plus.webview.getWebviewById(pageId + '') } @@ -45,5 +60,11 @@ function initScope(pageId: number, vm: ComponentPublicInstance) { vm.$scope = { $getAppWebview, } + vm.getOpenerEventChannel = () => { + if (!pageInstance.eventChannel) { + pageInstance.eventChannel = new EventChannel(pageId) + } + return pageInstance.eventChannel as EventChannel + } return vm } diff --git a/packages/uni-core/src/helpers/page.ts b/packages/uni-core/src/helpers/page.ts index 06d8df64ac7df4defd933157edd4b5b191d6f984..e0e5b358ac213d738a4308b151a5a542ece5427a 100644 --- a/packages/uni-core/src/helpers/page.ts +++ b/packages/uni-core/src/helpers/page.ts @@ -1,3 +1,4 @@ +import { EventChannel } from '@dcloudio/uni-shared' import { extend } from '@vue/shared' import { ComponentPublicInstance, getCurrentInstance } from 'vue' import { rpx2px } from './util' @@ -108,7 +109,8 @@ export function initPageInternalInstance( openType: UniApp.OpenType, url: string, pageQuery: Record, - meta: UniApp.PageRouteMeta + meta: UniApp.PageRouteMeta, + eventChannel?: EventChannel ): Page.PageInstance['$page'] { const { id, route } = meta return { @@ -119,6 +121,7 @@ export function initPageInternalInstance( options: pageQuery, meta, openType, + eventChannel, statusBarStyle: meta.navigationBar.titleColor === '#000000' ? 'dark' : 'light', } diff --git a/packages/uni-h5/dist/uni-h5.cjs.js b/packages/uni-h5/dist/uni-h5.cjs.js index c3752bdaa1a1746a6b6a2599e6af07999577caa1..aef5acb5addce580c8ccd236702c2a3a61e61b6d 100644 --- a/packages/uni-h5/dist/uni-h5.cjs.js +++ b/packages/uni-h5/dist/uni-h5.cjs.js @@ -505,7 +505,7 @@ function normalizePullToRefreshRpx(pullToRefresh) { } return pullToRefresh; } -function initPageInternalInstance(openType, url, pageQuery, meta) { +function initPageInternalInstance(openType, url, pageQuery, meta, eventChannel) { const { id, route } = meta; return { id, @@ -515,6 +515,7 @@ function initPageInternalInstance(openType, url, pageQuery, meta) { options: pageQuery, meta, openType, + eventChannel, statusBarStyle: meta.navigationBar.titleColor === "#000000" ? "dark" : "light" }; } diff --git a/packages/uni-h5/dist/uni-h5.es.js b/packages/uni-h5/dist/uni-h5.es.js index 1e84b24e4fff083adfda4fd8940d0fee997dd6f0..9de0a2525355a50f03593e0c8994fd711b822688 100644 --- a/packages/uni-h5/dist/uni-h5.es.js +++ b/packages/uni-h5/dist/uni-h5.es.js @@ -479,8 +479,8 @@ const viewMethods = Object.create(null); function normalizeViewMethodName(pageId, name) { return pageId + "." + name; } -function subscribeViewMethod(pageId) { - UniViewJSBridge.subscribe(normalizeViewMethodName(pageId, INVOKE_VIEW_API), onInvokeViewMethod); +function subscribeViewMethod(pageId, wrapper2) { + UniViewJSBridge.subscribe(normalizeViewMethodName(pageId, INVOKE_VIEW_API), wrapper2 ? wrapper2(onInvokeViewMethod) : onInvokeViewMethod); } function unsubscribeViewMethod(pageId) { UniViewJSBridge.unsubscribe(normalizeViewMethodName(pageId, INVOKE_VIEW_API)); @@ -956,7 +956,7 @@ function normalizePullToRefreshRpx(pullToRefresh) { } return pullToRefresh; } -function initPageInternalInstance(openType, url, pageQuery, meta) { +function initPageInternalInstance(openType, url, pageQuery, meta, eventChannel) { const { id: id2, route } = meta; return { id: id2, @@ -966,6 +966,7 @@ function initPageInternalInstance(openType, url, pageQuery, meta) { options: pageQuery, meta, openType, + eventChannel, statusBarStyle: meta.navigationBar.titleColor === "#000000" ? "dark" : "light" }; }