tools.js 7.19 KB
/*
 * @Date: 2022-04-18 15:59:42
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2026-01-23 15:18:16
 * @FilePath: /git/xyxBooking-weapp/src/utils/tools.js
 * @Description: 工具函数库
 */
import dayjs from 'dayjs'
import Taro from '@tarojs/taro'
import BASE_URL, { REQUEST_DEFAULT_PARAMS } from './config'

/**
 * @description 格式化时间
 * @param {string|number|Date} date 时间入参
 * @returns {string} 格式化后的时间字符串(YYYY-MM-DD HH:mm)
 */
const formatDate = date => {
  return dayjs(date).format('YYYY-MM-DD HH:mm')
}

/**
 * @description 判断设备信息
 * @returns {Object} 设备信息对象,包含是否为 Android、iOS、是否为平板等属性
 */
const wxInfo = () => {
  const info = Taro.getSystemInfoSync()
  const isAndroid = info.platform === 'android'
  const isiOS = info.platform === 'ios'
  // 说明:当前项目只用到 Android/iOS 区分;平板能力按需补充
  return {
    isAndroid,
    isiOS,
    isTable: false // 小程序通常不是 tablet 模式,或者可以根据 screenWidth 判断
  }
}

/**
 * @description 解析 URL 参数
 * @param {string} url 完整 URL 或带 query 的路径
 * @returns {Object} URL 参数对象(键值对)
 */
const parseQueryString = url => {
  if (!url) {
    return {}
  }
  const json = {}
  const arr = url.indexOf('?') >= 0 ? url.substr(url.indexOf('?') + 1).split('&') : []
  arr.forEach(item => {
    const tmp = item.split('=')
    json[tmp[0]] = tmp[1]
  })
  return json
}

/**
 * @description 判断字符串是否包含数组中的任意子串
 * @param {Array<string>} array 子串数组
 * @param {string} str 目标字符串
 * @returns {boolean} true=包含任意一个子串,false=都不包含
 */
const strExist = (array, str) => {
  if (!str) {
    return false
  }
  const exist = array.filter(arr => {
    if (str.indexOf(arr) >= 0) {
      return str
    }
  })
  return exist.length > 0
}

/**
 * 格式化日期时间字符串,提取开始/结束时间并拼接为「日期 开始时间-结束时间」格式
 * @description 处理包含 begin_time/end_time 的数据对象,截取时间戳最后6位(毫秒/时区等冗余部分),
 *              最终拼接为 "YYYY-MM-DD HH:mm:ss-HH:mm:ss" 格式的字符串
 * @param {Object} data - 包含开始/结束时间的数据源对象
 * @param {string} [data.begin_time] - 开始时间字符串(格式示例:2026-01-13T12:30:45.123456+08:00)
 * @param {string} [data.end_time] - 结束时间字符串(格式同 begin_time)
 * @returns {string} 格式化后的日期时间字符串,格式为「日期 开始时间-结束时间」;若入参无效返回空字符串
 * @example
 * 输入示例
 * const timeData = {
 *   begin_time: '2026-01-13T10:00:00.987654+08:00',
 *   end_time: '2026-01-13T18:30:00.123456+08:00'
 * };
 * 调用函数
 * formatDatetime(timeData); // 返回 "2026-01-13 10:00:00-18:30:00"
 *
 * @example
 * 入参为空的情况
 * formatDatetime(null); // 返回 ""
 * formatDatetime({}); // 返回 ""
 *
 * @note 1. 入参时间字符串需保证前19位为有效格式(YYYY-MM-DDTHH:mm:ss),否则截取后可能出现异常;
 *       2. 若 begin_time/end_time 缺失,拼接后可能出现 "undefined-undefined" 等异常,需保证入参完整性;
 *       3. 该函数默认截取时间字符串前19位(slice(0, -6)),需根据实际时间格式调整截取长度
 */
const formatDatetime = data => {
  if (!data || !data.begin_time || !data.end_time) {
    return ''
  }

  const normalize = timeStr => {
    if (!timeStr) {
      return ''
    }
    let clean = timeStr.split('+')[0]
    clean = clean.split('Z')[0]
    clean = clean.trim().replace(/\s+/, 'T')
    return clean
  }

  const start = dayjs(normalize(data.begin_time))
  const end = dayjs(normalize(data.end_time))

  if (!start.isValid() || !end.isValid()) {
    return ''
  }

  const isNextDayMidnight =
    end.diff(start, 'day') === 1 && end.hour() === 0 && end.minute() === 0 && end.second() === 0

  const endTimeText = isNextDayMidnight ? '24:00' : end.format('HH:mm')

  return `${start.format('YYYY-MM-DD')} ${start.format('HH:mm')}-${endTimeText}`
}

/**
 * @description 证件号脱敏
 * @param {string} id_number 证件号
 * @param {Object} [options] 脱敏配置
 * @param {number} [options.keep_start] 保留前几位(传了则按“前后保留”模式脱敏)
 * @param {number} [options.keep_end] 保留后几位(传了则按“前后保留”模式脱敏)
 * @param {number} [options.mask_count=8] 中间替换为 * 的位数(默认 8)
 * @returns {string} 脱敏后的证件号
 */
const mask_id_number = (id_number, options = {}) => {
  const raw = String(id_number || '')
  if (!raw) {
    return ''
  }

  const has_keep_start = Number.isFinite(options.keep_start)
  const has_keep_end = Number.isFinite(options.keep_end)
  const keep_start = has_keep_start ? options.keep_start : 0
  const keep_end = has_keep_end ? options.keep_end : 0
  const mask_count = Number.isFinite(options.mask_count) ? options.mask_count : 8

  if (has_keep_start && has_keep_end) {
    if (raw.length <= keep_start + keep_end) {
      return raw
    }
    const prefix = raw.slice(0, keep_start)
    const suffix = raw.slice(raw.length - keep_end)
    const middle_len = Math.max(1, raw.length - keep_start - keep_end)
    return `${prefix}${'*'.repeat(middle_len)}${suffix}`
  }

  if (raw.length < 15) {
    return raw
  }

  const safe_mask_count = Math.min(Math.max(1, mask_count), raw.length)
  const start = Math.floor((raw.length - safe_mask_count) / 2)
  const end = start + safe_mask_count
  if (start < 0 || end > raw.length) {
    return raw
  }

  return raw.substring(0, start) + '*'.repeat(safe_mask_count) + raw.substring(end)
}

/**
 * @description 二维码状态文案
 * @param {string|number} status 状态值
 * @returns {string} 状态文案
 */
const get_qrcode_status_text = status => {
  const key = String(status || '')
  if (key === '1') {
    return '未激活'
  }
  if (key === '3') {
    return '待使用'
  }
  if (key === '5') {
    return '被取消'
  }
  if (key === '7') {
    return '已使用'
  }
  return '未知状态'
}

/**
 * @description 订单状态文案
 * @param {string|number} status 状态值
 * @returns {string} 状态文案
 */
const get_bill_status_text = status => {
  const key = String(status || '')
  if (key === '3') {
    return '预约成功'
  }
  if (key === '5') {
    return '已取消'
  }
  if (key === '9') {
    return '已使用'
  }
  if (key === '11') {
    return '退款中'
  }
  return '未知状态'
}

/**
 * @description 构建 API 请求 URL(带默认公共参数)
 * @param {string} action 接口动作名称(例如:openid_wxapp)
 * @param {Object} [params={}] 额外 query 参数
 * @returns {string} 完整请求 URL(BASE_URL + /srv/?a=...&f=...&client_name=...)
 */
const buildApiUrl = (action, params = {}) => {
  const queryParams = new URLSearchParams({
    a: action,
    f: REQUEST_DEFAULT_PARAMS.f,
    client_name: REQUEST_DEFAULT_PARAMS.client_name,
    ...params
  })
  return `${BASE_URL}/srv/?${queryParams.toString()}`
}

export {
  formatDate,
  wxInfo,
  parseQueryString,
  strExist,
  formatDatetime,
  mask_id_number,
  get_qrcode_status_text,
  get_bill_status_text,
  buildApiUrl
}