PrivacyPolicyPage.ets 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. import { promptAction, router } from '@kit.ArkUI'
  2. import { webview } from '@kit.ArkWeb'
  3. import { bundleManager, common } from '@kit.AbilityKit'
  4. import { BusinessError } from '@kit.BasicServicesKit'
  5. import { WebViewJavascriptBridge, WVJBResponseCallback } from '@yue/webview_javascript_bridge'
  6. import util from '@kit.ArkTS';
  7. @Component
  8. struct PrivacyPolicyPage {
  9. @Require fromText: string
  10. @State targetUrl: string = ''
  11. @State title: string = ''
  12. // 是否有进度条
  13. hasProgressBar: boolean = false
  14. // 网络加载进度
  15. @State progressNum: number = 0
  16. // 网络是否在加载中
  17. @State loading: boolean = true
  18. // 是否显示
  19. navBarShow: boolean = true
  20. // 进入页面时,状态条颜色
  21. onPageShowBarColor: StatusBarColor = StatusBarColor.Black
  22. // 隐藏页面时,状态条颜色
  23. onPageHideBarColor: StatusBarColor = StatusBarColor.Black
  24. // 顶部安全高度
  25. safeTop: number = AppStorage.get('safeTop') as number
  26. // 底部安全高度
  27. safeBottom: number = AppStorage.get('safeBottom') as number
  28. controller: webview.WebviewController = new webview.WebviewController()
  29. historyCurrIndex: number = 0
  30. // WebViewJavascriptBridge 桥接
  31. private bridge: WebViewJavascriptBridge | undefined;
  32. private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
  33. // 是否安装支付宝应用
  34. private canOpenAlipays = false;
  35. // 是否安装微信应用
  36. private canOpenWechat = false;
  37. // 是否已拉起微信支付
  38. private isWeixinPay = false;
  39. // 支付订单页url,如果成功拉起微信支付以后取消会回到app,此时需要跳到订单页
  40. private orderUrl: string = '';
  41. // 监听网页加载是否出错
  42. @State loadError: boolean = false;
  43. // 点击返回键需要直接退出web组件的url集合
  44. private excludedUrlList: string[] = [
  45. 'https://app.sightcloud.cn/storage/#/package',
  46. 'https://app.aikancloud.com/storage/#/packageOrder',
  47. 'https://wx.88iot.net'
  48. ];
  49. aboutToAppear(): void {
  50. this.onPageShow()
  51. }
  52. onPageShow(): void {
  53. // this.statusBarColorSelect(this.onPageShowBarColor)
  54. // 判断是否安装支付宝
  55. try {
  56. this.canOpenAlipays = bundleManager.canOpenLink('alipays://');
  57. this.canOpenWechat = bundleManager.canOpenLink('weixin://');
  58. } catch (err) {
  59. let message = (err as BusinessError).message;
  60. console.log('canOpenLink call error:', message);
  61. }
  62. // 判断是否是跳转到微信以后再返回app,若是,则跳转到订单页,若没有拿到订单url,直接返回上一页
  63. if (this.isWeixinPay) {
  64. this.isWeixinPay = false
  65. if (this.orderUrl) {
  66. // 清跳转订单页
  67. this.controller.loadUrl(this.orderUrl)
  68. } else {
  69. this.backMethod()
  70. }
  71. }
  72. }
  73. onPageHide(): void {
  74. this.statusBarColorSelect(this.onPageHideBarColor)
  75. }
  76. // 状态条颜色选取函数
  77. statusBarColorSelect(color: StatusBarColor) {
  78. switch (color) {
  79. case StatusBarColor.White:
  80. // themeManager.settingStatusBarWhite()
  81. break
  82. case StatusBarColor.Black:
  83. // themeManager.settingStatusBarBlack()
  84. break
  85. default:
  86. // themeManager.settingStatusBarBlack()
  87. break
  88. }
  89. }
  90. // 页面自行处理返回逻辑,不进行页面路由
  91. onBackPress() {
  92. this.backMethod()
  93. return true
  94. }
  95. backMethod() {
  96. if (this.loadError) {
  97. router.back()
  98. return
  99. }
  100. const url = this.controller.getUrl()
  101. if (this.excludedUrlList.some(item => url.startsWith(item))) {
  102. router.back()
  103. return
  104. }
  105. if (this.historyCurrIndex > 0) {
  106. this.controller.backward()
  107. } else {
  108. router.back()
  109. }
  110. }
  111. build() {
  112. NavDestination() {
  113. Column() {
  114. if (this.navBarShow) {
  115. // PrivacyPolicyPageTitle({ title: this.title })
  116. }
  117. Stack({ alignContent: Alignment.Top }) {
  118. if (this.hasProgressBar && this.loading && !this.loadError) {
  119. this.progress()
  120. }
  121. if (this.loadError) {
  122. Column() {
  123. Text('出错啦!点击空白处刷新')
  124. .fontColor('#686868')
  125. .fontSize(20)
  126. }
  127. // .width(StyleConstants.FULL_PARENT)
  128. // .height(StyleConstants.FULL_PARENT)
  129. .justifyContent(FlexAlign.Center)
  130. .onClick(() => {
  131. this.loadError = false
  132. })
  133. } else {
  134. Scroll() {
  135. Web({
  136. src: this.fromText,
  137. controller: this.controller
  138. })
  139. .width('100%')
  140. .height('100%')
  141. .geolocationAccess(false)
  142. .domStorageAccess(true)
  143. .mixedMode(MixedMode.Compatible)
  144. .fileAccess(true)
  145. .multiWindowAccess(true)
  146. .onControllerAttached(() => {
  147. this.setupWebViewJavascriptBridge();
  148. })
  149. .onProgressChange((date) => {
  150. if (date) {
  151. //记录加载进度
  152. this.progressNum = date.newProgress
  153. if (this.progressNum == 100) {
  154. //加载完成进度条消失
  155. animateTo({ duration: 800, delay: 300 }, () => {
  156. this.loading = false
  157. })
  158. }
  159. }
  160. })
  161. .onRefreshAccessedHistory((event) => {
  162. //H5页面间的跳转
  163. // if (this.controller.getTitle().length < 5) {
  164. // this.title = this.controller.getTitle()
  165. // }
  166. if (event) {
  167. const history = this.controller.getBackForwardEntries()
  168. this.historyCurrIndex = history.currentIndex
  169. }
  170. })
  171. .onPageBegin((event) => {
  172. this.progressNum = 0
  173. this.loading = true
  174. })
  175. .onLoadIntercept((event) => {
  176. const isPayment = this.handlePaymentIntercept(event)
  177. return isPayment
  178. })
  179. .onPageEnd((event) => {
  180. // this.controller.runJavaScript('document.forms[0].submit()')
  181. if (this.orderUrl && event.url == this.orderUrl) {
  182. this.controller.clearHistory()
  183. }
  184. })
  185. .onErrorReceive((event) => {
  186. if (event.request.getRequestUrl() == this.controller.getUrl()) {
  187. console.error(`WebView load error: ${event.request.getRequestUrl()}, code: ${event.error.getErrorCode()}, message: ${event.error.getErrorInfo()}`);
  188. this.loadError = true; // 显示错误页面
  189. }
  190. })
  191. }
  192. .margin({ bottom: this.navBarShow ? this.safeBottom : 0 })
  193. }
  194. }
  195. }
  196. .width('100%')
  197. .height('100%')
  198. .padding({ top: this.navBarShow ? this.safeTop : 0, bottom: this.navBarShow ? this.safeBottom : 0 })
  199. }
  200. .hideTitleBar(true)
  201. }
  202. // 处理支付相关的拦截逻辑
  203. private handlePaymentIntercept(event: OnLoadInterceptEvent) {
  204. let data = event.data;
  205. let url = data.getRequestUrl();
  206. console.log(`alipay: url: ${url}`);
  207. // 点击商城回退时会触发该跳转,直接退出就好,不加载
  208. if (url.includes('js_native://close')) {
  209. router.back()
  210. return true
  211. }
  212. // 拉起支付宝支付
  213. if (url.startsWith('alipays://platformapi/startApp')) {
  214. if (this.canOpenAlipays) {
  215. try {
  216. this.context.openLink(url).then(() => {
  217. console.info('open alipays success.');
  218. }).catch((err: BusinessError) => {
  219. console.error(`open alipays failed. Code is ${err.code}, message is ${err.message}`);
  220. promptAction.showToast({ message: '打开支付宝失败' })
  221. })
  222. } catch (paramError) {
  223. console.error(`Failed to start alipays. Code is ${paramError.code}, message is ${paramError.message}`)
  224. promptAction.showToast({ message: '打开支付宝失败,请稍后重试' })
  225. }
  226. } else {
  227. promptAction.showToast({ message: '请先安装支付宝客户端' })
  228. }
  229. }
  230. // 拉起微信支付链接,提前将支付成功微信支付跳转回app的订单页url保存,取消时同样需要跳转
  231. if (url.startsWith('https://wx.tenpay.com')) {
  232. // 提取 redirect_url 参数
  233. const redirectUrl = this.getRedirectUrlFromTenpay(url);
  234. if (redirectUrl) {
  235. console.info('提取到重定向链接:', redirectUrl);
  236. // 可以选择保存该链接用于后续操作
  237. this.orderUrl = redirectUrl;
  238. }
  239. return false;
  240. }
  241. // 拉起微信支付链接
  242. if (url.startsWith('weixin://')) {
  243. if (this.canOpenWechat) {
  244. try {
  245. this.context.openLink(url).then(() => {
  246. // 拉起微信支付成功
  247. this.isWeixinPay = true
  248. console.info('open weixin success.');
  249. }).catch((err: BusinessError) => {
  250. console.error(`open weixin failed. Code is ${err.code}, message is ${err.message}`);
  251. promptAction.showToast({ message: '打开微信失败' })
  252. })
  253. } catch (paramError) {
  254. console.error(`Failed to start weixin. Code is ${paramError.code}, message is ${paramError.message}`)
  255. promptAction.showToast({ message: '打开微信失败,请稍后重试' })
  256. }
  257. return false
  258. } else {
  259. promptAction.showToast({ message: '请先安装微信客户端'})
  260. return true
  261. }
  262. }
  263. return false
  264. }
  265. // 从微信支付链接中提取 redirect_url 参数
  266. private getRedirectUrlFromTenpay(url: string): string | null {
  267. try {
  268. // 使用正则提取 redirect_url 参数
  269. const regex = /[?&]redirect_url=([^&]+)/;
  270. const match = url.match(regex);
  271. if (match && match[1]) {
  272. let decodedUrl = decodeURIComponent(match[1]);
  273. return decodedUrl;
  274. }
  275. } catch (e) {
  276. console.error('解析 redirect_url 失败:', e);
  277. }
  278. return null;
  279. }
  280. @Builder
  281. progress() {
  282. Progress({ value: this.progressNum, total: 100, type: ProgressType.Linear })
  283. .color('#17ab19')
  284. .zIndex(1)
  285. }
  286. /**
  287. * 设置 jsBridge
  288. */
  289. private setupWebViewJavascriptBridge() {
  290. // 开启调试,控制台打印信息,默认无调试
  291. WebViewJavascriptBridge.enableLogging();
  292. // 创建 WebViewJavascriptBridge
  293. this.bridge = new WebViewJavascriptBridge(this.controller);
  294. this.registerHandlers(this.bridge)
  295. }
  296. // 注册原生处理函数 (这里需要知道H5端的方法名)
  297. private registerHandlers(bridge: WebViewJavascriptBridge) {
  298. bridge.registerHandler('backToNative', (data: object, responseCallback: WVJBResponseCallback) => {
  299. this.backMethod();
  300. responseCallback(null);
  301. });
  302. }
  303. }
  304. export enum StatusBarColor {
  305. White = 'White',
  306. Black = 'Black',
  307. }
  308. @Builder
  309. function PrivacyPolicyBuilder(_: string, fromText: string) {
  310. PrivacyPolicyPage({fromText})
  311. }