app.js 8.06 KB
/*
 * @Date: 2025-06-28 10:33:00
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2026-01-13 13:55:59
 * @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 { qrcodeListAPI } from '@/api/index'
import { formatDatetime } from '@/utils/tools'

let has_shown_network_modal = false

/**
 * 格式化支付记录,按 pay_id 分组,相同 pay_id 下的记录 sort 为 0,否则为 1
 * @param {*} data 支付记录数组
 * @returns 格式化后的支付记录数组
 */
const formatGroup = (data) => {
    let lastPayId = null;
    for (let i = 0; i < data.length; i++) {
        if (data[i].pay_id !== lastPayId) {
            data[i].sort = 1;
            lastPayId = data[i].pay_id;
        } else {
            data[i].sort = 0;
        }
    }
    return data;
}

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)
        }

        /**
         * 预加载二维码数据
         * - 仅在有网络连接时调用
         * - 成功后将数据存储到本地缓存(key: OFFLINE_QR_DATA)
         * - 失败则移除缓存
         */
        const preloadQrData = async () => {
            try {
                const { code, data } = await qrcodeListAPI();
                if (code && data) {
                    data.forEach(item => {
                        item.datetime = formatDatetime({ begin_time: item.begin_time, end_time: item.end_time })
                        item.sort = 0;
                    });
                    const validData = data.filter(item => item.qr_code !== '');
                    if (validData.length > 0) {
                        const processed = formatGroup(validData);
                        const offline_data = processed.map(item => ({
                            name: item.name,
                            id_number: item.id_number,
                            qr_code: item.qr_code,
                            begin_time: item.begin_time,
                            end_time: item.end_time,
                            datetime: item.datetime,
                            pay_id: item.pay_id,
                            sort: item.sort,
                        }));
                        Taro.setStorageSync('OFFLINE_QR_DATA', offline_data);
                    } else {
                        Taro.removeStorageSync('OFFLINE_QR_DATA');
                    }
                }
            } catch (e) {
                console.error('Preload QR failed', e);
            }
        };

        /**
         * 监听网络状态变化
         * - 当网络连接且有授权时,调用 preloadQrData 预加载二维码数据
         */
        Taro.onNetworkStatusChange((res) => {
            if (res.isConnected && hasAuth()) {
                preloadQrData()
            }
        })

        const is_usable_network = (network_type) => {
            return ['wifi', '4g', '5g', '3g'].includes(network_type)
        }

        /**
         * 获取当前网络类型
         * @returns {string} 当前网络类型,如 wifi、4g、5g、3g、none 等
         */
        const get_network_type = async () => {
            try {
                const result = await new Promise((resolve, reject) => {
                    Taro.getNetworkType({
                        success: resolve,
                        fail: reject,
                    })
                })
                return result?.networkType || 'unknown'
            } catch (e) {
                return 'unknown'
            }
        }

        /**
         * 检查是否有离线预约码缓存
         * @returns {boolean} 如果有离线预约码缓存且有效,则返回 true;否则返回 false
         */
        const has_offline_qr_cache = () => {
            try {
                const data = Taro.getStorageSync('OFFLINE_QR_DATA')
                return Array.isArray(data) && data.length > 0
            } catch (e) {
                return false
            }
        }

        /**
         * 处理在启动时出现的不良网络情况
         * - 当网络连接不良且有离线预约码缓存时,提示用户是否使用缓存预约码进入离线模式
         * - 当网络连接不良且无离线预约码缓存时,提示用户网络连接不畅
         * @returns {Promise<boolean>} 如果用户选择使用缓存预约码进入离线模式,则返回 true;否则返回 false
         */
        const handle_bad_network_on_launch = async () => {
            /**
             * 避免重复提示用户
             * - 仅在首次启动时检查网络情况
             */
            if (has_shown_network_modal) return false

            const network_type = await get_network_type()
            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_qr_cache()) {
                try {
                    const modal_res = await Taro.showModal({
                        title: '网络连接不畅',
                        content: '当前网络信号较弱,可使用已缓存的预约码进入离线模式',
                        confirmText: '预约码',
                        cancelText: '知道了',
                    })
                    if (modal_res?.confirm) {
                        await Taro.reLaunch({ url: '/pages/offlineBookingCode/index' })
                        return true
                    }
                } catch (e) {
                    return is_none_network
                }
            } else {
                try {
                    await Taro.showToast({ title: '网络连接不畅', icon: 'none', duration: 2000 })
                } catch (e) {
                    return is_none_network
                }
            }

            return is_none_network
        }

        const should_stop = await handle_bad_network_on_launch()
        if (should_stop) return

        /**
         * 尝试在网络可用时预加载二维码数据
         * - 仅在有授权时调用
         * - 成功后将数据存储到本地缓存(key: OFFLINE_QR_DATA)
         * - 失败则移除缓存
         * @returns {Promise<void>}
         */
        const try_preload_when_online = () => {
            if (!hasAuth()) return
            Taro.getNetworkType({
                success: (res) => {
                    if (is_usable_network(res.networkType)) {
                        preloadQrData()
                    }
                }
            })
        }

        /**
         * 尝试在有授权时预加载二维码数据
         * - 若无授权,则尝试静默授权
         * - 授权成功后调用 try_preload_when_online 预加载数据
         * - 授权失败则跳转至授权页面
         */
        if (hasAuth()) {
            try_preload_when_online()
            return
        }

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

        try {
            // 尝试静默授权
            await silentAuth()
            // 授权成功后预加载数据
            try_preload_when_online()
        } catch (error) {
            console.error('静默授权失败:', error)
            // 授权失败则跳转至授权页面
            navigateToAuth(full_path || undefined)
        }
    },
    onShow() {
    },
    // 入口组件不需要实现 render 方法,即使实现了也会被 taro 所覆盖
});

App.use(createPinia())

export default App