From 54e84b3b2df01fa66d322c43f84ab570e5bb164e Mon Sep 17 00:00:00 2001 From: qiang Date: Wed, 4 Aug 2021 14:24:11 +0800 Subject: [PATCH] feat(App): subNVue --- package.json | 2 +- .../uni-app-plus/dist/uni-app-service.es.js | 178 ++++++++++++++++++ .../uni-app-plus/dist/uni-app-view.umd.js | 2 +- packages/uni-app-plus/src/constants.ts | 2 + .../uni-app-plus/src/service/api/index.ts | 1 + .../src/service/api/ui/subNVue.ts | 123 ++++++++++++ .../framework/webview/init/subNVues.ts | 119 +++++++++++- .../uni-app-plus/src/view/bridge/index.ts | 2 +- .../uni-cli-nvue/lib/get-current-sub-nvue.js | 4 +- yarn.lock | 8 +- 10 files changed, 431 insertions(+), 10 deletions(-) create mode 100644 packages/uni-app-plus/src/service/api/ui/subNVue.ts diff --git a/package.json b/package.json index a1a60b900..345f0d0bd 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ }, "devDependencies": { "@babel/preset-env": "^7.14.7", - "@dcloudio/types": "^2.4.4", + "@dcloudio/types": "^2.4.5", "@jest/types": "^27.0.2", "@microsoft/api-extractor": "^7.13.2", "@rollup/plugin-alias": "^3.1.1", 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 bb0c3c850..8c15fa677 100644 --- a/packages/uni-app-plus/dist/uni-app-service.es.js +++ b/packages/uni-app-plus/dist/uni-app-service.es.js @@ -8704,6 +8704,7 @@ var serviceContext = (function (vue) { }); const VD_SYNC = 'vdSync'; + const APP_SERVICE_ID = '__uniapp__service'; const ON_WEBVIEW_READY = 'onWebviewReady'; const PAGE_SCROLL_TO = 'pageScrollTo'; const LOAD_FONT_FACE = 'loadFontFace'; @@ -8863,6 +8864,94 @@ var serviceContext = (function (vue) { resolve(); }, HideTabBarRedDotProtocol, HideTabBarRedDotOptions); + const EVENT_TYPE_NAME = 'UniAppSubNVue'; + class SubNvue { + constructor(id, isSub) { + this.callbacks = []; + const webview = (this.webview = plus.webview.getWebviewById(id)); + this.isSub = isSub || false; + if (webview.__uniapp_mask_id) { + const maskWebview = (this.maskWebview = + webview.__uniapp_mask_id === '0' + ? { + setStyle({ mask }) { + requireNativePlugin('uni-tabview').setMask({ + color: mask, + }); + }, + } + : plus.webview.getWebviewById(webview.__uniapp_mask_id)); + const closeMask = function () { + maskWebview.setStyle({ + mask: 'none', + }); + }; + webview.addEventListener('hide', closeMask); + webview.addEventListener('close', closeMask); + } + } + show(...args) { + if (this.maskWebview) { + const maskColor = this.webview.__uniapp_mask; + this.maskWebview.setStyle({ + mask: maskColor, + }); + } + this.webview.show(...args); + } + hide(...args) { + this.webview.hide(...args); + } + setStyle(style) { + this.webview.setStyle(style); + } + initMessage() { + if (this.messageReady) { + return; + } + this.messageReady = true; + const listener = (event) => { + if (event.data && event.data.type === EVENT_TYPE_NAME) { + const target = event.data.target; + if (target.id === this.webview.id && target.isSub === this.isSub) { + this.callbacks.forEach((callback) => { + callback({ + origin: this.webview.__uniapp_host, + data: event.data.data, + }); + }); + } + } + }; + const globalEvent = requireNativePlugin('globalEvent'); + globalEvent.addEventListener('plusMessage', listener); + this.webview.addEventListener('close', () => { + // TODO 暂时仅清空回调 + this.callbacks.length = 0; + // globalEvent.removeEventListener('plusMessage', listener) + }); + } + postMessage(data) { + const webviewExt = plus.webview; + webviewExt.postMessageToUniNView({ + type: EVENT_TYPE_NAME, + data, + target: { + id: this.webview.id, + isSub: !this.isSub, + }, + }, APP_SERVICE_ID); + } + onMessage(callback) { + this.initMessage(); + this.callbacks.push(callback); + } + } + const getSubNVueById = function (id, isSub) { + // TODO 暂时通过 isSub 区分来自 subNVue 页面 + return new SubNvue(id, isSub); + }; + const providers = { oauth(callback) { plus.oauth.getServices((services) => { @@ -9971,12 +10060,100 @@ var serviceContext = (function (vue) { webview.setStyle(webviewStyle); } + function initSubNVues(webview, path, routeMeta) { + const subNVues = routeMeta.subNVues || []; + subNVues.forEach((subNVue) => { + if (!subNVue.path) { + return; + } + const style = subNVue.style || {}; + const isNavigationBar = subNVue.type === 'navigationBar'; + const isPopup = subNVue.type === 'popup'; + style.uniNView = { + path: subNVue.path.replace('.nvue', '.js'), + defaultFontSize: __uniConfig.defaultFontSize, + viewport: __uniConfig.viewport, + }; + const extras = { + __uniapp_host: path, + __uniapp_origin: style.uniNView.path.split('?')[0].replace('.js', ''), + __uniapp_origin_id: webview.id, + __uniapp_origin_type: webview.__uniapp_type, + }; + let maskWebview; + if (isNavigationBar) { + style.position = 'dock'; + style.dock = 'top'; + style.top = '0'; + style.width = '100%'; + style.height = String(NAVBAR_HEIGHT + getStatusbarHeight()); + delete style.left; + delete style.right; + delete style.bottom; + delete style.margin; + } + else if (isPopup) { + style.position = 'absolute'; + if (isTabBarPage$1(path)) { + maskWebview = tabBar$1; + } + else { + maskWebview = webview; + } + extras.__uniapp_mask = style.mask || 'rgba(0,0,0,0.5)'; + extras.__uniapp_mask_id = maskWebview.id; + } + delete style.mask; + const subNVueWebview = plus.webview.create('', subNVue.id, style, extras); + if (isPopup) { + if (!maskWebview.popupSubNVueWebviews) { + maskWebview.popupSubNVueWebviews = {}; + } + maskWebview.popupSubNVueWebviews[subNVueWebview.id] = subNVueWebview; + const hideSubNVue = function () { + maskWebview.setStyle({ + mask: 'none', + }); + subNVueWebview.hide('auto'); + }; + maskWebview.addEventListener('maskClick', hideSubNVue); + let isRemoved = false; // 增加个 remove 标记,防止出错 + subNVueWebview.addEventListener('show', () => { + if (!isRemoved) { + plus.key.removeEventListener('backbutton', backbuttonListener); + plus.key.addEventListener('backbutton', hideSubNVue); + isRemoved = true; + } + }); + subNVueWebview.addEventListener('hide', () => { + if (isRemoved) { + plus.key.removeEventListener('backbutton', hideSubNVue); + plus.key.addEventListener('backbutton', backbuttonListener); + isRemoved = false; + } + }); + subNVueWebview.addEventListener('close', () => { + delete maskWebview.popupSubNVueWebviews[subNVueWebview.id]; + if (isRemoved) { + plus.key.removeEventListener('backbutton', hideSubNVue); + plus.key.addEventListener('backbutton', backbuttonListener); + isRemoved = false; + } + }); + } + else { + webview.append(subNVueWebview); + } + }); + } + function initWebview(webview, path, query, routeMeta) { // 首页或非 nvue 页面 if (webview.id === '1' || !routeMeta.isNVue) { // path 必须参数为空,因为首页已经在 manifest.json 中设置了 uniNView,不能再次设置,否则会二次加载 initWebviewStyle(webview, '', query, routeMeta); } + initSubNVues(webview, path, routeMeta); initWebviewEvent(webview); } @@ -11678,6 +11855,7 @@ var serviceContext = (function (vue) { showTabBarRedDot: showTabBarRedDot, removeTabBarBadge: removeTabBarBadge, hideTabBarRedDot: hideTabBarRedDot, + getSubNVueById: getSubNVueById, getProvider: getProvider, login: login, getUserInfo: getUserInfo, 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 85e3bd934..ccffb1156 100644 --- a/packages/uni-app-plus/dist/uni-app-view.umd.js +++ b/packages/uni-app-plus/dist/uni-app-view.umd.js @@ -5497,6 +5497,7 @@ } [ON_PAGE_SCROLL, ON_REACH_BOTTOM]; const VD_SYNC = "vdSync"; + const APP_SERVICE_ID = "__uniapp__service"; const ON_WEBVIEW_READY = "onWebviewReady"; const PAGE_SCROLL_TO = "pageScrollTo"; const LOAD_FONT_FACE = "loadFontFace"; @@ -5504,7 +5505,6 @@ const WEBVIEW_INSERTED = "webviewInserted"; const WEBVIEW_REMOVED = "webviewRemoved"; const WEBVIEW_ID_PREFIX = "webviewId"; - const APP_SERVICE_ID = "__uniapp__service"; const UniViewJSBridge$1 = /* @__PURE__ */ extend(ViewJSBridge, { publishHandler }); diff --git a/packages/uni-app-plus/src/constants.ts b/packages/uni-app-plus/src/constants.ts index f5555197a..47af276e2 100644 --- a/packages/uni-app-plus/src/constants.ts +++ b/packages/uni-app-plus/src/constants.ts @@ -1,5 +1,7 @@ export const VD_SYNC = 'vdSync' +export const APP_SERVICE_ID = '__uniapp__service' + export const ON_WEBVIEW_READY = 'onWebviewReady' export const PAGE_SCROLL_TO = 'pageScrollTo' diff --git a/packages/uni-app-plus/src/service/api/index.ts b/packages/uni-app-plus/src/service/api/index.ts index 6b9147e22..47825adc1 100644 --- a/packages/uni-app-plus/src/service/api/index.ts +++ b/packages/uni-app-plus/src/service/api/index.ts @@ -52,6 +52,7 @@ export * from './ui/loadFontFace' export * from './ui/pageScrollTo' export * from './ui/navigationBar' export * from './ui/tabBar' +export * from './ui/subNVue' export * from './plugin/getProvider' export * from './plugin/oauth' diff --git a/packages/uni-app-plus/src/service/api/ui/subNVue.ts b/packages/uni-app-plus/src/service/api/ui/subNVue.ts new file mode 100644 index 000000000..9b0d8b6ea --- /dev/null +++ b/packages/uni-app-plus/src/service/api/ui/subNVue.ts @@ -0,0 +1,123 @@ +import { APP_SERVICE_ID } from '../.../../../../constants' +import { requireNativePlugin } from '../base/requireNativePlugin' +import { PlusWebviewWebviewObjectWithExtras } from '../../framework/webview/init/subNVues' + +const EVENT_TYPE_NAME = 'UniAppSubNVue' + +interface OnMessageResult { + origin: string + data: any +} +type AnyFn = (res: OnMessageResult) => void + +interface GlobalEventData { + type?: string + data: any + target?: { + id: string + isSub: boolean + } +} + +interface WebviewExt extends PlusWebview { + postMessageToUniNView: (message: GlobalEventData, id: string) => void +} + +class SubNvue implements ReturnType { + private webview: PlusWebviewWebviewObjectWithExtras + private maskWebview?: PlusWebviewWebviewObject + private callbacks: AnyFn[] = [] + private isSub: boolean + private messageReady?: boolean + + constructor(id: string, isSub?: boolean) { + const webview = (this.webview = plus.webview.getWebviewById( + id + ) as PlusWebviewWebviewObjectWithExtras) + this.isSub = isSub || false + if (webview.__uniapp_mask_id) { + const maskWebview = (this.maskWebview = + webview.__uniapp_mask_id === '0' + ? ({ + setStyle({ mask }) { + requireNativePlugin('uni-tabview').setMask({ + color: mask, + }) + }, + } as PlusWebviewWebviewObject) + : plus.webview.getWebviewById(webview.__uniapp_mask_id)) + + const closeMask = function () { + maskWebview.setStyle({ + mask: 'none', + }) + } + webview.addEventListener('hide', closeMask) + webview.addEventListener('close', closeMask) + } + } + show(...args: any[]) { + if (this.maskWebview) { + const maskColor = this.webview.__uniapp_mask + this.maskWebview.setStyle({ + mask: maskColor, + }) + } + this.webview.show(...args) + } + hide(...args: any[]) { + this.webview.hide(...args) + } + setStyle(style: PlusWebviewWebviewStyles) { + this.webview.setStyle(style) + } + private initMessage() { + if (this.messageReady) { + return + } + this.messageReady = true + const listener = (event: { data?: GlobalEventData }) => { + if (event.data && event.data.type === EVENT_TYPE_NAME) { + const target = event.data.target! + if (target.id === this.webview.id && target.isSub === this.isSub) { + this.callbacks.forEach((callback) => { + callback({ + origin: this.webview.__uniapp_host, + data: event.data!.data, + }) + }) + } + } + } + const globalEvent = requireNativePlugin('globalEvent') + globalEvent.addEventListener('plusMessage', listener) + this.webview.addEventListener('close', () => { + // TODO 暂时仅清空回调 + this.callbacks.length = 0 + // globalEvent.removeEventListener('plusMessage', listener) + }) + } + postMessage(data: any) { + const webviewExt = plus.webview as WebviewExt + webviewExt.postMessageToUniNView( + { + type: EVENT_TYPE_NAME, + data, + target: { + id: this.webview.id, + isSub: !this.isSub, + }, + }, + APP_SERVICE_ID + ) + } + onMessage(callback: AnyFn) { + this.initMessage() + this.callbacks.push(callback) + } +} + +export const getSubNVueById = function (id: string, isSub?: boolean) { + // TODO 暂时通过 isSub 区分来自 subNVue 页面 + return new SubNvue(id, isSub) +} diff --git a/packages/uni-app-plus/src/service/framework/webview/init/subNVues.ts b/packages/uni-app-plus/src/service/framework/webview/init/subNVues.ts index a1627bcfb..f44ace0d0 100644 --- a/packages/uni-app-plus/src/service/framework/webview/init/subNVues.ts +++ b/packages/uni-app-plus/src/service/framework/webview/init/subNVues.ts @@ -1,5 +1,122 @@ +import { NAVBAR_HEIGHT } from '@dcloudio/uni-shared' +import { getStatusbarHeight } from '../../../../helpers/statusBar' +import { isTabBarPage } from '../../../../helpers/plus' +import tabBar from '../../app/tabBar' +import { backbuttonListener } from '../../app/utils' + +interface Extras { + __uniapp_host: string + __uniapp_origin: string + __uniapp_origin_id: string + __uniapp_origin_type: string + __uniapp_mask?: string + __uniapp_mask_id?: string +} + +export interface PlusWebviewWebviewObjectWithExtras + extends PlusWebviewWebviewObject, + Extras {} + export function initSubNVues( webview: PlusWebviewWebviewObject, path: string, routeMeta: UniApp.PageRouteMeta -) {} +) { + const subNVues = routeMeta.subNVues || [] + subNVues.forEach((subNVue) => { + if (!subNVue.path) { + return + } + interface StyleExt extends PlusWebviewWebviewStyles { + uniNView?: { + path: string + defaultFontSize?: number + viewport?: number + } + } + const style: StyleExt = (subNVue.style as StyleExt) || {} + const isNavigationBar = subNVue.type === 'navigationBar' + const isPopup = subNVue.type === 'popup' + + style.uniNView = { + path: subNVue.path.replace('.nvue', '.js'), + defaultFontSize: (__uniConfig as any).defaultFontSize, + viewport: (__uniConfig as any).viewport, + } + + const extras: Extras = { + __uniapp_host: path, + __uniapp_origin: style.uniNView.path.split('?')[0].replace('.js', ''), + __uniapp_origin_id: webview.id, + __uniapp_origin_type: (webview as any).__uniapp_type, + } + + interface MaskWebview extends PlusWebviewWebviewObject { + popupSubNVueWebviews: Record + } + let maskWebview: MaskWebview + + if (isNavigationBar) { + style.position = 'dock' + style.dock = 'top' + style.top = '0' + style.width = '100%' + style.height = String(NAVBAR_HEIGHT + getStatusbarHeight()) + delete style.left + delete style.right + delete style.bottom + delete style.margin + } else if (isPopup) { + style.position = 'absolute' + if (isTabBarPage(path)) { + maskWebview = tabBar as any + } else { + maskWebview = webview as MaskWebview + } + extras.__uniapp_mask = style.mask || 'rgba(0,0,0,0.5)' + extras.__uniapp_mask_id = maskWebview.id + } + + delete style.mask + const subNVueWebview = plus.webview.create('', subNVue.id, style, extras) + + if (isPopup) { + if (!maskWebview!.popupSubNVueWebviews) { + maskWebview!.popupSubNVueWebviews = {} + } + maskWebview!.popupSubNVueWebviews[subNVueWebview.id] = subNVueWebview + const hideSubNVue = function () { + maskWebview.setStyle({ + mask: 'none', + }) + subNVueWebview.hide('auto') + } + maskWebview!.addEventListener('maskClick', hideSubNVue) + let isRemoved = false // 增加个 remove 标记,防止出错 + subNVueWebview.addEventListener('show', () => { + if (!isRemoved) { + plus.key.removeEventListener('backbutton', backbuttonListener) + plus.key.addEventListener('backbutton', hideSubNVue) + isRemoved = true + } + }) + subNVueWebview.addEventListener('hide', () => { + if (isRemoved) { + plus.key.removeEventListener('backbutton', hideSubNVue) + plus.key.addEventListener('backbutton', backbuttonListener) + isRemoved = false + } + }) + subNVueWebview.addEventListener('close', () => { + delete maskWebview.popupSubNVueWebviews[subNVueWebview.id] + if (isRemoved) { + plus.key.removeEventListener('backbutton', hideSubNVue) + plus.key.addEventListener('backbutton', backbuttonListener) + isRemoved = false + } + }) + } else { + webview.append(subNVueWebview) + } + }) +} diff --git a/packages/uni-app-plus/src/view/bridge/index.ts b/packages/uni-app-plus/src/view/bridge/index.ts index b76ed6947..d4350ef31 100644 --- a/packages/uni-app-plus/src/view/bridge/index.ts +++ b/packages/uni-app-plus/src/view/bridge/index.ts @@ -2,7 +2,7 @@ import { extend } from '@vue/shared' import { getCurrentPageId, ViewJSBridge } from '@dcloudio/uni-core' -const APP_SERVICE_ID = '__uniapp__service' +import { APP_SERVICE_ID } from '../../constants' export const UniViewJSBridge = /*#__PURE__*/ extend(ViewJSBridge, { publishHandler, diff --git a/packages/uni-cli-nvue/lib/get-current-sub-nvue.js b/packages/uni-cli-nvue/lib/get-current-sub-nvue.js index a3f1369ed..27d754302 100644 --- a/packages/uni-cli-nvue/lib/get-current-sub-nvue.js +++ b/packages/uni-cli-nvue/lib/get-current-sub-nvue.js @@ -1,3 +1,3 @@ export default function getCurrentSubNVue() { - return uni.getSubNVueById(plus.webview.currentWebview().id) -} + return uni.getSubNVueById(plus.webview.currentWebview().id, true) +} diff --git a/yarn.lock b/yarn.lock index a3e7c9e77..f149b3811 100644 --- a/yarn.lock +++ b/yarn.lock @@ -922,10 +922,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@dcloudio/types@^2.4.4": - version "2.4.4" - resolved "https://registry.yarnpkg.com/@dcloudio/types/-/types-2.4.4.tgz#ae8b09aa0ae51d72d625e1061b4e6948c8ea01d5" - integrity sha512-EzUqM5uDg3ZauhnDj9bJp8OVrO/J1JHRktrO3kkoEtK6sECU/F87GtVVpVActyKAMLOSw65yIfeF17Yl+ctaNA== +"@dcloudio/types@^2.4.5": + version "2.4.5" + resolved "https://registry.yarnpkg.com/@dcloudio/types/-/types-2.4.5.tgz#3ead18599a6da0052afb814b522bc007043d814c" + integrity sha512-6PUP4D4PZgm5Zc7hAegNZZreJN6D9VK8bEp/oHG3tevVJ0w9+C/9PBgcX1kStxIwqYcfR0dv8jTyjG08BF185Q== "@eslint/eslintrc@^0.4.3": version "0.4.3" -- GitLab