experience-webview.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import { type H5BridgeMessage, type H5ExperienceRequest } from '../../game/experience/h5Experience'
  2. type ExperienceWebViewPageData = {
  3. pageTitle: string
  4. pageSubtitle: string
  5. presentation: 'sheet' | 'dialog' | 'fullscreen'
  6. webViewSrc: string
  7. webViewReady: boolean
  8. loadErrorText: string
  9. panelBodyHeightPx: number
  10. }
  11. let currentRequest: H5ExperienceRequest | null = null
  12. let currentEventChannel: WechatMiniprogram.EventChannel | null = null
  13. let pageResolved = false
  14. function appendQueryParam(url: string, key: string, value: string): string {
  15. const separator = url.indexOf('?') >= 0 ? '&' : '?'
  16. return `${url}${separator}${key}=${encodeURIComponent(value)}`
  17. }
  18. function buildWebViewSrc(request: H5ExperienceRequest): string {
  19. let nextUrl = request.url
  20. nextUrl = appendQueryParam(nextUrl, 'cmrBridge', request.bridgeVersion)
  21. nextUrl = appendQueryParam(nextUrl, 'cmrKind', request.kind)
  22. return nextUrl
  23. }
  24. function emitFallbackAndClose() {
  25. if (!currentRequest || !currentEventChannel) {
  26. return
  27. }
  28. if (!pageResolved) {
  29. pageResolved = true
  30. currentEventChannel.emit('fallback', currentRequest.fallback)
  31. }
  32. wx.navigateBack({
  33. fail: () => {},
  34. })
  35. }
  36. function emitCloseAndBack(payload?: Record<string, unknown>) {
  37. if (currentEventChannel && !pageResolved) {
  38. pageResolved = true
  39. currentEventChannel.emit('close', payload || {})
  40. }
  41. wx.navigateBack({
  42. fail: () => {},
  43. })
  44. }
  45. Page<ExperienceWebViewPageData, WechatMiniprogram.IAnyObject>({
  46. data: {
  47. pageTitle: '内容体验',
  48. pageSubtitle: '',
  49. presentation: 'sheet',
  50. webViewSrc: '',
  51. webViewReady: false,
  52. loadErrorText: '',
  53. panelBodyHeightPx: 420,
  54. },
  55. onLoad() {
  56. const systemInfo = wx.getSystemInfoSync()
  57. const windowHeight = typeof systemInfo.windowHeight === 'number' ? systemInfo.windowHeight : 700
  58. pageResolved = false
  59. currentRequest = null
  60. currentEventChannel = null
  61. this.setData({
  62. pageTitle: '内容体验',
  63. pageSubtitle: '',
  64. presentation: 'sheet',
  65. webViewSrc: '',
  66. webViewReady: false,
  67. loadErrorText: '',
  68. panelBodyHeightPx: Math.max(420, Math.floor(windowHeight * 0.62)),
  69. })
  70. try {
  71. currentEventChannel = this.getOpenerEventChannel()
  72. } catch {
  73. currentEventChannel = null
  74. }
  75. if (!currentEventChannel) {
  76. return
  77. }
  78. currentEventChannel.on('init', (request: H5ExperienceRequest) => {
  79. currentRequest = request
  80. const presentation = request.presentation || 'sheet'
  81. const panelHeightPx = presentation === 'dialog'
  82. ? Math.max(420, Math.floor(windowHeight * 0.7))
  83. : presentation === 'fullscreen'
  84. ? Math.max(520, windowHeight - 24)
  85. : Math.max(420, Math.floor(windowHeight * 0.72))
  86. const headerHeightPx = presentation === 'fullscreen' ? 84 : 76
  87. this.setData({
  88. pageTitle: request.title || '内容体验',
  89. pageSubtitle: request.subtitle || '',
  90. presentation,
  91. webViewSrc: buildWebViewSrc(request),
  92. webViewReady: true,
  93. loadErrorText: '',
  94. panelBodyHeightPx: Math.max(240, panelHeightPx - headerHeightPx),
  95. })
  96. })
  97. },
  98. onUnload() {
  99. if (currentEventChannel && !pageResolved) {
  100. currentEventChannel.emit('close', {})
  101. }
  102. pageResolved = false
  103. currentRequest = null
  104. currentEventChannel = null
  105. },
  106. handleWebViewMessage(event: WechatMiniprogram.CustomEvent) {
  107. const dataList = event.detail && Array.isArray(event.detail.data)
  108. ? event.detail.data
  109. : []
  110. const rawMessage = dataList.length ? dataList[dataList.length - 1] : null
  111. if (!rawMessage || typeof rawMessage !== 'object') {
  112. return
  113. }
  114. const message = rawMessage as H5BridgeMessage
  115. const action = message.action || message.type || ''
  116. if (!action) {
  117. return
  118. }
  119. if (action === 'close') {
  120. emitCloseAndBack(message.payload)
  121. return
  122. }
  123. if (action === 'submitResult') {
  124. if (currentEventChannel) {
  125. currentEventChannel.emit('submitResult', message.payload || {})
  126. }
  127. return
  128. }
  129. if (action === 'fallback') {
  130. emitFallbackAndClose()
  131. }
  132. },
  133. handleWebViewError() {
  134. this.setData({
  135. loadErrorText: '页面打开失败,已回退原生内容',
  136. })
  137. emitFallbackAndClose()
  138. },
  139. handleCloseTap() {
  140. emitCloseAndBack({})
  141. },
  142. })