network-access-guard.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. import appSettings from '@/core/config/app-settings'
  2. import { runWithoutHttpErrorToast } from '@/core/http/interceptor-registry'
  3. let blocked = false
  4. let modalOpen = false
  5. let probeInflight = null
  6. let i18nRef = null
  7. const listeners = new Set()
  8. function t(key, fallback) {
  9. if (!i18nRef) return fallback
  10. try {
  11. const v = i18nRef.t(key)
  12. return v && v !== key ? v : fallback
  13. } catch (_) {
  14. return fallback
  15. }
  16. }
  17. function notify() {
  18. listeners.forEach((fn) => {
  19. try {
  20. fn(blocked)
  21. } catch (_) {}
  22. })
  23. }
  24. function setBlocked(next) {
  25. const v = !!next
  26. if (blocked === v) return
  27. blocked = v
  28. notify()
  29. }
  30. export function isNetworkBlocked() {
  31. return blocked
  32. }
  33. export function subscribeNetworkBlock(listener) {
  34. if (typeof listener === 'function') {
  35. listeners.add(listener)
  36. listener(blocked)
  37. }
  38. return () => listeners.delete(listener)
  39. }
  40. function getNetworkTypeAsync() {
  41. return new Promise((resolve) => {
  42. uni.getNetworkType({
  43. success: (res) => resolve((res && res.networkType) || 'unknown'),
  44. fail: () => resolve('none')
  45. })
  46. })
  47. }
  48. function probeApiReachable() {
  49. const base = (appSettings.appurl || '').replace(/\/$/, '')
  50. if (!base) return Promise.resolve(true)
  51. // 探测根路径常会 404,但会触发全局拦截器 toast「API 接口不存在」
  52. return runWithoutHttpErrorToast(
  53. () =>
  54. new Promise((resolve) => {
  55. uni.request({
  56. url: `${base}/`,
  57. method: 'GET',
  58. timeout: 6000,
  59. success: (res) => {
  60. const code = res && res.statusCode
  61. resolve(code >= 200 && code < 500)
  62. },
  63. fail: () => resolve(false)
  64. })
  65. })
  66. )
  67. }
  68. /**
  69. * 是否具备可用网络(无网络 / 系统禁止本 App 使用网络时返回 false)
  70. */
  71. export async function checkNetworkAccess() {
  72. const type = await getNetworkTypeAsync()
  73. if (type === 'none') return false
  74. // 已连接 Wi‑Fi/蜂窝但仍可能被系统禁止本 App 使用网络,用轻量请求探测
  75. const reachable = await probeApiReachable()
  76. return reachable
  77. }
  78. export async function refreshNetworkAccess() {
  79. // #ifndef APP-PLUS
  80. setBlocked(false)
  81. return true
  82. // #endif
  83. // #ifdef APP-PLUS
  84. if (probeInflight) return probeInflight
  85. probeInflight = checkNetworkAccess()
  86. .then((ok) => {
  87. setBlocked(!ok)
  88. return ok
  89. })
  90. .finally(() => {
  91. probeInflight = null
  92. })
  93. return probeInflight
  94. // #endif
  95. }
  96. export function openNetworkSettings() {
  97. // #ifdef APP-PLUS
  98. try {
  99. const os = (plus.os.name || '').toLowerCase()
  100. if (os === 'ios') {
  101. plus.runtime.openURL('app-settings:')
  102. return
  103. }
  104. if (os === 'android') {
  105. const main = plus.android.runtimeMainActivity()
  106. const Intent = plus.android.importClass('android.content.Intent')
  107. const Settings = plus.android.importClass('android.provider.Settings')
  108. const intent = new Intent(Settings.ACTION_WIRELESS_SETTINGS)
  109. main.startActivity(intent)
  110. return
  111. }
  112. } catch (e) {
  113. console.warn('[network-guard] open settings failed', e)
  114. }
  115. // #endif
  116. if (typeof uni.openAppAuthorizeSetting === 'function') {
  117. uni.openAppAuthorizeSetting({})
  118. }
  119. }
  120. export function promptNetworkRequired() {
  121. if (!blocked || modalOpen) return
  122. modalOpen = true
  123. uni.showModal({
  124. title: t('networkGuard.title', '需要网络'),
  125. content: t(
  126. 'networkGuard.content',
  127. '当前无法访问网络。请在系统设置中允许本应用使用无线局域网与蜂窝数据,否则无法继续使用。'
  128. ),
  129. confirmText: t('networkGuard.confirm', '去设置'),
  130. cancelText: t('networkGuard.cancel', '知道了'),
  131. showCancel: true,
  132. success: (res) => {
  133. modalOpen = false
  134. if (res.confirm) openNetworkSettings()
  135. },
  136. fail: () => {
  137. modalOpen = false
  138. }
  139. })
  140. }
  141. // #ifdef APP-PLUS
  142. let nativeMask = null
  143. function ensureNativeMask() {
  144. if (nativeMask) return nativeMask
  145. nativeMask = new plus.nativeObj.View(
  146. 'hl88-network-blocker',
  147. {
  148. top: '0px',
  149. left: '0px',
  150. width: '100%',
  151. height: '100%',
  152. backgroundColor: 'rgba(0,0,0,0.01)'
  153. },
  154. []
  155. )
  156. nativeMask.interceptTouchEvent(true)
  157. nativeMask.addEventListener('click', () => {
  158. promptNetworkRequired()
  159. })
  160. return nativeMask
  161. }
  162. function syncNativeMask() {
  163. try {
  164. const mask = ensureNativeMask()
  165. if (blocked) mask.show()
  166. else mask.hide()
  167. } catch (e) {
  168. console.warn('[network-guard] native mask', e)
  169. }
  170. }
  171. // #endif
  172. function onBlockStateChange() {
  173. // #ifdef APP-PLUS
  174. syncNativeMask()
  175. // #endif
  176. }
  177. let started = false
  178. export function startNetworkAccessGuard(i18n) {
  179. // #ifndef APP-PLUS
  180. return
  181. // #endif
  182. // #ifdef APP-PLUS
  183. if (started) return
  184. started = true
  185. i18nRef = i18n
  186. subscribeNetworkBlock(onBlockStateChange)
  187. const kickoff = () => refreshNetworkAccess()
  188. if (typeof plus !== 'undefined') {
  189. kickoff()
  190. } else if (typeof document !== 'undefined') {
  191. document.addEventListener('plusready', kickoff, false)
  192. } else {
  193. kickoff()
  194. }
  195. if (typeof uni.onNetworkStatusChange === 'function') {
  196. uni.onNetworkStatusChange(() => {
  197. refreshNetworkAccess()
  198. })
  199. }
  200. // #endif
  201. }
  202. export function stopNetworkAccessGuard() {
  203. // #ifndef APP-PLUS
  204. return
  205. // #endif
  206. // #ifdef APP-PLUS
  207. started = false
  208. if (nativeMask) {
  209. try {
  210. nativeMask.hide()
  211. nativeMask.close()
  212. } catch (_) {}
  213. nativeMask = null
  214. }
  215. // #endif
  216. }