From d16353210cf4aced58b0fc8ee72f9d15c76325e0 Mon Sep 17 00:00:00 2001 From: DCloud_LXH <283700113@qq.com> Date: Wed, 28 Jul 2021 17:33:57 +0800 Subject: [PATCH] feat(App): webview --- packages/uni-app-plus/src/constants.ts | 5 + .../service/framework/app/subscriber/index.ts | 22 ++- .../app/subscriber/webviewLifecycle.ts | 17 +++ .../src/service/framework/dom/Page.ts | 24 ++++ .../src/service/onWebInvokeAppService.ts | 47 +++++++ .../src/view/components/map/index.tsx | 19 +-- .../src/view/components/web-view/index.tsx | 127 ++++++++++++++++++ .../framework/dom/components/UniWebView.ts | 1 + packages/uni-app-plus/style/webview.css | 8 ++ .../src/view/components/web-view/index.tsx | 8 +- packages/uni-shared/src/vdom/Event.ts | 2 +- packages/uni-shared/src/vdom/index.ts | 1 + 12 files changed, 268 insertions(+), 13 deletions(-) create mode 100644 packages/uni-app-plus/src/service/framework/app/subscriber/webviewLifecycle.ts create mode 100644 packages/uni-app-plus/src/service/onWebInvokeAppService.ts create mode 100644 packages/uni-app-plus/style/webview.css diff --git a/packages/uni-app-plus/src/constants.ts b/packages/uni-app-plus/src/constants.ts index abd4abd86..f5555197a 100644 --- a/packages/uni-app-plus/src/constants.ts +++ b/packages/uni-app-plus/src/constants.ts @@ -16,3 +16,8 @@ export const ACTION_TYPE_DICT = 0 export type Value = string | number | boolean | null export type Dictionary = Value[] export type DictAction = [typeof ACTION_TYPE_DICT, Dictionary] + +export const WEBVIEW_INSERTED = 'webviewInserted' +export const WEBVIEW_REMOVED = 'webviewRemoved' +export const WEBVIEW_ID_PREFIX = 'webviewId' +export const WEB_INVOKE_APPSERVICE = 'WEB_INVOKE_APPSERVICE' diff --git a/packages/uni-app-plus/src/service/framework/app/subscriber/index.ts b/packages/uni-app-plus/src/service/framework/app/subscriber/index.ts index 77fbd329c..bd3bc4704 100644 --- a/packages/uni-app-plus/src/service/framework/app/subscriber/index.ts +++ b/packages/uni-app-plus/src/service/framework/app/subscriber/index.ts @@ -1,10 +1,21 @@ import { subscribeServiceMethod } from '@dcloudio/uni-core' -import { ON_WEBVIEW_READY, VD_SYNC } from '../../../../constants' +import { + ON_WEBVIEW_READY, + VD_SYNC, + WEBVIEW_INSERTED, + WEBVIEW_REMOVED, + WEB_INVOKE_APPSERVICE, +} from '../../../../constants' import { onVdSync } from '../../dom' import { onPlusMessage } from '../initGlobalEvent' import { subscribeAd } from './ad' import { subscribeNavigator } from './navigator' import { subscribeWebviewReady } from './webviewReady' +import { onWebviewInserted, onWebviewRemoved } from './webviewLifeCycle' +import { + onWebInvokeAppService, + WebInvokeAppService, +} from '../../../onWebInvokeAppService' export function initSubscribeHandlers() { const { subscribe, subscribeHandler } = UniServiceJSBridge @@ -16,6 +27,13 @@ export function initSubscribeHandlers() { } ) + onPlusMessage<{ + data: Parameters[0] + webviewIds: string[] + }>(WEB_INVOKE_APPSERVICE, ({ data, webviewIds }) => { + onWebInvokeAppService(data, webviewIds) + }) + if (__uniConfig.renderer !== 'native') { // 非纯原生 subscribe(ON_WEBVIEW_READY, subscribeWebviewReady) @@ -23,5 +41,7 @@ export function initSubscribeHandlers() { subscribeServiceMethod() subscribeAd() subscribeNavigator() + subscribe(WEBVIEW_INSERTED, onWebviewInserted) + subscribe(WEBVIEW_REMOVED, onWebviewRemoved) } } diff --git a/packages/uni-app-plus/src/service/framework/app/subscriber/webviewLifecycle.ts b/packages/uni-app-plus/src/service/framework/app/subscriber/webviewLifecycle.ts new file mode 100644 index 000000000..0f01855b1 --- /dev/null +++ b/packages/uni-app-plus/src/service/framework/app/subscriber/webviewLifecycle.ts @@ -0,0 +1,17 @@ +function findPage(pageId: string) { + const page = getCurrentPages().find( + (page) => page.$page.id === parseInt(pageId) + ) + if (!page) { + return console.error(`Page[${pageId}] not found`) + } + return page +} +export function onWebviewInserted(data: any, pageId: string) { + const page = findPage(pageId) + page && ((page as any).__uniapp_webview = true) +} +export function onWebviewRemoved(data: any, pageId: string) { + const page = findPage(pageId) + page && delete (page as any).__uniapp_webview +} diff --git a/packages/uni-app-plus/src/service/framework/dom/Page.ts b/packages/uni-app-plus/src/service/framework/dom/Page.ts index d3f2c079c..d57b0e4d6 100644 --- a/packages/uni-app-plus/src/service/framework/dom/Page.ts +++ b/packages/uni-app-plus/src/service/framework/dom/Page.ts @@ -33,6 +33,7 @@ import { Value, VD_SYNC, } from '../../../constants' +import { getPageById } from '@dcloudio/uni-core' export default class UniPageNode extends UniNode implements IUniPageNode { pageId: number @@ -352,3 +353,26 @@ export function createPageNode( ) { return new UniPageNode(pageId, pageOptions, setup) } + +export function getPageNode(pageId: string): UniPageNode | null { + const page = getPageById(parseInt(pageId)) + if (!page) return null + return (page as any).__page_container__ as UniPageNode +} + +export function findNodeByTagName( + tagName: string, + uniNode: UniNode +): UniNode | null { + if (uniNode.nodeName === tagName.toLocaleUpperCase()) { + return uniNode + } + const { childNodes } = uniNode + for (let i = 0; i < childNodes.length; i++) { + const uniNode = findNodeByTagName(tagName, childNodes[i]) + if (uniNode) { + return uniNode + } + } + return null +} diff --git a/packages/uni-app-plus/src/service/onWebInvokeAppService.ts b/packages/uni-app-plus/src/service/onWebInvokeAppService.ts new file mode 100644 index 000000000..651e83580 --- /dev/null +++ b/packages/uni-app-plus/src/service/onWebInvokeAppService.ts @@ -0,0 +1,47 @@ +import { getPageNode, findNodeByTagName } from './framework/dom/Page' +import { createUniEvent } from '@dcloudio/uni-shared' + +type Name = + | 'navigateTo' + | 'navigateBack' + | 'switchTab' + | 'reLaunch' + | 'redirectTo' + | 'postMessage' +type WebInvokeData = { + name: Name + arg: any +} +export type WebInvokeAppService = ( + webInvokeData: WebInvokeData, + pageIds: string[] +) => void + +export const onWebInvokeAppService: WebInvokeAppService = ( + { name, arg }, + pageIds +) => { + if (name === 'postMessage') { + onMessage(pageIds[0], arg) + } else { + uni[name](arg) + } +} + +function onMessage(pageId: string, arg: any) { + const page = getPageNode(pageId) + if (!page) { + return + } + const uniNode = findNodeByTagName('web-view', page) + uniNode?.dispatchEvent( + createUniEvent({ + type: 'onMessage', + target: Object.create(null), + currentTarget: Object.create(null), + detail: { + data: [arg], + }, + }) + ) +} diff --git a/packages/uni-app-plus/src/view/components/map/index.tsx b/packages/uni-app-plus/src/view/components/map/index.tsx index 2b4f1f720..6fd65b354 100644 --- a/packages/uni-app-plus/src/view/components/map/index.tsx +++ b/packages/uni-app-plus/src/view/components/map/index.tsx @@ -247,6 +247,7 @@ export default /*#__PURE__*/ defineBuiltInComponent({ onBeforeUnmount(() => { if (map) { map.close() + _setMap(null) } }) @@ -274,7 +275,7 @@ export default /*#__PURE__*/ defineBuiltInComponent({ type Callback = (res: any) => void function useMapMethods(props: Props, trigger: CustomEventTrigger) { - let map: Map + let map: Map | null function moveToLocation( resolve: Callback, { @@ -368,18 +369,18 @@ function useMapMethods(props: Props, trigger: CustomEventTrigger) { } } } - map.addOverlay(nativeMarker as unknown as PlusMapsOverlay) + map?.addOverlay(nativeMarker as unknown as PlusMapsOverlay) // 此处5+文档中PlusMapsMarker对象只有方法,没有属性 // @ts-expect-error map.__markers__.push(nativeMarker) - map.__markers_map__[id + ''] = nativeMarker + map && (map.__markers_map__[id + ''] = nativeMarker) }) } function _clearMarkers() { if (!map) return const markers = map.__markers__ markers.forEach((marker) => { - map.removeOverlay(marker as unknown as PlusMapsOverlay) + map?.removeOverlay(marker as unknown as PlusMapsOverlay) }) map.__markers__ = [] map.__markers_map__ = {} @@ -396,7 +397,7 @@ function useMapMethods(props: Props, trigger: CustomEventTrigger) { if (!map) return if (map.__lines__.length > 0) { map.__lines__.forEach((circle) => { - map.removeOverlay(circle as unknown as PlusMapsOverlay) + map?.removeOverlay(circle as unknown as PlusMapsOverlay) }) map.__lines__ = [] } @@ -423,7 +424,7 @@ function useMapMethods(props: Props, trigger: CustomEventTrigger) { if (width) { polyline.setLineWidth(width) } - map.addOverlay(polyline as unknown as PlusMapsOverlay) + map?.addOverlay(polyline as unknown as PlusMapsOverlay) // 此处5+文档中PlusMapsPolyline对象只有方法,没有属性 // @ts-expect-error map.__lines__.push(polyline) @@ -433,7 +434,7 @@ function useMapMethods(props: Props, trigger: CustomEventTrigger) { if (!map) return if (map.__circles__.length > 0) { map.__circles__.forEach((circle) => { - map.removeOverlay(circle as unknown as PlusMapsOverlay) + map?.removeOverlay(circle as unknown as PlusMapsOverlay) }) map.__circles__ = [] } @@ -458,7 +459,7 @@ function useMapMethods(props: Props, trigger: CustomEventTrigger) { if (strokeWidth) { nativeCircle.setLineWidth(strokeWidth) } - map.addOverlay(nativeCircle as unknown as PlusMapsOverlay) + map?.addOverlay(nativeCircle as unknown as PlusMapsOverlay) // 此处5+文档中PlusMapsCircle对象只有方法,没有属性 // @ts-expect-error map.__circles__.push(nativeCircle) @@ -485,7 +486,7 @@ function useMapMethods(props: Props, trigger: CustomEventTrigger) { _addMarkers, _addMapLines, _addMapCircles, - _setMap(_map: Map) { + _setMap(_map: Map | null) { map = _map }, } diff --git a/packages/uni-app-plus/src/view/components/web-view/index.tsx b/packages/uni-app-plus/src/view/components/web-view/index.tsx index 349441444..6d6cac762 100644 --- a/packages/uni-app-plus/src/view/components/web-view/index.tsx +++ b/packages/uni-app-plus/src/view/components/web-view/index.tsx @@ -1,5 +1,132 @@ +import { ref, watch, onBeforeUnmount, Ref, computed } from 'vue' +import { extend } from '@vue/shared' import { defineBuiltInComponent } from '@dcloudio/uni-components' +import { getCurrentPageId } from '@dcloudio/uni-core' +import { getRealPath } from '../../../platform/getRealPath' +import { + WEBVIEW_INSERTED, + WEBVIEW_REMOVED, + WEBVIEW_ID_PREFIX, +} from '../../../constants' +import { NAVBAR_HEIGHT } from '@dcloudio/uni-shared' +import { useNative } from '../../../helpers/useNative' + +const props = { + src: { + type: String, + default: '', + }, + webviewStyles: { + type: Object, + default() { + return {} + }, + }, +} + +let webview: PlusWebviewWebviewObject | null + +const insertHTMLWebView = ({ + htmlId, + src, + webviewStyles, +}: { + htmlId: string + src: string + webviewStyles: PlusWebviewWebviewStyles +}) => { + const parentWebview = plus.webview.currentWebview() + // fixed by hxy web-view 组件所在的 webview 不注入 uni-app 框架 + const styles: PlusWebviewWebviewStyles & { + 'uni-app': string + isUniH5: boolean + } = extend(webviewStyles, { + 'uni-app': 'none', + isUniH5: true, + }) + const parentTitleNView = parentWebview.getTitleNView() + if (parentTitleNView) { + let defaultTop: number = NAVBAR_HEIGHT + parseFloat(styles.top || '0') + if (plus.navigator.isImmersedStatusbar()) { + defaultTop += plus.navigator.getStatusbarHeight() + } + styles.top = String(defaultTop) + styles.bottom = styles.bottom || '0' + } + webview = plus.webview.create(src, htmlId, styles) + if (parentTitleNView) { + webview.addEventListener('titleUpdate', function () { + const title = webview?.getTitle() + parentWebview.setStyle({ + titleNView: { + // iOS titleText 为空字符串时 按钮会隐藏 + titleText: !title || title === 'null' ? ' ' : title, + }, + }) + }) + } + plus.webview.currentWebview().append(webview as any) +} + +const removeHTMLWebView = () => { + plus.webview.currentWebview().remove(webview as any) + webview?.close('none') + webview = null +} export default /*#__PURE__*/ defineBuiltInComponent({ name: 'WebView', + props, + setup(props) { + const pageId = getCurrentPageId() + const containerRef: Ref = ref(null) + const { position, hidden, onParentReady } = useNative(containerRef) + + const webviewStyles = computed(() => props.webviewStyles) + + onParentReady(() => { + const htmlId = ref(WEBVIEW_ID_PREFIX + pageId) + insertHTMLWebView({ + htmlId: htmlId.value, + src: getRealPath(props.src), + webviewStyles: webviewStyles.value, + }) + UniViewJSBridge.publishHandler(WEBVIEW_INSERTED, {}, pageId) + if (hidden.value) webview?.hide() + }) + + onBeforeUnmount(() => { + removeHTMLWebView() + UniViewJSBridge.publishHandler(WEBVIEW_REMOVED, {}, pageId) + }) + + watch( + () => props.src, + (val) => { + const realPath = getRealPath(val) || '' + if (!realPath) { + return + } + if ( + /^(http|https):\/\//.test(realPath) && + props.webviewStyles.progress + ) { + webview?.setStyle({ + progress: { + color: props.webviewStyles.progress.color, + }, + }) + } + webview?.loadURL(realPath) + } + ) + watch(webviewStyles, (webviewStyles) => { + webview?.setStyle(webviewStyles) + }) + watch(hidden, (val) => { + webview && webview[val ? 'hide' : 'show']() + }) + + return () => + }, }) diff --git a/packages/uni-app-plus/src/view/framework/dom/components/UniWebView.ts b/packages/uni-app-plus/src/view/framework/dom/components/UniWebView.ts index 11d5c10a1..17d83e0ed 100644 --- a/packages/uni-app-plus/src/view/framework/dom/components/UniWebView.ts +++ b/packages/uni-app-plus/src/view/framework/dom/components/UniWebView.ts @@ -1,4 +1,5 @@ import { UniNodeJSON } from '@dcloudio/uni-shared' +import '../../../../../style/webview.css' import WebView from '../../../components/web-view' import { UniComponent } from './UniComponent' diff --git a/packages/uni-app-plus/style/webview.css b/packages/uni-app-plus/style/webview.css new file mode 100644 index 000000000..a0c3249c9 --- /dev/null +++ b/packages/uni-app-plus/style/webview.css @@ -0,0 +1,8 @@ +uni-web-view { + display: inline-block; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} diff --git a/packages/uni-h5/src/view/components/web-view/index.tsx b/packages/uni-h5/src/view/components/web-view/index.tsx index 43b57d691..89de0bf15 100644 --- a/packages/uni-h5/src/view/components/web-view/index.tsx +++ b/packages/uni-h5/src/view/components/web-view/index.tsx @@ -13,11 +13,15 @@ import { useAttrs, } from '@dcloudio/uni-components' import { getRealPath } from '@dcloudio/uni-platform' -import { updateElementStyle, once } from '@dcloudio/uni-shared' +import { + updateElementStyle, + once, + ON_WEB_INVOKE_APP_SERVICE, +} from '@dcloudio/uni-shared' import { onWebInvokeAppService } from '../../../service/onWebInvokeAppService' const Invoke = /*#__PURE__*/ once(() => - UniServiceJSBridge.on('onWebInvokeAppService', onWebInvokeAppService) + UniServiceJSBridge.on(ON_WEB_INVOKE_APP_SERVICE, onWebInvokeAppService) ) const props = { diff --git a/packages/uni-shared/src/vdom/Event.ts b/packages/uni-shared/src/vdom/Event.ts index f431c2c8f..ab435b0ea 100644 --- a/packages/uni-shared/src/vdom/Event.ts +++ b/packages/uni-shared/src/vdom/Event.ts @@ -60,7 +60,7 @@ export class UniEvent { } } -function createUniEvent(evt: Record) { +export function createUniEvent(evt: Record) { if (evt instanceof UniEvent) { return evt } diff --git a/packages/uni-shared/src/vdom/index.ts b/packages/uni-shared/src/vdom/index.ts index 22e91f210..2c99c4f96 100644 --- a/packages/uni-shared/src/vdom/index.ts +++ b/packages/uni-shared/src/vdom/index.ts @@ -5,6 +5,7 @@ export { UniEventListener, parseEventName, normalizeEventType, + createUniEvent, } from './Event' export { ATTR_CLASS, -- GitLab