7021f2fd创建于 2025年5月14日历史提交
import pagesJson from '../pages.json'
// 引入uni-parse-pages
import pagesJsonToRoutes from 'uni-parse-pages'
import { colorPanel } from './constants'
import tip from './tip'
import { platform, isH5, isApp, isMp, isMpWeixin, isMpAplipay, isMpToutiao } from '@/utils/platform'
import { isString } from './is'

/**
 * 缓存,默认有效期2小时
 * @param key 缓存key
 * @param value  缓存值
 * @param seconds 缓存时间(秒)
 * @returns {*}
 */
export function cache(key, value = null, seconds = 2 * 3600) {
  const timestamp = +new Date() / 1000
  if (key && value === null) {
    // 获取缓存
    const val = uni.getStorageSync(key)
    if (val && val.length > 0) {
      const tmp = val.split('|')
      if (!tmp[2] || timestamp >= tmp[2]) {
        console.log('key已失效')
        // 删除缓存
        uni.removeStorageSync(key)
        return ''
      } else {
        console.log('key未失效')
        if (tmp[1] == 'json') {
          return JSON.parse(tmp[0])
        }
        return tmp[0]
      }
    }
  } else if (key && value) {
    // 设置缓存
    const expire = timestamp + seconds
    console.log('typeof value', typeof value)
    if (typeof value === 'object') {
      value = JSON.stringify(value) + '|json|' + expire
    } else {
      value = value + '|string|' + expire
    }
    uni.setStorageSync(key, value)
  } else {
    console.log('key不能空')
  }
}

// 获取静态文件地址
export const getStaticDomainURL = () => {
  return import.meta.env.VITE_SERVER_BASEURL + '/sys/common/static'
}

export const getFileAccessHttpUrl = function (avatar, subStr?) {
  if (!avatar) return ''
  if (!subStr) subStr = 'http'
  if (avatar) {
    avatar = avatar.replace(/user_imgs\\/, 'user_imgs/')
  }
  if (avatar && avatar.startsWith(subStr)) {
    return avatar
  } else {
    return getStaticDomainURL() + '/' + avatar
  }
}
interface hasRouteType {
  name?: string
  path?: string
  routeList?: any
}
// 判断路由是否存在
export const hasRoute = ({ name, path, routeList }: hasRouteType) => {
  routeList = routeList ?? pagesJsonToRoutes(pagesJson)
  if (path) {
    return !!routeList.find((item) => item.path === path)
  }
  if (name) {
    return !!routeList.find((item) => item.path.split('/').pop() === name)
  }
}

/**
 * 人性化显示时间
 *
 * @param {Object} datetime
 */
export function beautifyTime(datetime = '') {
  if (datetime == null) {
    return ''
  }
  datetime = datetime.toString().replace(/-/g, '/')
  const time = new Date()
  let outTime = new Date(datetime)
  if (/^[1-9]\d*$/.test(datetime)) {
    outTime = new Date(parseInt(datetime))
  }

  if (time.getTime() < outTime.getTime()) {
    return parseTime(outTime, '{y}/{m}/{d}')
  }

  if (time.getFullYear() != outTime.getFullYear()) {
    return parseTime(outTime, '{y}/{m}/{d}')
  }

  if (time.getMonth() != outTime.getMonth()) {
    return parseTime(outTime, '{m}/{d}')
  }

  if (time.getDate() != outTime.getDate()) {
    const day = outTime.getDate() - time.getDate()
    if (day == -1) {
      return parseTime(outTime, '昨天 {h}:{i}')
    }

    if (day == -2) {
      return parseTime(outTime, '前天 {h}:{i}')
    }

    return parseTime(outTime, '{m}-{d}')
  }

  if (time.getHours() != outTime.getHours()) {
    return parseTime(outTime, '{h}:{i}')
  }

  let minutes = outTime.getMinutes() - time.getMinutes()
  if (minutes == 0) {
    return '刚刚'
  }

  minutes = Math.abs(minutes)
  return `${minutes}分钟前`
}
/**
 * 格式化时间
 * @param {Object} time
 * @param {Object} cFormat
 */
export function parseTime(time, cFormat) {
  if (arguments.length === 0) {
    return null
  }

  let date
  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'

  if (typeof time === 'object') {
    date = time
  } else {
    if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
      time = parseInt(time)
    } else {
      time = new Date(time)
    }
    date = new Date(time.toString().replace(/-/g, '/'))
  }

  const formatObj = {
    y: date.getFullYear(),
    m: date.getMonth() + 1,
    d: date.getDate(),
    h: date.getHours(),
    i: date.getMinutes(),
    s: date.getSeconds(),
    a: date.getDay(),
  }

  const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
    const value = formatObj[key]
    // Note: getDay() returns 0 on Sunday
    if (key === 'a') {
      return ['日', '一', '二', '三', '四', '五', '六'][value]
    }

    return value.toString().padStart(2, '0')
  })

  return time_str
}

/**
 * 随机生成字符串
 * @param length 字符串的长度
 * @param chats 可选字符串区间(只会生成传入的字符串中的字符)
 * @return string 生成的字符串
 */
export function randomString(length, chats?) {
  if (!length) length = 1
  if (!chats) chats = '0123456789qwertyuioplkjhgfdsazxcvbnm'
  let str = ''
  for (let i = 0; i < length; i++) {
    // @ts-ignore
    const num = randomNumber(0, chats.length - 1)
    str += chats[num]
  }
  return str
}

/**
 * 随机生成数字
 *
 * 示例:生成长度为 12 的随机数:randomNumber(12)
 * 示例:生成 3~23 之间的随机数:randomNumber(3, 23)
 *
 * @param1 最小值 | 长度
 * @param2 最大值
 * @return int 生成后的数字
 */
export function randomNumber() {
  // 生成 最小值 到 最大值 区间的随机数
  const random = (min, max) => {
    return Math.floor(Math.random() * (max - min + 1) + min)
  }
  if (arguments.length === 1) {
    // @ts-ignore
    const [length] = arguments
    // 生成指定长度的随机数字,首位一定不是 0
    // @ts-ignore
    const nums = [...Array(length).keys()].map((i) => (i > 0 ? random(0, 9) : random(1, 9)))
    return parseInt(nums.join(''))
  } else if (arguments.length >= 2) {
    // @ts-ignore
    const [min, max] = arguments
    return random(min, max)
  } else {
    return Number.NaN
  }
}

/**
 * 时间格式化
 * @param value
 * @param fmt
 * @returns {*}
 */
export function formatDate(value, fmt) {
  const regPos = /^\d+(\.\d+)?$/
  if (regPos.test(value)) {
    // 如果是数字
    const getDate = new Date(value)
    const o = {
      'M+': getDate.getMonth() + 1,
      'd+': getDate.getDate(),
      'h+': getDate.getHours(),
      'H+': getDate.getHours(),
      'm+': getDate.getMinutes(),
      's+': getDate.getSeconds(),
      'q+': Math.floor((getDate.getMonth() + 3) / 3),
      S: getDate.getMilliseconds(),
    }
    if (/(y+)/.test(fmt)) {
      fmt = fmt.replace(RegExp.$1, (getDate.getFullYear() + '').substr(4 - RegExp.$1.length))
    }
    for (const k in o) {
      if (new RegExp('(' + k + ')').test(fmt)) {
        fmt = fmt.replace(
          RegExp.$1,
          RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length),
        )
      }
    }
    return fmt
  } else {
    // TODO
    if (value && value.length > 0) {
      value = value.trim()
      return value.substr(0, fmt.length)
    }
    return value
  }
}

// 通过时间或者时间戳获取对应antd的年、月、周、季度。
export function getWeekMonthQuarterYear(date) {
  // 获取 ISO 周数的函数
  const getISOWeek = (date) => {
    const jan4 = new Date(date.getFullYear(), 0, 4)
    const oneDay = 86400000 // 一天的毫秒数
    return Math.ceil(((date - jan4.getTime()) / oneDay + jan4.getDay() + 1) / 7)
  }
  // 将时间戳转换为日期对象
  const dateObj = new Date(date)
  // 计算周
  const week = getISOWeek(dateObj)
  // 计算月
  const month = dateObj.getMonth() + 1 // 月份是从0开始的,所以要加1
  // 计算季度
  const quarter = Math.floor(dateObj.getMonth() / 3) + 1
  // 计算年
  const year = dateObj.getFullYear()
  return {
    year: `${year}`,
    month: `${year}-${month.toString().padStart(2, '0')}`,
    week: `${year}-${week}周`,
    quarter: `${year}-Q${quarter}`,
  }
}

// 生成 1 到 10 之间的随机整数
export function getRandomIntBetweenOneAndTen() {
  return Math.floor(Math.random() * 10) + 1
}
/**
 * 获取随机颜色
 * @param {any} color
 * 颜色板
 * classic:经典
 * technology:科技
 * business:商务
 * botany:植物
 * natural:自然
 * colour:彩色
 * @return
 */
export function getRandomColor() {
  const colorType = ['classic', 'technology', 'business', 'botany', 'natural', 'colour']
  // 生成一个随机索引,范围是从 0 到数组长度减 1
  const randomIndex = Math.floor(Math.random() * colorType.length)
  // 根据随机索引从数组中获取一个随机类型
  const randomColorType = colorType[randomIndex]
  return colorPanel.natural[getRandomIntBetweenOneAndTen()] || '#00bcd4'
}

// 消除后缀:
export const getPlaceholder = (attrs: any = {}) => {
  let label = attrs.label
  if (label.endsWith(':') || label.endsWith(':')) {
    label = label.substr(0, label.length - 1)
  }
  return `请选择${label}`
}
/**
 * 日期格式化
 * @param text
 */
export function getFormatDate(text, column) {
  if (!text) {
    return ''
  }
  let a = text
  if (a.length > 10) {
    a = a.substring(0, 10)
  }
  let fieldExtendJson = column?.fieldExtendJson
  console.log('getFormat  Datetext', text)
  console.log('getFormatDate  fieldExtendJson', fieldExtendJson)
  if (fieldExtendJson) {
    fieldExtendJson = JSON.parse(fieldExtendJson)
    if (fieldExtendJson.picker && fieldExtendJson.picker != 'default') {
      const result = getWeekMonthQuarterYear(a)
      return result[fieldExtendJson.picker]
    }
  }
  return a
}

/**
 * 字典值替换文本通用方法(多选)
 * @param dictOptions  字典数组
 * @param text  字典值
 * @return String
 */
export function filterMultiDictText(dictOptions, text) {
  // js “!text” 认为0为空,所以做提前处理
  if (text === 0 || text === '0') {
    if (dictOptions) {
      for (const dictItem of dictOptions) {
        if (text == dictItem.value) {
          return dictItem.text
        }
      }
    }
  }

  if (!text || text == 'undefined' || text == 'null' || !dictOptions || dictOptions.length == 0) {
    return ''
  }
  let re = ''
  text = text.toString()
  const arr = text.split(',')
  dictOptions.forEach(function (option) {
    if (option) {
      for (let i = 0; i < arr.length; i++) {
        if (arr[i] === option.value) {
          re += option.text + ','
          break
        }
      }
    }
  })
  if (re == '') {
    return text
  }
  return re.substring(0, re.length - 1)
}

/** 获取url参数
 * @param {Object} url
 */
export function getQueryVariable(url) {
  if (!url) return
  let t
  let n
  let r
  const i = url.split('?')[1]
  const s = {}
  ;(t = i.split('&')), (r = null), (n = null)
  for (const o in t) {
    const u = t[o].indexOf('=')
    u !== -1 && ((r = t[o].substr(0, u)), (n = t[o].substr(u + 1)), (s[r] = n))
  }
  return s
}

/**
 * 是否oauth环境
 */
export function isOAuth2AppEnv() {
  let isOAuthEnv = false
  // #ifdef H5
  isOAuthEnv = /wxwork|dingtalk/i.test(navigator.userAgent)
  // #endif
  return isOAuthEnv
}

/**
 * 获取url中的参数
 * @param url
 */
export const getUrlParams = (url) => {
  const result = {
    url: '',
    params: {},
  }
  const list = url.split('?')
  result.url = list[0]
  const params = list[1]
  if (params) {
    const list = params.split('&')
    list.forEach((ele) => {
      const dic = ele.split('=')
      const label = dic[0]
      const value = dic[1]
      result.params[label] = value
    })
  }
  return result
}

/**
 * 判断文件地址是否支持存在
 * @param url
 * @returns {Promise<any>}
 */
export function downloadAbled(url) {
  return new Promise((resolve, reject) => {
    let xmlHttp
    if (window.XMLHttpRequest) {
      xmlHttp = new XMLHttpRequest() // 其他浏览器
    } else if ((window as any).ActiveXObject) {
      // try {
      //   xmlHttp = new window.ActiveXObject("Microsoft.XMLHTTP");//IE
      // }catch (e) {
      //   console.log('创建xmlHttp失败',e)
      // }
    }
    if (!xmlHttp) {
      reject('创建xmlHttp失败')
    }
    xmlHttp.open('GET', url, false)
    xmlHttp.send()
    if (xmlHttp.readyState == 4) {
      if (xmlHttp.status == 200) {
        resolve(true)
      } else if (xmlHttp.status == 404) {
        reject('文件不存在!')
      } else {
        reject('请求失败,status:' + xmlHttp.status)
      }
    } else {
      reject('请求失败,readyState:' + xmlHttp.readyState)
    }
  })
}

/**
 * app获取url地址的传参问题
 * @param url
 * @returns {Object}
 */
export function appGetUrlParams(url) {
  const theRequest = new Object()
  const index = url.indexOf('?')
  if (index != -1) {
    const str = url.substring(index + 1)
    const strs = str.split('&')
    for (let i = 0; i < strs.length; i++) {
      theRequest[strs[i].split('=')[0]] = unescape(strs[i].split('=')[1])
    }
  }
  return theRequest
}
/**
 *
 * @param lat1  纬度1
 * @param lng1  经度1
 * @param lat2  纬度2
 * @param lng2  经度2
 * 返回 米
 */
export function geoDistance(lng1, lat1, lng2, lat2) {
  let radLat1 = rad(lat1)
  let radLat2 = rad(lat2)
  let a = radLat1 - radLat2
  let b = rad(lng1) - rad(lng2)
  let s =
    2 *
    Math.asin(
      Math.sqrt(
        Math.pow(Math.sin(a / 2), 2) +
          Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2),
      ),
    )
  s = s * 6378.137 // EARTH_RADIUS;
  s = Math.round(s * 10000) / 10
  return s
}
//经纬度转换成三角函数中度分表形式。
function rad(d) {
  return (d * Math.PI) / 180.0
}

export function downloadFile(obj) {
  let url = ''
  if (isMp) {
    if (obj.currentTarget) {
      url = encodeURI(obj.currentTarget.dataset.url)
      downloadNH5(url)
    } else if (isString(obj)) {
      // 为字符串类型时,可认为就是url
      url = getFileAccessHttpUrl(obj)
      downloadNH5(url)
    }
  } else if (isApp) {
    url = encodeURI(obj)
    downloadNH5(url)
  } else if (isH5) {
    url = getFileAccessHttpUrl(obj)
    window.open(url)
  }
}
/**
 * 非H5文件下载地址
 * @param 文件路径 url
 */
function downloadNH5(url) {
  const image_arr = ['png', 'jpg', 'jpeg']
  const fileType = url.split('.').pop()
  uni.downloadFile({
    url,
    success: (res) => {
      if (res.statusCode == 200) {
        let filePath = res.tempFilePath
        const system = uni.getSystemInfoSync().platform
        if (system == 'ios') {
          filePath = encodeURI(filePath)
        }
        if (isMp) {
          const suffix = getSuffix(url).toLowerCase()
          if (image_arr.indexOf(suffix) != -1) {
            // 预览图片
            uni.previewImage({
              urls: [filePath],
            })
          } else {
            uni.openDocument({
              filePath,
              fileType,
              success: (res) => {
                console.log('打开文档成功')
              },
              fail: (res) => {
                console.log('打开文档失败')
                tip.error('不支持打开此格式', true)
              },
            })
          }
        } else if (isApp) {
          const suffix = getSuffix(url).toLowerCase()
          if (image_arr.indexOf(suffix) != -1) {
            // 预览图片
            uni.previewImage({
              urls: [filePath],
            })
          } else {
            uni.saveFile({
              tempFilePath: filePath,
              success: (res) => {
                // 保存成功并打开文件
                tip.success('保存成功')
                uni.openDocument({
                  filePath: res.savedFilePath,
                  success: function (res) {
                    console.log('openDocument', res)
                  },
                  fail() {
                    uni.showToast({
                      title: '暂不支持打开此类型',
                      duration: 2000,
                    })
                  },
                })
              },
              fail: () => tip.alert('保存失败'),
            })
          }
        }
      } else {
        tip.alert('文件异常')
      }
    },
    fail: (err) => {
      tip.alert('下载失败')
    },
  })
}
// 获取后缀
export function getSuffix(text) {
  if (text) {
    const arr = text.split('.')
    const suffix = arr[arr.length - 1]
    return suffix
  }
  return text
}
/**
 * 获取后缀图标
 * @param {Object} doctype
 */
export function getFileIcon(doctype) {
  const file_doc_type_arr = ['doc', 'excel', 'file', 'folder', 'image', 'pdf', 'video']

  if (file_doc_type_arr.indexOf(doctype) >= 0) {
    return `/static/${doctype}.png`
  } else {
    return '/static/file.png'
  }
}
/**
 * 获取uuid
 */
export function uuid() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    var r = (Math.random() * 16) | 0,
      v = c == 'x' ? r : (r & 0x3) | 0x8
    return v.toString(16)
  })
}

export const queryString = (params) => {
  return Object.entries(params)
    .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value as string)}`)
    .join('&')
}

/**
 * 将任意日期转换为对应季度的首日
 * @param {Date|string} date - 日期对象或日期字符串
 * @returns {string} 格式为'YYYY-MM-DD'的季度首日
 */
export function dateToQuarterStart(date) {
  if (!date){
    return ''
  }
  // 如果传入的是字符串,转换为Date对象
  if (typeof date === 'string') {
    date = new Date(date)
  }

  const year = date.getFullYear()
  const month = date.getMonth() // 0-11

  // 计算季度
  const quarter = Math.floor(month / 3)

  // 确定季度首月的月份 (0=1月, 3=4月, 6=7月, 9=10月)
  const quarterStartMonth = quarter * 3

  // 格式化为'YYYY-MM-DD'
  return `${year}-${String(quarterStartMonth + 1).padStart(2, '0')}-01`
}

//时间数组
export function timeData(startTime = '08:00', endTime = '18:30', timeInterval = 0.5) {
  const time = []
  const date = timeStamp(Date.now()).allDate
  const startDate = `${date} ${startTime}`
  const endDate = `${date} ${endTime}`
  const startTimeStamp = new Date(startDate).getTime()
  const endTimeStamp = new Date(endDate).getTime()
  const timeStr = 3600 * 1000 * timeInterval
  console.log(startTimeStamp)
  for (let i = startTimeStamp; i <= endTimeStamp; i = i + timeStr) {
    const timeObj:any = {}
    timeObj.time = timeStamp(i).hour
    timeObj.disable = 0
    time.push(timeObj)
  }
  return time
}
//字符串拼接
function strFormat(str) {
  return str < 10 ? `0${str}` : str
}
//时间戳转字符串
export function timeStamp(time) {
  const dates = new Date(time)
  const year = dates.getFullYear()
  const month = dates.getMonth() + 1
  const date = dates.getDate()
  const day = dates.getDay()
  const hour = dates.getHours()
  const min = dates.getMinutes()
  const days = ['日', '一', '二', '三', '四', '五', '六']
  return {
    allDate: `${year}/${strFormat(month)}/${strFormat(date)}`,
    date: `${strFormat(month)}-${strFormat(date)}`, //返回的日期 07-01
    day: `星期${days[day]}`, //返回的礼拜天数  星期一
    hour: strFormat(hour) + ':' + strFormat(min) //返回的时钟 08:00
  }
}
/**
 * 字符串转时间
 * @param {Object} dateString
 */
export function stringToDate(dateString) {
  var newDate = new Date();
  var yearNum = Number(dateString.substr(0,4));
  var monthNum = Number(dateString.substr(5,2))-1;
  var dayNum = Number(dateString.substr(8,2));
  var hourNum = Number(dateString.substr(11,2));
  var minuteNum = Number(dateString.substr(14,2));

  //var secondNum = Number(dateString.substr(12,2));
  newDate.setFullYear(yearNum);
  newDate.setMonth(monthNum);
  newDate.setDate(dayNum);
  newDate.setHours(hourNum);
  newDate.setMinutes(minuteNum);
  //newDate.setSeconds(secondNum);
  return newDate;
}