useOfflineBookingCache.js 7.18 KB
/**
 * 刷新离线预约记录缓存
 * - 仅在有授权且网络可用时调用
 * - 成功后将数据存储到本地缓存(key: OFFLINE_BOOKING_DATA)
 * @param {boolean} force - 是否强制刷新,默认为 false
 * @returns {Promise<void>}
 */

import Taro from '@tarojs/taro'
import { billOfflineAllAPI } from '@/api/index'
import { hasAuth } from '@/utils/authRedirect'
import { formatDatetime } from '@/utils/tools'
import { is_usable_network, get_network_type } from '@/utils/network'

export const OFFLINE_BOOKING_CACHE_KEY = 'OFFLINE_BOOKING_DATA'

let refresh_promise = null

/**
 * @description 兼容不同后端结构:从预约记录中提取可用数据载荷
 * - 部分接口会把字段塞到 bill.list 对象里,这里做一次展开合并
 * @param {Object} bill 原始预约记录
 * @returns {Object} 扁平化后的预约记录对象
 */
const extract_bill_payload = bill => {
  if (!bill) {
    return {}
  }

  const data = { ...bill }
  const list = data.list

  if (list && typeof list === 'object' && !Array.isArray(list)) {
    return { ...list, ...data }
  }

  return data
}

/**
 * @description 从预约记录中提取人员列表
 * - 兼容不同字段名(person_list/bill_person_list/persons/qrcode_list/qr_list/detail_list)
 * - 保证返回数组类型
 * @param {Object} bill 预约记录
 * @returns {Array} 人员列表
 */
const extract_person_list = bill => {
  if (!bill) {
    return []
  }

  /**
   * 从预约记录中提取人员列表
   * - 考虑不同字段名的情况(如 person_list, bill_person_list, persons, qrcode_list, qr_list, detail_list)
   * - 确保返回的是数组类型
   */
  const candidate =
    (Array.isArray(bill.list) ? bill.list : null) ||
    (Array.isArray(bill?.list?.list) ? bill.list.list : null) ||
    bill.person_list ||
    bill.bill_person_list ||
    bill.persons ||
    bill.qrcode_list ||
    bill.qr_list ||
    bill.detail_list ||
    []

  return Array.isArray(candidate) ? candidate : []
}

/**
 * @description 格式化预约记录项(统一字段与展示用时间)
 * @param {Object} item 原始预约记录项
 * @returns {Object} 格式化后的预约记录项
 */
const normalize_bill_item = item => {
  const data = extract_bill_payload(item)

  data.datetime = data.datetime || formatDatetime(data)
  data.booking_time = data.booking_time || data.datetime
  data.order_time = data.order_time || (data.created_time ? data.created_time.slice(0, -3) : '')

  if (!data.person_name) {
    const person_list = extract_person_list(item)
    const first = person_list[0]
    const name = first?.name || first?.person_name
    if (name) {
      data.person_name = name
    }
  }

  return data
}

/**
 * 获取离线预约记录缓存
 * @returns {Array} 格式化后的预约记录项列表
 */
export const get_offline_booking_cache = () => {
  try {
    const data = Taro.getStorageSync(OFFLINE_BOOKING_CACHE_KEY)
    return Array.isArray(data) ? data : []
  } catch (e) {
    return []
  }
}

/**
 * 检查是否存在离线预约记录缓存
 * @returns {boolean} 是否存在缓存且非空
 */
export const has_offline_booking_cache = () => {
  const list = get_offline_booking_cache()
  return Array.isArray(list) && list.length > 0
}

/**
 * 根据支付ID获取离线预约记录
 * @param {*} pay_id 支付ID
 * @returns {Object|null} 匹配的预约记录项或 null
 */
export const get_offline_booking_by_pay_id = pay_id => {
  const list = get_offline_booking_cache()
  const target_pay_id = String(pay_id || '')
  return list.find(item => String(item?.pay_id || '') === target_pay_id) || null
}

/**
 * 获取预约记录中的人员列表
 * @param {Object} bill - 预约记录项
 * @returns {Array} 人员列表(包含姓名、身份证号、二维码等信息)
 */
export const get_offline_bill_person_list = bill => {
  return extract_person_list(bill)
}

/**
 * 构建预约记录中的二维码列表
 * @param {Object} bill - 预约记录项
 * @returns {Array} 二维码列表(包含姓名、身份证号、二维码、预约时间等信息)
 */
export const build_offline_qr_list = bill => {
  const list = get_offline_bill_person_list(bill)
  const datetime = bill?.datetime || formatDatetime(bill || {})

  return list
    .filter(
      item =>
        item &&
        (item.qr_code || item.qrcode || item.qrCode) &&
        (item.qr_code || item.qrcode || item.qrCode) !== ''
    )
    .map(item => {
      const begin_time = item.begin_time || bill?.begin_time
      const end_time = item.end_time || bill?.end_time
      const qr_code = item.qr_code || item.qrcode || item.qrCode
      const name = item.name || item.person_name || item.real_name
      const id_number = item.id_number || item.idcard || item.idCard || item.id
      return {
        name,
        id_number,
        qr_code,
        begin_time,
        end_time,
        datetime:
          item.datetime ||
          (begin_time && end_time ? formatDatetime({ begin_time, end_time }) : datetime),
        pay_id: bill?.pay_id,
        sort: 0
      }
    })
}

/**
 * 刷新离线预约记录缓存
 * - 仅在有授权且网络可用时调用
 * - 成功后将数据存储到本地缓存(key: OFFLINE_BOOKING_DATA)
 * @param {boolean} force - 是否强制刷新,默认为 false. force 参数的核心作用是控制是否忽略 “正在进行的缓存请求”, 管的是 “是否允许重复发起请求”,不管 “请求能不能成功执行缓存”。
 * @returns 不同情况返回值不一样
 * - 成功时包含格式化后的预约记录项列表
 * - 失败时包含错误信息(如网络错误、授权失败等)
 */

export const refresh_offline_booking_cache = async ({ force = false } = {}) => {
  // 1. 检查是否有正在进行的刷新请求
  // 2. 如果有,且 force 为 false,则直接返回该 Promise
  // 3. 如果没有,或 force 为 true,则继续执行刷新逻辑
  // 4. 刷新完成后,将结果存储到本地缓存(key: OFFLINE_BOOKING_CACHE_KEY)
  // 5. 返回刷新结果 Promise

  if (!hasAuth()) {
    return { code: 0, data: null, msg: '未授权' }
  }

  if (refresh_promise && !force) {
    return refresh_promise
  }

  // 核心逻辑:
  // 1. 立刻触发异步逻辑,同时捕获 Promise 状态
  // 2. 保证 refresh_promise 始终是 Promise 类型,适配 await
  // 3. 隔离作用域,避免变量污染
  // 加 () 是为了 “让异步逻辑立刻跑起来”,并把 “跑的结果(Promise)” 存起来,供后续复用和等待。
  refresh_promise = (async () => {
    const network_type = await get_network_type()
    if (!is_usable_network(network_type)) {
      return { code: 0, data: null, msg: '网络不可用' }
    }

    const { code, data, msg } = await billOfflineAllAPI()
    if (code && Array.isArray(data)) {
      // 过滤出状态为3(已完成)的记录
      const normalized = data
        .map(normalize_bill_item)
        .filter(item => item && item.pay_id && item.status == 3)
      if (normalized.length > 0) {
        // TAG: 核心逻辑:将过滤后的记录存储到本地缓存
        Taro.setStorageSync(OFFLINE_BOOKING_CACHE_KEY, normalized)
      }
    }
    return { code, data, msg }
  })()

  try {
    return await refresh_promise
  } finally {
    refresh_promise = null
  }
}