app.js 7.61 KB
/*
 * @Date: 2025-06-28 10:33:00
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2026-01-17 12:14:16
 * @FilePath: /xyxBooking-weapp/src/app.js
 * @Description: 应用入口文件
 */
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import './utils/polyfill'
import './app.less'
import { saveCurrentPagePath, hasAuth, silentAuth, navigateToAuth } from '@/utils/authRedirect'
import Taro from '@tarojs/taro'
import {
  refresh_offline_booking_cache,
  has_offline_booking_cache
} from '@/composables/useOfflineBookingCache'
import { is_usable_network, get_network_type } from '@/utils/network'
import { enable_offline_booking_cache_polling } from '@/composables/useOfflineBookingCachePolling'
import { weak_network_text, get_weak_network_modal_use_cache_options } from '@/utils/uiText'

// 记录是否已展示过网络异常提示弹窗
let has_shown_network_modal = false
// 记录上一次网络是否可用,用于识别“从可用变为不可用”的场景
let last_network_usable = null

const App = createApp({
  // 对应 onLaunch
  async onLaunch(options) {
    const path = options?.path || ''
    const query = options?.query || {}

    const query_string = Object.keys(query)
      .map(key => `${key}=${encodeURIComponent(query[key])}`)
      .join('&')
    const full_path = query_string ? `${path}?${query_string}` : path

    // 保存当前页面路径,用于授权后跳转回原页面
    if (full_path) {
      saveCurrentPagePath(full_path)
    }

    /**
     * @description 预加载离线预约记录数据(列表+详情)
     * - 仅在有授权且网络可用时调用
     * - 成功后将数据存储到本地缓存(key: OFFLINE_BOOKING_DATA)
     * @returns {Promise<void>} 无返回值
     */
    const preloadBookingData = async () => {
      try {
        await refresh_offline_booking_cache()
      } catch (e) {
        console.error('Preload booking cache failed', e)
      }
    }

    /**
     * @description 判断是否应该跳过网络异常提示弹窗
     * - 仅在当前页面为离线预约列表/详情/核销码页时返回 true
     * @returns {boolean} true=跳过提示,false=不跳过
     */

    const should_skip_network_prompt = () => {
      const pages = Taro.getCurrentPages ? Taro.getCurrentPages() : []
      const current_page = pages && pages.length ? pages[pages.length - 1] : null
      const current_route = String(current_page?.route || '')
      if (!current_route) {
        return false
      }
      if (current_route.includes('pages/offlineBookingList/index')) {
        return true
      }
      if (current_route.includes('pages/offlineBookingDetail/index')) {
        return true
      }
      if (current_route.includes('pages/offlineBookingCode/index')) {
        return true
      }
      return false
    }

    /**
     * @description 处理不良网络情况
     * - 仅在当前页面未跳过提示弹窗时调用
     * - 若有离线预约缓存,则展示弹窗询问是否使用缓存数据
     * - 否则展示简单提示 toast
     * @param {string} network_type 网络类型(wifi/4g/5g/3g/none/unknown)
     * @returns {Promise<boolean>} true=需要中断后续启动流程,false=继续
     */

    const handle_bad_network = async network_type => {
      if (has_shown_network_modal) {
        return false
      }
      if (should_skip_network_prompt()) {
        return false
      }

      const is_none_network = network_type === 'none'
      const is_weak_network = !is_usable_network(network_type)
      if (!is_weak_network) {
        return false
      }

      has_shown_network_modal = true

      if (has_offline_booking_cache()) {
        try {
          const modal_res = await Taro.showModal(get_weak_network_modal_use_cache_options())
          if (modal_res?.confirm) {
            await Taro.reLaunch({ url: '/pages/offlineBookingList/index' })
            return true
          }
        } catch (e) {
          return is_none_network
        }
      } else {
        try {
          await Taro.showToast({
            title: weak_network_text.toast_title,
            icon: 'none',
            duration: 2000
          })
        } catch (e) {
          return is_none_network
        }
      }

      return is_none_network
    }

    /**
     * 监听网络状态变化
     * - 当网络连接且有授权时,预加载离线预约记录数据
     */
    Taro.onNetworkStatusChange(res => {
      const is_connected = res?.isConnected !== false
      const network_type = res?.networkType || 'none'
      const network_usable = is_connected && is_usable_network(network_type)

      if (network_usable) {
        has_shown_network_modal = false
        last_network_usable = true
        if (hasAuth()) {
          preloadBookingData()
        }
        return
      }

      const should_prompt = last_network_usable === true || last_network_usable === null
      last_network_usable = false
      if (should_prompt) {
        handle_bad_network(network_type)
      }
    })

    /**
     * @description 处理启动时的不良网络情况(只在启动阶段检查一次)
     * - 网络不可用且有离线缓存:询问是否进入离线模式
     * - 网络不可用且无缓存:toast 提示网络不佳
     * @returns {Promise<boolean>} true=进入离线模式并中断启动,false=继续启动
     */
    const handle_bad_network_on_launch = async () => {
      /**
       * 避免重复提示用户
       * - 仅在首次启动时检查网络情况
       * - 如果用户已展示过提示弹窗,则直接返回 false
       */
      if (has_shown_network_modal) {
        return false
      }

      const network_type = await get_network_type()
      last_network_usable = is_usable_network(network_type)
      return handle_bad_network(network_type)
    }

    /**
     * @description 尝试在网络可用时预加载离线预约记录数据
     * - 仅在有授权时调用
     * @returns {void} 无返回值
     */
    const try_preload_when_online = () => {
      if (!hasAuth()) {
        return
      }
      Taro.getNetworkType({
        success: res => {
          if (is_usable_network(res.networkType)) {
            preloadBookingData()
          }
        }
      })
    }

    /**
     * @description 授权成功后的共用启动逻辑
     * - 尝试在网络可用时预加载离线预约数据
     * - 启动离线预约缓存轮询(会自行处理网络可用性与引用计数)
     * @returns {void} 无返回值
     */

    const bootstrap_after_auth = () => {
      try_preload_when_online()
      enable_offline_booking_cache_polling({ interval_ms: 2 * 1000 * 60 })
    }

    // 处理在启动时出现的不良网络情况
    const should_stop = await handle_bad_network_on_launch()
    // 如果用户选择进入离线模式,则直接返回
    if (should_stop) {
      return
    }

    /**
     * 尝试在有授权时预加载离线预约记录数据
     * - 若无授权,则尝试静默授权
     * - 授权成功后调用 bootstrap_after_auth 启动共用逻辑
     * - 授权失败则跳转至授权页面
     */

    // 如果用户已授权,则直接调用 bootstrap_after_auth 启动共用逻辑
    if (hasAuth()) {
      bootstrap_after_auth()
      return
    }

    if (path === 'pages/auth/index') {
      return
    }

    try {
      // 尝试静默授权
      await silentAuth()
      // 授权成功后调用 bootstrap_after_auth 启动共用逻辑
      bootstrap_after_auth()
    } catch (error) {
      console.error('静默授权失败:', error)
      // 授权失败则跳转至授权页面
      navigateToAuth(full_path || undefined)
    }
  },
  onShow() {}
})

App.use(createPinia())

export default App