Skip to content
On this page

这是函数库的整个文件

Hidden Title

ts
import { unref, isRef, toRaw } from '@vue/reactivity'
import type { Ref } from '@vue/reactivity'
import { consola } from 'consola'
import { cloneDeep } from 'es-toolkit' // 这里不要lodash-es的原因是, 体积太大, 超过500kb无法打包
import { ElMessage, ElMessageBox, MessageOptions } from 'element-plus'

/**
 * 现有方法如下
 * $toast(message, type: string | object = 'success', otherParams: object = {})
 * setStorage(storageName: string, params: any, isSession = false)
 * getStorage(data, isSession = false)
 * clearStorage(str: string | [] | object = '')
 * validForm(ref, { message = '表单校验错误, 请检查', detail = false, showMessage = true } = {})
 * isEmpty(data: any): boolean
 * notEmpty(v: any): boolean
 * merge(obj1: object, obj2: object): object
 * clone(data, times = 1)
 * formatTime(time, cFormat = '{y}-{m}-{d} {h}:{i}:{s}')
 * formatDurationTime(timestamp, cFormat = '{d} 天 {h} 时 {i} 分 {s} 秒')
 * uuid(type = '',length = 4,{ emailStr = '@qq.com', timeStr = '{m}-{d} {h}:{i}:{s}', startStr = '', optionsIndex = null } = {},)
 * getType(type)
 * sleep(delay = 0, fn?: () => void)
 * validate(type = 'required', rules = {}, pureValid = false)
 * asyncWrapper(func, ...args)
 * formatImg(photoName, addPath = '', { basePath = 'assets/images' } = {})
 * copy = (text, toastParams = {})
 * formatThousands(number)
 * log(variableStr, variable, otherInfo = '')
 * random(min = 0, max = 10)
 * toLine(text, connect = '-')
 * processWidth(initValue, isBase = false)
 * formatBytes(bytes)
 * formatBytesConvert(bytes)
 * throttle(fn, delay = 1000)
 * debounce(fn, delay = 1000)
 * confirm(message, options)
 * formatNewLines(str)
 * getVariable('--green')
 */
export const isString = (val: any): val is string => typeof val === 'string'
export const isStringNumber = (val: string): boolean => {
  if (!isString(val)) {
    return false
  }
  return !Number.isNaN(Number(val))
}

export const isNumber = (val: any): val is number => typeof val === 'number'

/**
 * @example1
  proxy.$toast('保存成功') // s:success; i: info; w: warning; e: error;  
  proxy.$toast('保存失败', 'e')
  proxy.$toast('永不关闭', {duration: 0})
  proxy.$toast({
    message: 'andy',
    type: 'warning',
    duration: 300,
    closeAll: true,
  })
* $toast.success('This is a success message')
* @example2 显示对象 
* $toast({
    dangerouslyUseHTMLString: true,
    message: `<h6>复制成功</h6><pre>${JSON.stringify(obj, null, 2)}</pre>`,
    type: 'success',
    duration: 5000,
  })
*/
type MessageType = 'success' | 'info' | 'error' | 'warning'
type ShortType = 's' | 'i' | 'e' | 'w'
type ToastType = MessageType | ShortType
type ToastOptions = Partial<MessageOptions> & { closeAll?: boolean }

export function $toast(
  message: string | ToastOptions,
  type: ToastType | ToastOptions = 'success',
  otherParams: ToastOptions = {},
): void {
  const typeMap: Record<ShortType, MessageType> = {
    s: 'success',
    i: 'info',
    e: 'error',
    w: 'warning',
  }

  function isShortType(t: any): t is ShortType {
    return ['s', 'i', 'e', 'w'].includes(t)
  }

  function isToastOptions(obj: any): obj is ToastOptions {
    return typeof obj === 'object' && obj !== null
  }

  // Case 1: message is options object
  if (isToastOptions(message)) {
    if (message.closeAll) {
      ElMessage.closeAll()
    }
    ElMessage(message)
    return
  }

  // Case 2: type is options object
  if (isToastOptions(type)) {
    if (type.closeAll) {
      ElMessage.closeAll()
    }
    ElMessage({
      message,
      type: 'success',
      ...type,
    })
    return
  }

  // Case 3: regular message with type and options
  if (otherParams.closeAll) {
    ElMessage.closeAll()
  }

  const resolvedType = isShortType(type) ? typeMap[type] : type

  ElMessage({
    message,
    type: resolvedType,
    ...otherParams,
  })
}

// Add shorthand methods for each type of message
$toast.success = (message, otherParams = {}) => $toast(message, 'success', otherParams)
$toast.info = (message, otherParams = {}) => $toast(message, 'info', otherParams)
$toast.error = (message, otherParams = {}) => $toast(message, 'error', otherParams)
$toast.warning = (message, otherParams = {}) => $toast(message, 'warning', otherParams)

export function setStorage(storageName: string, params: any, isSession = false) {
  let handleParams
  if (typeof params === 'number' || typeof params === 'string') {
    handleParams = params
  } else {
    handleParams = JSON.stringify(params)
  }
  if (isSession) {
    sessionStorage.setItem(storageName, handleParams)
  } else {
    localStorage.setItem(storageName, handleParams)
  }
}

export function getStorage(data, isSession = false) {
  // 先获取localStorage数据, 如果没有再获取sessionStorage数据。 如果都没有, null;
  let getLocalData = ''
  let getSessionData = ''
  // 如果isSessionFirst为true, 先判断sessionStorage, 后判断localStorage
  if (isSession) {
    getSessionData = sessionStorage.getItem(data)
  } else {
    getLocalData = localStorage.getItem(data)
  }
  if (getLocalData) {
    try {
      if (typeof JSON.parse(getLocalData) !== 'number') {
        getLocalData = JSON.parse(getLocalData)
      }
    } catch (e) {}
    return getLocalData
  } else if (getSessionData) {
    try {
      if (typeof JSON.parse(getSessionData) !== 'number') {
        getSessionData = JSON.parse(getSessionData)
      }
    } catch (e) {}
    return getSessionData
  }
  return null
}

/**
 *
 * @param {*} str 需要清空的localStorage或sessionStorage, 如果不传清空所有
 * @param {*} param1 需要排除的sessionStorage或者localStorage
 * @example
 * clearStorage()
 * clearStorage('loginId')
 * clearStorage(['loginId', 'token'])
 * clearStorage({ exclude: ['loginId', 'token'] })
 */
export function clearStorage(str: string | [] | object = '') {
  if (isEmpty(str)) {
    sessionStorage.clear()
    localStorage.clear()
  }
  if (notEmpty(str) && getType(str) !== 'object') {
    let strArr = Array.isArray(str) ? str : [str]
    for (let i = 0; i < strArr.length; i++) {
      sessionStorage.removeItem(strArr[i])
      localStorage.removeItem(strArr[i])
    }
  }
  if (_isObjectWithExclude(str)) {
    if (notEmpty(str.exclude) && getType(str) === 'object') {
      let sessionStorageObj = {}
      let localStorageObj = {}
      for (const key in str.exclude) {
        if (Object.prototype.hasOwnProperty.call(str.exclude, key)) {
          const name = str.exclude[key]
          if (getStorage(name)) {
            localStorageObj[name] = getStorage(name)
          }
          if (getStorage(name, true)) {
            sessionStorageObj[name] = getStorage(name, true)
          }
        }
      }
      sessionStorage.clear()
      localStorage.clear()
      for (const key in sessionStorageObj) {
        setStorage(key, sessionStorageObj[key], true)
      }
      for (const key in localStorageObj) {
        setStorage(key, localStorageObj[key])
      }
    }
  }
}
// 自定义类型守卫函数
function _isObjectWithExclude(obj: object | string | []): obj is { exclude: { [key: string]: string } } {
  return typeof obj === 'object' && obj !== null && 'exclude' in obj && typeof obj.exclude === 'object'
}

/**
 * element-plus的form表单使用promise进行封装
 * @param ref
 * @param param1
 * @returns Promise
 * await proxy.validForm(formRef);
 * await proxy.validForm(formRef, {message: '自定义错误'});
 * await proxy.validForm(formRef, {showMessage: false});
 * await proxy.validForm(formRef, {detail: true});
 */

export function validForm(ref, { message = '表单校验错误, 请检查', detail = false, showMessage = true } = {}) {
  return new Promise((resolve, reject) => {
    unref(ref).validate((valid, status) => {
      if (valid) {
        resolve(status)
      } else {
        if (message && showMessage) {
          let errorText = Object.keys(status)
          let toastMessage = message
          if (detail) {
            toastMessage = message + errorText.join(',')
          }
          $toast(toastMessage, 'e')
        }
        reject(status)
      }
    })
  })
}

/**
 * 判断变量是否空值
 * undefined, null, '', '   ', false, 0, [], {}, NaN, new Set(), new Map(), BigInt(0), new Date('无效日期') 均返回true,否则返回false
 */
export function isEmpty(data: any, strict = false): boolean {
  if (isRef(data)) {
    data = unref(data)
  }

  // 处理严格模式(strict=true 时,0/false 不算空)
  if (strict) {
    if (data === false || data === 0 || data === BigInt(0)) {
      return false
    }
  }

  // 处理 null/undefined
  if (data == null) return true
  // 如果是日期对象,检查它是否是有效的日期
  if (data instanceof Date) {
    return isNaN(data.getTime())
  }
  // 处理基础类型
  switch (typeof data) {
    case 'string':
      return data.trim().length === 0
    case 'boolean':
      return !data
    case 'number':
      return 0 === data || isNaN(data) // ❗ `NaN`或者0 被认为是空
    case 'symbol':
      return false
    case 'bigint':
      return data === BigInt(0)
  }
  // 处理集合类型
  if (data instanceof Map || data instanceof Set) return data.size === 0
  // 处理数组/类数组
  if (
    Array.isArray(data) ||
    (typeof data.length === 'number' && Object.prototype.toString.call(data) === '[object Object]')
  ) {
    return data.length === 0
  }

  // 处理普通对象
  if (typeof data === 'object') {
    return Object.keys(data).length === 0
  }

  return false
}
// 非空
export function notEmpty(v: any): boolean {
  return !isEmpty(v)
}

/**
 *  将两个对象合并, 以第二个对象为准, 如果两个对象, 一个属性有值, 一个没值, 合并后有值; 如果两个属性都有值, 以第二个属性为准
 *  */
export function merge(obj1: object, obj2: object): object {
  let merged = { ...obj1, ...obj2 }
  for (let key in merged) {
    if (!isEmpty(obj1[key]) && !isEmpty(obj2[key])) {
      merged[key] = obj2[key]
    } else if (isEmpty(obj1[key]) && !isEmpty(obj2[key])) {
      merged[key] = obj2[key]
    } else if (!isEmpty(obj1[key]) && isEmpty(obj2[key])) {
      merged[key] = obj1[key]
    }
  }
  return merged
}

/**
 * 克隆数据并根据需要复制数组
 * @param {any} data - 要克隆的数据
 * @param {number} [times=1] - 如果是数组,要复制的次数
 * @returns {any} - 克隆后的数据或复制后的数组
 * clone(123) => 123
 * clone([1,2, {name: 'andy'}], 2) => [1, 2, {name: 'andy'}, 1, 2, {name: 'andy'}]
 */
export function clone(data, times = 1) {
  if (isRef(data)) {
    data = unref(data)
  }
  // Check if the data is not an array
  if (getType(data) !== 'array') {
    // If not an array, return a deep clone of the data
    return cloneDeep(data)
  }
  const clonedData = cloneDeep(data)
  const result: typeof clonedData = []
  for (let i = 0; i < times; i++) {
    result.push(...clonedData)
  }
  return result
}

/**
 * 格式化时间为年月日时分秒的格式, 格式可以自定义。
 * ① 时间戳10位和13位都可以转换成格式化的日期
 * ② java8格式的日期和有效的日期都可以转换成定义的日期格式
 * @param {Date, string}  都有默认参数
 * @example
 * formatTime() // 2020-07-17 09:53:07
 * formatTime('2018-02-13T06:17') // 2018-02-13 06:17:00
 * formatTime('2020/03/02 06:02') // 2020-03-02 06:02:00
 * formatTime(1541927611000); //2018-11-11 17:13:21
 * formatTime(1541927611000, "{y}年{m}月{d}日 {h}时{m}分{s}秒"); // 2018年11月11日 17时11分31秒
 * formatTime(1541927611, "{y}/{m}/{d} {h}:{m}:{s}"); // 2018/11/11 17:11:31
 * formatTime(new Date()); // 2018-11-11 17:13:21
 * formatTime(new Date().getTime()); // 2018-11-11 17:13:21
 * formatTime('1764128798.456'); // 2025/11/26 11:11:38
 */
export function formatTime(time, cFormat = '{y}-{m}-{d} {h}:{i}:{s}') {
  if (!time) {
    return time
  }
  let date
  if (typeof time === 'object') {
    date = time
  } else {
    const timeStr = '' + time
    // 处理带小数的时间戳格式,如 1764128798.456
    if (timeStr.includes('.') && !isNaN(parseFloat(timeStr))) {
      time = parseFloat(time) * 1000
    } else if (timeStr.length === 10) {
      time = parseInt(time) * 1000
    }
    date = new Date(time)
  }
  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 = cFormat.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
    let value = formatObj[key] // Note: getDay() returns 0 on Sunday
    if (key === 'a') {
      return ['日', '一', '二', '三', '四', '五', '六'][value]
    }
    if (result.length > 0 && value < 10) {
      value = '0' + value
    }
    return value || 0
  })
  return time_str
}

/**
 *
 * @param timestamp 持续的时间戳
 * @param cFormat 格式化的规则
 * @returns 天时分秒的字符串
 * @example
 * formatDurationTime(1162821) => 19分24秒
 */
export function formatDurationTime(timestamp, cFormat = '{d}天{h}时{i}分{s}秒') {
  const secondsPerMinute = 60
  const minutesPerHour = 60
  const hoursPerDay = 24
  let totalSeconds = Math.floor(timestamp / 1000)
  let days = 0
  // 只有返回天了, 才计算时间有多少天
  if (cFormat.indexOf('d') !== -1) {
    days = Math.floor(totalSeconds / (secondsPerMinute * minutesPerHour * hoursPerDay))
    totalSeconds %= secondsPerMinute * minutesPerHour * hoursPerDay
  }
  // 计算总秒数
  let hours = Math.floor(totalSeconds / (secondsPerMinute * minutesPerHour))
  totalSeconds %= secondsPerMinute * minutesPerHour
  let minutes = Math.floor(totalSeconds / secondsPerMinute)
  let seconds = totalSeconds % secondsPerMinute
  const formatObj = {
    d: days,
    h: hours,
    i: minutes,
    s: seconds,
  }
  let parseFormat = cFormat
  if (days === 0) {
    parseFormat = cFormat.match(/{h}.*/g)[0]
    if (hours === 0) {
      parseFormat = cFormat.match(/{i}.*/g)[0]
      if (minutes === 0) {
        parseFormat = cFormat.match(/{s}.*/g)[0]
      }
    }
  }
  const time_str = parseFormat.replace(/{(y|m|d|h|i|s)+}/g, (result, key) => {
    let value = formatObj[key] // Note: getDay() returns 0 on Sunday
    if (result.length > 0 && value < 10 && value != 0) {
      value = '0' + value
    }
    return value || '00'
  })
  return time_str
}

/**
 * 生成 UUID
 * @param type - 生成 UUID 的类型,可以是 'phone', 'email', 'time', 'number', 'ip', 'port' 或空字符串
 * @param length - 生成字符串的长度(默认为4)
 * @param options - 额外的选项
 * @param options.emailStr - 生成 email 时使用的后缀(默认为 '@qq.com')
 * @param options.timeStr - 生成时间字符串的格式(默认为 '{m}-{d} {h}:{i}:{s}')
 * @param options.startStr - 起始字符串(默认为空)
 * @param options.optionsIndex - 数组索引(默认为随机)
 * @returns 生成的 UUID (字符串或数字)
 * * uuid("名字") => 名字hc8f
 * uuid() => abcd
 * uuid('time') => 25MR 10-27 17:34:01
 * uuid('time', 0, {startStr:'andy', timeStr:"{h}:{i}:{s}"}) => andy 17:38:23
 * uuid('phone') => 13603312460
 * uuid('email') => cBZA@qq.com
 * uuid('number') => 2319
 * uuid([ { label: "小泽泽", value: "xzz" },{ label: "小月月", value: "xyy" }]) => xzz
 */
export function uuid(
  type: string | Array<{ label: string; value: any }> = '',
  length = 4,
  options: {
    emailStr?: string
    timeStr?: string
    startStr?: string
    optionsIndex?: number | null
  } = {},
): string | number {
  const { emailStr = '@qq.com', timeStr = '{y}-{m}-{d} {h}:{i}:{s}', startStr = '', optionsIndex = null } = options

  // 辅助函数:判断是否为ref对象
  function isRef(obj: any): obj is Ref<any> {
    return obj && typeof obj === 'object' && obj._isRef === true
  }

  // 辅助函数:获取ref的实际值
  function unref<T>(ref: Ref<T> | T): T {
    return isRef(ref) ? ref.value : ref
  }

  // 辅助函数:生成随机数
  function random(min: number, max: number): number {
    return Math.floor(Math.random() * (max - min + 1) + min)
  }

  // 解包可能为ref的参数
  type = unref(type)

  // 处理数组类型参数(下拉框选项)
  if (Array.isArray(type)) {
    if (type.length === 0) return ''

    const randIndex = optionsIndex ?? random(0, type.length - 1)
    const selectedItem = type[randIndex]

    // 如果数组项是对象且有value属性,则返回value
    if (typeof selectedItem === 'object' && selectedItem !== null && 'value' in selectedItem) {
      return selectedItem.value
    }

    // 否则直接返回数组项
    return selectedItem
  }

  // 生成随机字符串
  let randomChars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
  let result = startStr

  // 生成手机号
  if (type === 'phone') {
    const prefixes = ['130', '131', '132', '133', '135', '136', '137', '138', '170', '187', '189']
    result = prefixes[random(0, prefixes.length - 1)]

    for (let i = 0; i < 8; i++) {
      result += Math.floor(Math.random() * 10)
    }
    return result
  }

  // 生成邮箱
  if (type === 'email') {
    result = uuid(startStr, length) + emailStr
    return result
  }

  // 生成时间
  if (type === 'time') {
    return uuid(startStr, length, options) + ' ' + formatTime(new Date(), timeStr)
  }

  // 生成数字
  if (type === 'number') {
    const numChars = '123456789'
    result = ''

    for (let i = 0; i < length; i++) {
      result += numChars[random(0, numChars.length - 1)]
    }
    return Number(result)
  }

  // 生成IP地址
  if (type === 'ip') {
    const randomNum = random(1, 99)
    return `10.0.11.${randomNum}`
  }

  // 生成端口号
  if (type === 'port') {
    return random(1, 65535)
  }

  // 生成普通随机字符串
  for (let i = 0; i < length; i++) {
    result += randomChars[random(0, randomChars.length - 1)]
  }

  return result
}

// 声明全局的Ref接口
declare interface Ref<T = any> {
  _isRef: boolean
  value: T
}

/**
 * 判断传入参数的类型
 * @param {*} type
 * getType(new RegExp()) regexp
 * getType(new Date()) date
 * getType([]) array
 * getType({}) object
 * getType(null) null
 * getType(123) number
 */
export function getType(type) {
  if (typeof type === 'object') {
    const objType = Object.prototype.toString.call(type).slice(8, -1).toLowerCase()
    return objType
  } else {
    return typeof type
  }
}

/**
 * 一个辅助函数,用于在代码中创建一个暂停(延迟)。
 * 它返回一个 Promise,你可以在 `await` 后使用它来实现类似 "sleep" 的效果。
 *
 * @param delay - 等待的毫秒数。默认值为 0,表示不延迟。
 * @param fn - (可选) 一个在延迟结束后立即执行的函数。
 *
 * @returns 一个 Promise,当延迟结束后解析(resolve)。
 *
 * @example
 * // 基本用法:延迟 2 秒后打印消息
 * console.log('开始');
 * await sleep(2000);
 * console.log('2秒后执行');
 *
 * @example
 * // 带回调函数的用法:延迟 1 秒后执行清理工作
 * sleep(1000, () => {
 *   console.log('执行清理操作...');
 *   // 清理代码...
 * });
 *
 * @example
 * // 在循环中使用:每次迭代后延迟 500 毫秒
 * for (let i = 0; i < 5; i++) {
 *   console.log(`当前值: ${i}`);
 *   await sleep(500);
 * }
 */
export function sleep(delay = 0, fn?: () => void) {
  return new Promise<void>((resolve) =>
    setTimeout(() => {
      fn?.()
      resolve()
    }, delay),
  )
}

/** @使用方式 
 * 1. 在el-form中使用
name: [ proxy.validate('name', { message: '你干嘛哈哈' })],
between: [ proxy.validate('between', { max: 99 })],
number: [ proxy.validate('number')],
length: [proxy.validate('length')],
mobile: [ proxy.validate('mobile')],
ip: [ proxy.validate('ip')],
custom: [proxy.validate('custom', { message: '最多保留2位小数', reg: /^\d+\.?\d{0,2}$/ })]
confirmRegPwd: [
  proxy.validate('same', { value: form.value.regPwd }),
], //1. 如果判断两个密码一致, 还要在input输入值改变的时候, 再校验一下两个input
  formRef.value.validate('regPwd')
  formRef.value.validate('confirmRegPwd')
  // 2. rules需要使用computed包裹, 否则值改变无法传递
 * 2. 在函数中使用, 返回boolean
 let ip = proxy.validate('ip', 122322, true)
 let custom = proxy.validate('custom', { value: -123, reg: /^-\d+\.?\d{0,2}$/ }, true)
*/
export function validate(type = 'required', rules = {}, pureValid = false) {
  // 如果第一个参数是对象, 则相当于重载
  if (getType(type) === 'object') {
    pureValid = rules || false
    rules = type
    type = 'required'
  }
  // let trigger = rules.trigger || ['blur', 'change']
  let trigger = rules.trigger || []
  const typeMaps = ['required', 'pwd', 'number', 'mobile', 'between', 'length', 'same', 'ip', 'port', 'custom']
  let parseRequired = rules.required ?? true

  // 如果不包含typeMaps中的类型, 直接将第一个参数作为message
  if (!typeMaps.includes(type)) {
    return {
      required: parseRequired,
      message: type,
      trigger: trigger,
    }
  }
  if (type === 'required') {
    return {
      required: parseRequired,
      message: rules.message ?? '请输入',
      trigger: trigger,
    }
  }

  // validator: this.validateName,
  if (type === 'password') {
    const validateName = (rule, value, callback) => {
      let validFlag = /^[a-zA-Z0-9_-]+$/.test(value)
      if (!validFlag) {
        callback(new Error(rules.message || '密码只能由英文、数字、下划线、中划线组成'))
      } else {
        callback()
      }
    }
    return {
      validator: validateName,
      trigger: trigger,
    }
  }
  if (type === 'positive' || type === 'number') {
    // 正整数
    return _validValue(rules, '请输入正整数', pureValid, /^[1-9]+\d*$/)
  }
  if (type === 'zeroPositive') {
    // 正整数且包含0
    return _validValue(rules, '请输入非负整数', pureValid, /^(0|[1-9]+\d*)$/)
  }
  // 整数, 包含负数和0
  if (type === 'integer') {
    return _validValue(rules, '请输入整数', pureValid, /^(0|[-]?[1-9]\d*)$/)
  }
  // 非负数, 整数和最多2位小数
  if (type === 'decimal') {
    return _validValue(rules, '请输入非负数字, 包含小数且最多2位', pureValid, /(0|[1-9]\d*)(\.\d{1, 2})?|0\.\d{1,2}/)
  }
  if (type === 'mobile') {
    return _validValue(rules, '请输入正确的手机号', pureValid, /^[1][0-9]{10}$/)
  }
  if (type === 'ip') {
    return _validValue(
      rules,
      '请输入正确的ip地址',
      pureValid,
      /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
    )
  }
  if (type === 'between') {
    let min = rules.min || 1
    let max = rules.max || 10
    const validateBetween = (rule, value, callback) => {
      let validFlag = /^[0-9]+$/.test(value)
      if (!validFlag) {
        callback(new Error('请输入数字'))
      }
      if (value < min) {
        callback(new Error(`数字不能小于${min}`))
      }
      if (value > max) {
        callback(new Error(`数字不能大于${max}`))
      }
      callback()
    }
    return {
      validator: validateBetween,
      trigger: trigger,
      required: parseRequired,
    }
  }
  if (type === 'length') {
    return {
      min: rules.min,
      max: rules.max,
      message: rules.message ?? `请输入${rules.min}到${rules.max}个字符`,
      trigger: trigger,
      required: parseRequired,
    }
  }
  if (type === 'port') {
    return _validValue(
      rules,
      '请输入1-65535的端口号',
      pureValid,
      /^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-5][0-5][0-3][0-5])$/,
    )
  }

  if (type === 'same') {
    const validateSame = (rule, value, callback) => {
      let isSame = value === rules.value
      if (!isSame) {
        const errMessage = rules.message || '密码和确认密码要一致'
        callback(new Error(errMessage))
      }
      if (parseRequired && !value) {
        callback(new Error(rules.message || '请输入'))
      }
      callback()
    }
    let res = {
      validator: validateSame,
      trigger: trigger,
      required: parseRequired,
    }
    return res
  }
  if (type === 'custom') {
    //  _validValue(rules, '请输入正确的手机号', pureValid, /^[1][0-9]{10}$/)
    if (pureValid) {
      return _validValue(rules.value, rules.message, pureValid, rules.reg)
    } else {
      return _validValue(rules, rules.message, pureValid, rules.reg)
    }
  }

  function _validValue(rules, msg, pureValid, reg) {
    if (pureValid === true) {
      return reg.test(rules)
    }
    const validatePhone = (rule, value, callback) => {
      let validFlag = reg.test(value)
      if (!validFlag) {
        callback(new Error(rules.message ?? msg))
      } else {
        callback()
      }
    }
    return {
      validator: validatePhone,
      required: rules.required ?? true,
      trigger: trigger,
    }
  }
}

/**
 * 
const { res, err } = await proxy.asyncWrapper(listTests, pickForm);
if (err) {
	return;
}
 */
export async function asyncWrapper(func, ...args) {
  try {
    const res = await func(...args)
    return { res }
  } catch (err) {
    return { err }
  }
}

/** 获取assets静态资源
 * @example
 *  proxy.formatImg('1.png')
 *  proxy.formatImg('1.png', 'menu')
 *  */
export function formatImg(photoName, addPath = '', { basePath = 'assets/images' } = {}) {
  if (photoName.startsWith('http') || photoName.startsWith('https')) {
    return photoName
  }
  if (photoName.indexOf('.') === -1) {
    photoName = photoName + '.png'
  }
  const addLastSlash = addPath.endsWith('/') || !addPath ? addPath : `${addPath}/`
  const addLastBasePathSlash = basePath.endsWith('/') || !basePath ? basePath : `${basePath}/`
  let mergeSrc = `${addLastSlash}${photoName}`
  // '../assets/images/1.png'
  let res = new URL(`../${addLastBasePathSlash}${mergeSrc}`, import.meta.url).href
  return res
}

/**
 * 复制文本
 *
 * copy('这是要复制的文本');
 * copy('这是要复制的文本', {duration: 500});
 *
 *  */
export const copy = (text, toastParams = {}) => {
  const textarea = document.createElement('textarea')
  textarea.value = text
  textarea.style.position = 'fixed'
  document.body.appendChild(textarea)
  textarea.select()
  document.execCommand('copy')
  document.body.removeChild(textarea)
  if (!toastParams.hideToast) {
    $toast(text + '复制成功', toastParams)
  }
}

/**
 * 1234 => 1,234
 * 1234b => 1,234b
 * 1234.12b => 1,234.12b
 * @param number 加千分位
 * @returns
 */
export function formatThousands(number) {
  // 提取数字部分、小数点和小数部分
  let matches = ('' + number).match(/^([\d,]+)(\.?)(\d+)?(\D+)?$/)
  if (!matches) {
    return number // 如果没有找到匹配,则返回原始输入
  }

  let numericString = matches[1].replace(/\D/g, '') // 仅保留数字
  let decimalString = matches[3] ? `.${matches[3]}` : '' // 小数部分,如果没有则为空字符串
  let unit = matches[4] || '' // 单位部分,如果没有则为空字符串

  // 添加千位分隔符
  let numberWithSeparator = numericString.replace(/\B(?=(\d{3})+(?!\d))/g, ',')

  // 拼接数字、小数点、小数部分和单位,并返回结果
  return `${numberWithSeparator}${decimalString}${unit}`
}
/*
*
* @example
const str = ref(11)
proxy.log(`variableStr`, variableStr, "/cyrd/oeos-components/packages/utils/index.ts");
 */
export function log(variableStr, variable, otherInfo = '') {
  const stack = new Error().stack.split('\n')[2].trim() // 获取调用堆栈的第二行
  const matchResult = stack.match(/\((.*):(\d+):(\d+)\)/)
  let fileInfo = ''
  try {
    if (matchResult && otherInfo) {
      const lineNumber = matchResult[2]
      fileInfo = `vscode://file${JSON.parse(otherInfo)}:${lineNumber}`
    }
  } catch (error) {
    fileInfo = otherInfo
  }

  if (isRef(variable)) {
    let unrefVariable = unref(variable)
    _log(toRaw(unrefVariable))
  } else {
    _log(variable)
  }
  function _log(consoleData) {
    if (getType(consoleData) === 'object' || getType(consoleData) === 'array') {
      consola.log(
        `%c${variableStr} `,
        'background:#fff; color: blue;font-size: 0.8em',
        JSON.stringify(consoleData, null, '\t'),
        `${fileInfo}`,
      )
    } else {
      consola.log(`%c${variableStr} `, 'background:#fff; color: blue;font-size: 0.8em', consoleData, `${fileInfo}`)
    }
  }
  function getType(type) {
    if (typeof type === 'object') {
      const objType = Object.prototype.toString.call(type).slice(8, -1).toLowerCase()
      return objType
    } else {
      return typeof type
    }
  }
}

/**
 * 生成指定范围内的随机整数
 *
 * @param min 最小值,默认为0
 * @param max 最大值,默认为10
 * @returns 返回生成的随机整数
 */
export function random(min = 0, max = 10) {
  return Math.floor(Math.random() * (max - min + 1)) + min
}

/**
 * 将文本转换为带有连接符的行文本
 *
 * @param text 要转换的文本
 * @param connect 连接符,默认为'-'
 * @returns 返回转换后的行文本
 * toLine('NameAndy') // name-andy
 * toLine('nameAndy') // name-andy
 * toLine('_nameAndy') // _name-andy
 */
export function toLine(text, connect = '-') {
  let translateText = text
    .replace(/([A-Z])/g, (match, p1, offset, origin) => {
      if (offset === 0) {
        return `${match.toLocaleLowerCase()}`
      } else {
        return `${connect}${match.toLocaleLowerCase()}`
      }
    })
    .toLocaleLowerCase()
  return translateText
}

// processWidth(200) // { width: '200px' }
// processWidth('200', true) // 200px
// processWidth('200.33px') // { width: '200.33px' }
// processWidth('') // {}
export function processWidth(initValue, isBase = false) {
  let value = unref(initValue)
  let res = ''
  if (!value) {
    return isBase ? value : {}
  } else if (typeof value === 'number') {
    value = String(value)
  }
  if (value === '') {
    return isBase ? value : {}
  } else if (typeof value === 'string' && !isNaN(value)) {
    res = value + 'px'
  } else if (typeof value === 'string' && /^[0-9]+(\.[0-9]+)?(px|%|em|rem|vw|vh|ch)*$/.test(value)) {
    res = value
  } else {
    console.warn(`${value} is Invalid unit provided`)
    return value
  }
  if (isBase) {
    return res
  }
  return { width: res }
}

/**
 * 增加小数点
 * toFixed(22) -> '22.00'
 * toFixed('22') -> '22.00'
 * toFixed('22', 4) -> '22.0000'
 * toFixed('22', 2) -> 22
 * toFixed('22 TB', {prefix: '$', suffix: '%', unit: false}) -> $22.00%
 */
export function toFixed(
  value: any,
  options: { digit?: number; prefix?: string; suffix?: string; unit?: boolean } | number = {},
) {
  // 如果第二个参数是数字,则将其视为 digit
  if (typeof options === 'number') {
    options = { digit: options }
  }

  // 默认为 digit=2, prefix='', suffix=''
  let { digit = 2, prefix = '', suffix = '', unit = true } = options
  // 提取数字部分、小数点和小数部分
  let matches = ('' + value).match(/^([\d,]+)(\.?)(\d+)?(\D+)?$/)
  if (!matches) {
    return value // 如果没有找到匹配,则返回原始输入
  }

  let numericString = matches[1].replace(/\D/g, '') // 仅保留数字
  let decimalString = matches[3] ? `.${matches[3]}` : '' // 小数部分,如果没有则为空字符串
  let finalUnit = matches[4] || '' // 单位部分,如果没有则为空字符串

  let res = numericString
  if (isStringNumber(numericString) || isNumber(numericString)) {
    res = Number(numericString + decimalString).toFixed(digit)
  }
  if (!unit) {
    finalUnit = ''
  }
  return `${prefix}${res}${finalUnit}${suffix}`
}
/**
 * 格式化字节单位
 * @param bytes - 字节数
 * @param options - 配置项
 * @param options.digit - 小数位数(默认2)
 * @param options.thousands - 是否千分位分隔(默认true)
 * @param options.prefix - 前缀(默认空)
 * @param options.suffix - 后缀(默认空)
 * @param options.roundType - 取整方式:'floor'(向下, 默认) | 'ceil'(向上) | 'round'(四舍五入)
 */
export function formatBytes(
  bytes: number | string,
  options: {
    digit?: number
    thousands?: boolean
    prefix?: string
    suffix?: string
    roundType?: 'floor' | 'ceil' | 'round'
  } = {},
) {
  let { digit = 2, thousands = true, prefix = '', suffix = '', roundType = 'floor' } = options
  // 校验输入
  if (isStringNumber(bytes as any) || isNumber(bytes)) {
    bytes = Number(bytes)
  } else {
    return bytes
  }
  if (bytes <= 1) {
    return Math[roundType](bytes * Math.pow(10, digit)) / Math.pow(10, digit) + ' B'
  }

  const k = 1024
  const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
  const i = Math.floor(Math.log(bytes) / Math.log(k))

  // 根据 roundType 选择取整方式
  const power = Math.pow(k, i)
  let num = bytes / power

  num = Math[roundType](num * Math.pow(10, digit)) / Math.pow(10, digit)

  let res = num.toFixed(digit) + ' ' + sizes[i]

  if (thousands) {
    res = formatThousands(res)
  }

  return `${prefix}${res}${suffix}`
}

/**
 * 字节转数字
 * @param oBytes
 * @param param1
 * @returns number
 * formatBytesConvert('0.5GB') 536870912
 * formatBytesConvert('1,234 GB') 1324997410816
 * formatBytesConvert('1,234 GB', {thousand: true}) 1,324,997,410,816
 * formatBytesConvert('1,234 GB', {toFixed: 2}) 1324997410816.00
 */
export function formatBytesConvert(oBytes, { thounsands = false, digit = 0 } = {}) {
  let bytes = oBytes
  if (isStringNumber(oBytes) || isNumber(oBytes) || getType(oBytes) !== 'string') {
    return parseDigitThounsands(oBytes)
  }
  if (!oBytes) {
    return parseDigitThounsands(oBytes)
  }

  // 如果有千分位, 先将千分位的,去掉
  const regex = /^\d{1,3}(,\d{3})*(\.\d+)?[a-zA-Z ]*$/
  if (regex.test(oBytes)) {
    bytes = oBytes.replace(/,/g, '')
    if (isStringNumber(bytes) || isNumber(bytes) || getType(bytes) !== 'string') {
      return parseDigitThounsands(bytes)
    }
  }

  const bytesRegex = /^(\d+(?:\.\d+)?)\s*([BKMGTPEZY]?B|Byte)$/i
  const units = {
    B: 1,
    BYTE: 1,
    KB: 1024,
    MB: 1024 ** 2,
    GB: 1024 ** 3,
    TB: 1024 ** 4,
    PB: 1024 ** 5,
    EB: 1024 ** 6,
    ZB: 1024 ** 7,
    YB: 1024 ** 8,
  }

  const match = bytes.match(bytesRegex)
  if (!match) {
    console.warn("Invalid bytes format. Please provide a valid bytes string, like '100GB'.")
    return
  }

  const size = parseFloat(match[1])
  const unit = match[2].toUpperCase()

  if (!units.hasOwnProperty(unit)) {
    console.warn(
      "Invalid bytes unit. Please provide a valid unit, like 'B', 'BYTE', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', or 'YB'.",
    )
    return
  }
  function parseDigitThounsands(val) {
    let finalRes = val
    if (digit) {
      finalRes = Number(finalRes).toFixed(digit)
    }
    if (thounsands) {
      finalRes = formatThousands(finalRes)
    }
    return finalRes
  }
  return parseDigitThounsands(size * units[unit])
}

export function throttle(fn, delay = 1000) {
  // last为上一次触发毁掉的时间,timer是定时器
  let last = 0
  let timer = null
  // 将throttle处理结果当做函数返回
  return function () {
    // 保留调用时的this上下文
    let context = this
    // 保留调用时传入的参数
    let args = arguments
    // 记录本次触发回调的时间
    let now = +new Date()
    // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
    if (now - last < delay) {
      // 如果时间间隔小于设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
      clearTimeout(timer)
      timer = setTimeout(function () {
        last = now
        fn.apply(context, args)
      }, delay)
    } else {
      // 如果时间间隔超出了设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
      last = now
      fn.apply(context, args)
    }
  }
}

/**
 * 封装 Promise 执行,提供自动 loading 状态管理
 * @param promise 需要执行的 Promise
 * @param sendLoading 可选的 loading 状态(支持 Ref<boolean> 或 boolean)
 * @returns Promise<{ data: T | null; error: any }>
 * @example1
 * const loading = ref(false);
 * const { data, error } = await tryCatch(fetchUserData(), loading);
 * @example2 // 无视 loading 状态
 * const { data, error } = await tryCatch(fetchUserData());
 */
export function tryCatch<T>(
  promise: Promise<T>,
  sendLoading?: Ref<boolean> | null,
): Promise<{ data: T | null; error: any }> {
  const updateLoading = (value: boolean): void => {
    if (isRef(sendLoading)) {
      sendLoading.value = value
    } else if (sendLoading !== null) {
      console.warn('Cannot modify non-ref sendLoading directly!')
    }
  }

  // 初始化 loading 状态
  updateLoading(true)

  return promise
    .then((data: T) => {
      updateLoading(false)
      return { data, error: null }
    })
    .catch((error: any) => {
      updateLoading(false)
      return { data: null, error }
    })
}

export function debounce(fn, delay = 1000) {
  let timer = null
  return function () {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments)
      timer = null
    }, delay)
  }
}

/**
 * proxy.confirm('确定删除吗?')
 * proxy.confirm('哈哈', { icon: 'el-icon-plus' })
 * close-on-click-modal: 是否可通过点击遮罩层关闭 MessageBox 默认true
 * lock-scroll: 是否在 MessageBox 出现时将 body 滚动锁定. 默认true
 * 设置宽度, 内容使用组件
   import GWarning from '@/autoComponents/gWarning.vue'
    await proxy.confirm('', {
      dangerouslyUseHTMLString: true,
      customStyle: {
        maxWidth: '600px',
      },
      message: h(GWarning, {
        content:
          '对于光存储开启保持原始对象名称后,对象将作为独立文件在光存储介质直接存储。<br>注意:当桶内文件大小普遍较小(<100MB)或过大(>5GB)时不推荐打开此功能!您确定开启此功能吗?',
      }),
      showCancelButton: true,
      cancelButtonText: '取消',
      appendTo: '#highSettingsForm',
    })
 * 如果是多个dialog嵌套, 可以给上层的dom设置个id如highSettingsForm, 然后appendTo: '#highSettingsForm'
 */
export function confirm(message, options) {
  const resolvedMessage = typeof message === 'function' ? message() : message
  // 关键点:直接访问 Element Plus 内部维护的全局上下文
  const elContext =
    ElMessageBox.install?.context || ElMessageBox._context || document.querySelector('#app')?._vue_app?._context
  const mergeOptions = {
    title: '提示',
    draggable: true,
    showCancelButton: false,
    confirmButtonText: '确定',
    dangerouslyUseHTMLString: true, // 允许 HTML
    appContext: elContext, // 强制注入 Element Plus 的上下文
    ...options,
  }
  return ElMessageBox.confirm(resolvedMessage, mergeOptions)
}

/**
 * 格式化字符串中的换行符和制表符
 * @param str 待格式化的字符串
 * @returns 格式化后的字符串,如果输入的不是字符串或为空,则返回原字符串
 * @example
$toast(
  formatNewLines(
    'Example file\n  File : 111.jpeg\n  CreateTime : 1721011155921 2024-07-15 10:39:15\n  LastUpdateTime : 1721011155921 2024-07-15 10:39:15\n------------------------------------------------------------------------\nExtract:\n  aa=231\n------------------------------------------------------------------------\n',
  ),
  {
    duration: 5000,
    dangerouslyUseHTMLString: true,
  },
)
 */
export function formatNewLines(str) {
  // 如果 str 为空或者不是字符串类型,则直接返回 str
  if (!str || typeof str !== 'string') {
    return str
  }
  // 替换换行符 \n 为 <br> 标签
  str = str.replace(/\n/g, '<br>')
  // 替换制表符 \t 为四个空格或其他适当的字符
  str = str.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
  return str
}

/** Function to get a CSS custom property value
 *
 * getVariable('--blue');
 *  */
export function getVariable(propertyName) {
  let res = getComputedStyle(document.documentElement).getPropertyValue(propertyName).trim()
  return res
}
import { unref, isRef, toRaw } from '@vue/reactivity'
import type { Ref } from '@vue/reactivity'
import { consola } from 'consola'
import { cloneDeep } from 'es-toolkit' // 这里不要lodash-es的原因是, 体积太大, 超过500kb无法打包
import { ElMessage, ElMessageBox, MessageOptions } from 'element-plus'

/**
 * 现有方法如下
 * $toast(message, type: string | object = 'success', otherParams: object = {})
 * setStorage(storageName: string, params: any, isSession = false)
 * getStorage(data, isSession = false)
 * clearStorage(str: string | [] | object = '')
 * validForm(ref, { message = '表单校验错误, 请检查', detail = false, showMessage = true } = {})
 * isEmpty(data: any): boolean
 * notEmpty(v: any): boolean
 * merge(obj1: object, obj2: object): object
 * clone(data, times = 1)
 * formatTime(time, cFormat = '{y}-{m}-{d} {h}:{i}:{s}')
 * formatDurationTime(timestamp, cFormat = '{d} 天 {h} 时 {i} 分 {s} 秒')
 * uuid(type = '',length = 4,{ emailStr = '@qq.com', timeStr = '{m}-{d} {h}:{i}:{s}', startStr = '', optionsIndex = null } = {},)
 * getType(type)
 * sleep(delay = 0, fn?: () => void)
 * validate(type = 'required', rules = {}, pureValid = false)
 * asyncWrapper(func, ...args)
 * formatImg(photoName, addPath = '', { basePath = 'assets/images' } = {})
 * copy = (text, toastParams = {})
 * formatThousands(number)
 * log(variableStr, variable, otherInfo = '')
 * random(min = 0, max = 10)
 * toLine(text, connect = '-')
 * processWidth(initValue, isBase = false)
 * formatBytes(bytes)
 * formatBytesConvert(bytes)
 * throttle(fn, delay = 1000)
 * debounce(fn, delay = 1000)
 * confirm(message, options)
 * formatNewLines(str)
 * getVariable('--green')
 */
export const isString = (val: any): val is string => typeof val === 'string'
export const isStringNumber = (val: string): boolean => {
  if (!isString(val)) {
    return false
  }
  return !Number.isNaN(Number(val))
}

export const isNumber = (val: any): val is number => typeof val === 'number'

/**
 * @example1
  proxy.$toast('保存成功') // s:success; i: info; w: warning; e: error;  
  proxy.$toast('保存失败', 'e')
  proxy.$toast('永不关闭', {duration: 0})
  proxy.$toast({
    message: 'andy',
    type: 'warning',
    duration: 300,
    closeAll: true,
  })
* $toast.success('This is a success message')
* @example2 显示对象 
* $toast({
    dangerouslyUseHTMLString: true,
    message: `<h6>复制成功</h6><pre>${JSON.stringify(obj, null, 2)}</pre>`,
    type: 'success',
    duration: 5000,
  })
*/
type MessageType = 'success' | 'info' | 'error' | 'warning'
type ShortType = 's' | 'i' | 'e' | 'w'
type ToastType = MessageType | ShortType
type ToastOptions = Partial<MessageOptions> & { closeAll?: boolean }

export function $toast(
  message: string | ToastOptions,
  type: ToastType | ToastOptions = 'success',
  otherParams: ToastOptions = {},
): void {
  const typeMap: Record<ShortType, MessageType> = {
    s: 'success',
    i: 'info',
    e: 'error',
    w: 'warning',
  }

  function isShortType(t: any): t is ShortType {
    return ['s', 'i', 'e', 'w'].includes(t)
  }

  function isToastOptions(obj: any): obj is ToastOptions {
    return typeof obj === 'object' && obj !== null
  }

  // Case 1: message is options object
  if (isToastOptions(message)) {
    if (message.closeAll) {
      ElMessage.closeAll()
    }
    ElMessage(message)
    return
  }

  // Case 2: type is options object
  if (isToastOptions(type)) {
    if (type.closeAll) {
      ElMessage.closeAll()
    }
    ElMessage({
      message,
      type: 'success',
      ...type,
    })
    return
  }

  // Case 3: regular message with type and options
  if (otherParams.closeAll) {
    ElMessage.closeAll()
  }

  const resolvedType = isShortType(type) ? typeMap[type] : type

  ElMessage({
    message,
    type: resolvedType,
    ...otherParams,
  })
}

// Add shorthand methods for each type of message
$toast.success = (message, otherParams = {}) => $toast(message, 'success', otherParams)
$toast.info = (message, otherParams = {}) => $toast(message, 'info', otherParams)
$toast.error = (message, otherParams = {}) => $toast(message, 'error', otherParams)
$toast.warning = (message, otherParams = {}) => $toast(message, 'warning', otherParams)

export function setStorage(storageName: string, params: any, isSession = false) {
  let handleParams
  if (typeof params === 'number' || typeof params === 'string') {
    handleParams = params
  } else {
    handleParams = JSON.stringify(params)
  }
  if (isSession) {
    sessionStorage.setItem(storageName, handleParams)
  } else {
    localStorage.setItem(storageName, handleParams)
  }
}

export function getStorage(data, isSession = false) {
  // 先获取localStorage数据, 如果没有再获取sessionStorage数据。 如果都没有, null;
  let getLocalData = ''
  let getSessionData = ''
  // 如果isSessionFirst为true, 先判断sessionStorage, 后判断localStorage
  if (isSession) {
    getSessionData = sessionStorage.getItem(data)
  } else {
    getLocalData = localStorage.getItem(data)
  }
  if (getLocalData) {
    try {
      if (typeof JSON.parse(getLocalData) !== 'number') {
        getLocalData = JSON.parse(getLocalData)
      }
    } catch (e) {}
    return getLocalData
  } else if (getSessionData) {
    try {
      if (typeof JSON.parse(getSessionData) !== 'number') {
        getSessionData = JSON.parse(getSessionData)
      }
    } catch (e) {}
    return getSessionData
  }
  return null
}

/**
 *
 * @param {*} str 需要清空的localStorage或sessionStorage, 如果不传清空所有
 * @param {*} param1 需要排除的sessionStorage或者localStorage
 * @example
 * clearStorage()
 * clearStorage('loginId')
 * clearStorage(['loginId', 'token'])
 * clearStorage({ exclude: ['loginId', 'token'] })
 */
export function clearStorage(str: string | [] | object = '') {
  if (isEmpty(str)) {
    sessionStorage.clear()
    localStorage.clear()
  }
  if (notEmpty(str) && getType(str) !== 'object') {
    let strArr = Array.isArray(str) ? str : [str]
    for (let i = 0; i < strArr.length; i++) {
      sessionStorage.removeItem(strArr[i])
      localStorage.removeItem(strArr[i])
    }
  }
  if (_isObjectWithExclude(str)) {
    if (notEmpty(str.exclude) && getType(str) === 'object') {
      let sessionStorageObj = {}
      let localStorageObj = {}
      for (const key in str.exclude) {
        if (Object.prototype.hasOwnProperty.call(str.exclude, key)) {
          const name = str.exclude[key]
          if (getStorage(name)) {
            localStorageObj[name] = getStorage(name)
          }
          if (getStorage(name, true)) {
            sessionStorageObj[name] = getStorage(name, true)
          }
        }
      }
      sessionStorage.clear()
      localStorage.clear()
      for (const key in sessionStorageObj) {
        setStorage(key, sessionStorageObj[key], true)
      }
      for (const key in localStorageObj) {
        setStorage(key, localStorageObj[key])
      }
    }
  }
}
// 自定义类型守卫函数
function _isObjectWithExclude(obj: object | string | []): obj is { exclude: { [key: string]: string } } {
  return typeof obj === 'object' && obj !== null && 'exclude' in obj && typeof obj.exclude === 'object'
}

/**
 * element-plus的form表单使用promise进行封装
 * @param ref
 * @param param1
 * @returns Promise
 * await proxy.validForm(formRef);
 * await proxy.validForm(formRef, {message: '自定义错误'});
 * await proxy.validForm(formRef, {showMessage: false});
 * await proxy.validForm(formRef, {detail: true});
 */

export function validForm(ref, { message = '表单校验错误, 请检查', detail = false, showMessage = true } = {}) {
  return new Promise((resolve, reject) => {
    unref(ref).validate((valid, status) => {
      if (valid) {
        resolve(status)
      } else {
        if (message && showMessage) {
          let errorText = Object.keys(status)
          let toastMessage = message
          if (detail) {
            toastMessage = message + errorText.join(',')
          }
          $toast(toastMessage, 'e')
        }
        reject(status)
      }
    })
  })
}

/**
 * 判断变量是否空值
 * undefined, null, '', '   ', false, 0, [], {}, NaN, new Set(), new Map(), BigInt(0), new Date('无效日期') 均返回true,否则返回false
 */
export function isEmpty(data: any, strict = false): boolean {
  if (isRef(data)) {
    data = unref(data)
  }

  // 处理严格模式(strict=true 时,0/false 不算空)
  if (strict) {
    if (data === false || data === 0 || data === BigInt(0)) {
      return false
    }
  }

  // 处理 null/undefined
  if (data == null) return true
  // 如果是日期对象,检查它是否是有效的日期
  if (data instanceof Date) {
    return isNaN(data.getTime())
  }
  // 处理基础类型
  switch (typeof data) {
    case 'string':
      return data.trim().length === 0
    case 'boolean':
      return !data
    case 'number':
      return 0 === data || isNaN(data) // ❗ `NaN`或者0 被认为是空
    case 'symbol':
      return false
    case 'bigint':
      return data === BigInt(0)
  }
  // 处理集合类型
  if (data instanceof Map || data instanceof Set) return data.size === 0
  // 处理数组/类数组
  if (
    Array.isArray(data) ||
    (typeof data.length === 'number' && Object.prototype.toString.call(data) === '[object Object]')
  ) {
    return data.length === 0
  }

  // 处理普通对象
  if (typeof data === 'object') {
    return Object.keys(data).length === 0
  }

  return false
}
// 非空
export function notEmpty(v: any): boolean {
  return !isEmpty(v)
}

/**
 *  将两个对象合并, 以第二个对象为准, 如果两个对象, 一个属性有值, 一个没值, 合并后有值; 如果两个属性都有值, 以第二个属性为准
 *  */
export function merge(obj1: object, obj2: object): object {
  let merged = { ...obj1, ...obj2 }
  for (let key in merged) {
    if (!isEmpty(obj1[key]) && !isEmpty(obj2[key])) {
      merged[key] = obj2[key]
    } else if (isEmpty(obj1[key]) && !isEmpty(obj2[key])) {
      merged[key] = obj2[key]
    } else if (!isEmpty(obj1[key]) && isEmpty(obj2[key])) {
      merged[key] = obj1[key]
    }
  }
  return merged
}

/**
 * 克隆数据并根据需要复制数组
 * @param {any} data - 要克隆的数据
 * @param {number} [times=1] - 如果是数组,要复制的次数
 * @returns {any} - 克隆后的数据或复制后的数组
 * clone(123) => 123
 * clone([1,2, {name: 'andy'}], 2) => [1, 2, {name: 'andy'}, 1, 2, {name: 'andy'}]
 */
export function clone(data, times = 1) {
  if (isRef(data)) {
    data = unref(data)
  }
  // Check if the data is not an array
  if (getType(data) !== 'array') {
    // If not an array, return a deep clone of the data
    return cloneDeep(data)
  }
  const clonedData = cloneDeep(data)
  const result: typeof clonedData = []
  for (let i = 0; i < times; i++) {
    result.push(...clonedData)
  }
  return result
}

/**
 * 格式化时间为年月日时分秒的格式, 格式可以自定义。
 * ① 时间戳10位和13位都可以转换成格式化的日期
 * ② java8格式的日期和有效的日期都可以转换成定义的日期格式
 * @param {Date, string}  都有默认参数
 * @example
 * formatTime() // 2020-07-17 09:53:07
 * formatTime('2018-02-13T06:17') // 2018-02-13 06:17:00
 * formatTime('2020/03/02 06:02') // 2020-03-02 06:02:00
 * formatTime(1541927611000); //2018-11-11 17:13:21
 * formatTime(1541927611000, "{y}年{m}月{d}日 {h}时{m}分{s}秒"); // 2018年11月11日 17时11分31秒
 * formatTime(1541927611, "{y}/{m}/{d} {h}:{m}:{s}"); // 2018/11/11 17:11:31
 * formatTime(new Date()); // 2018-11-11 17:13:21
 * formatTime(new Date().getTime()); // 2018-11-11 17:13:21
 * formatTime('1764128798.456'); // 2025/11/26 11:11:38
 */
export function formatTime(time, cFormat = '{y}-{m}-{d} {h}:{i}:{s}') {
  if (!time) {
    return time
  }
  let date
  if (typeof time === 'object') {
    date = time
  } else {
    const timeStr = '' + time
    // 处理带小数的时间戳格式,如 1764128798.456
    if (timeStr.includes('.') && !isNaN(parseFloat(timeStr))) {
      time = parseFloat(time) * 1000
    } else if (timeStr.length === 10) {
      time = parseInt(time) * 1000
    }
    date = new Date(time)
  }
  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 = cFormat.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
    let value = formatObj[key] // Note: getDay() returns 0 on Sunday
    if (key === 'a') {
      return ['日', '一', '二', '三', '四', '五', '六'][value]
    }
    if (result.length > 0 && value < 10) {
      value = '0' + value
    }
    return value || 0
  })
  return time_str
}

/**
 *
 * @param timestamp 持续的时间戳
 * @param cFormat 格式化的规则
 * @returns 天时分秒的字符串
 * @example
 * formatDurationTime(1162821) => 19分24秒
 */
export function formatDurationTime(timestamp, cFormat = '{d}天{h}时{i}分{s}秒') {
  const secondsPerMinute = 60
  const minutesPerHour = 60
  const hoursPerDay = 24
  let totalSeconds = Math.floor(timestamp / 1000)
  let days = 0
  // 只有返回天了, 才计算时间有多少天
  if (cFormat.indexOf('d') !== -1) {
    days = Math.floor(totalSeconds / (secondsPerMinute * minutesPerHour * hoursPerDay))
    totalSeconds %= secondsPerMinute * minutesPerHour * hoursPerDay
  }
  // 计算总秒数
  let hours = Math.floor(totalSeconds / (secondsPerMinute * minutesPerHour))
  totalSeconds %= secondsPerMinute * minutesPerHour
  let minutes = Math.floor(totalSeconds / secondsPerMinute)
  let seconds = totalSeconds % secondsPerMinute
  const formatObj = {
    d: days,
    h: hours,
    i: minutes,
    s: seconds,
  }
  let parseFormat = cFormat
  if (days === 0) {
    parseFormat = cFormat.match(/{h}.*/g)[0]
    if (hours === 0) {
      parseFormat = cFormat.match(/{i}.*/g)[0]
      if (minutes === 0) {
        parseFormat = cFormat.match(/{s}.*/g)[0]
      }
    }
  }
  const time_str = parseFormat.replace(/{(y|m|d|h|i|s)+}/g, (result, key) => {
    let value = formatObj[key] // Note: getDay() returns 0 on Sunday
    if (result.length > 0 && value < 10 && value != 0) {
      value = '0' + value
    }
    return value || '00'
  })
  return time_str
}

/**
 * 生成 UUID
 * @param type - 生成 UUID 的类型,可以是 'phone', 'email', 'time', 'number', 'ip', 'port' 或空字符串
 * @param length - 生成字符串的长度(默认为4)
 * @param options - 额外的选项
 * @param options.emailStr - 生成 email 时使用的后缀(默认为 '@qq.com')
 * @param options.timeStr - 生成时间字符串的格式(默认为 '{m}-{d} {h}:{i}:{s}')
 * @param options.startStr - 起始字符串(默认为空)
 * @param options.optionsIndex - 数组索引(默认为随机)
 * @returns 生成的 UUID (字符串或数字)
 * * uuid("名字") => 名字hc8f
 * uuid() => abcd
 * uuid('time') => 25MR 10-27 17:34:01
 * uuid('time', 0, {startStr:'andy', timeStr:"{h}:{i}:{s}"}) => andy 17:38:23
 * uuid('phone') => 13603312460
 * uuid('email') => cBZA@qq.com
 * uuid('number') => 2319
 * uuid([ { label: "小泽泽", value: "xzz" },{ label: "小月月", value: "xyy" }]) => xzz
 */
export function uuid(
  type: string | Array<{ label: string; value: any }> = '',
  length = 4,
  options: {
    emailStr?: string
    timeStr?: string
    startStr?: string
    optionsIndex?: number | null
  } = {},
): string | number {
  const { emailStr = '@qq.com', timeStr = '{y}-{m}-{d} {h}:{i}:{s}', startStr = '', optionsIndex = null } = options

  // 辅助函数:判断是否为ref对象
  function isRef(obj: any): obj is Ref<any> {
    return obj && typeof obj === 'object' && obj._isRef === true
  }

  // 辅助函数:获取ref的实际值
  function unref<T>(ref: Ref<T> | T): T {
    return isRef(ref) ? ref.value : ref
  }

  // 辅助函数:生成随机数
  function random(min: number, max: number): number {
    return Math.floor(Math.random() * (max - min + 1) + min)
  }

  // 解包可能为ref的参数
  type = unref(type)

  // 处理数组类型参数(下拉框选项)
  if (Array.isArray(type)) {
    if (type.length === 0) return ''

    const randIndex = optionsIndex ?? random(0, type.length - 1)
    const selectedItem = type[randIndex]

    // 如果数组项是对象且有value属性,则返回value
    if (typeof selectedItem === 'object' && selectedItem !== null && 'value' in selectedItem) {
      return selectedItem.value
    }

    // 否则直接返回数组项
    return selectedItem
  }

  // 生成随机字符串
  let randomChars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
  let result = startStr

  // 生成手机号
  if (type === 'phone') {
    const prefixes = ['130', '131', '132', '133', '135', '136', '137', '138', '170', '187', '189']
    result = prefixes[random(0, prefixes.length - 1)]

    for (let i = 0; i < 8; i++) {
      result += Math.floor(Math.random() * 10)
    }
    return result
  }

  // 生成邮箱
  if (type === 'email') {
    result = uuid(startStr, length) + emailStr
    return result
  }

  // 生成时间
  if (type === 'time') {
    return uuid(startStr, length, options) + ' ' + formatTime(new Date(), timeStr)
  }

  // 生成数字
  if (type === 'number') {
    const numChars = '123456789'
    result = ''

    for (let i = 0; i < length; i++) {
      result += numChars[random(0, numChars.length - 1)]
    }
    return Number(result)
  }

  // 生成IP地址
  if (type === 'ip') {
    const randomNum = random(1, 99)
    return `10.0.11.${randomNum}`
  }

  // 生成端口号
  if (type === 'port') {
    return random(1, 65535)
  }

  // 生成普通随机字符串
  for (let i = 0; i < length; i++) {
    result += randomChars[random(0, randomChars.length - 1)]
  }

  return result
}

// 声明全局的Ref接口
declare interface Ref<T = any> {
  _isRef: boolean
  value: T
}

/**
 * 判断传入参数的类型
 * @param {*} type
 * getType(new RegExp()) regexp
 * getType(new Date()) date
 * getType([]) array
 * getType({}) object
 * getType(null) null
 * getType(123) number
 */
export function getType(type) {
  if (typeof type === 'object') {
    const objType = Object.prototype.toString.call(type).slice(8, -1).toLowerCase()
    return objType
  } else {
    return typeof type
  }
}

/**
 * 一个辅助函数,用于在代码中创建一个暂停(延迟)。
 * 它返回一个 Promise,你可以在 `await` 后使用它来实现类似 "sleep" 的效果。
 *
 * @param delay - 等待的毫秒数。默认值为 0,表示不延迟。
 * @param fn - (可选) 一个在延迟结束后立即执行的函数。
 *
 * @returns 一个 Promise,当延迟结束后解析(resolve)。
 *
 * @example
 * // 基本用法:延迟 2 秒后打印消息
 * console.log('开始');
 * await sleep(2000);
 * console.log('2秒后执行');
 *
 * @example
 * // 带回调函数的用法:延迟 1 秒后执行清理工作
 * sleep(1000, () => {
 *   console.log('执行清理操作...');
 *   // 清理代码...
 * });
 *
 * @example
 * // 在循环中使用:每次迭代后延迟 500 毫秒
 * for (let i = 0; i < 5; i++) {
 *   console.log(`当前值: ${i}`);
 *   await sleep(500);
 * }
 */
export function sleep(delay = 0, fn?: () => void) {
  return new Promise<void>((resolve) =>
    setTimeout(() => {
      fn?.()
      resolve()
    }, delay),
  )
}

/** @使用方式 
 * 1. 在el-form中使用
name: [ proxy.validate('name', { message: '你干嘛哈哈' })],
between: [ proxy.validate('between', { max: 99 })],
number: [ proxy.validate('number')],
length: [proxy.validate('length')],
mobile: [ proxy.validate('mobile')],
ip: [ proxy.validate('ip')],
custom: [proxy.validate('custom', { message: '最多保留2位小数', reg: /^\d+\.?\d{0,2}$/ })]
confirmRegPwd: [
  proxy.validate('same', { value: form.value.regPwd }),
], //1. 如果判断两个密码一致, 还要在input输入值改变的时候, 再校验一下两个input
  formRef.value.validate('regPwd')
  formRef.value.validate('confirmRegPwd')
  // 2. rules需要使用computed包裹, 否则值改变无法传递
 * 2. 在函数中使用, 返回boolean
 let ip = proxy.validate('ip', 122322, true)
 let custom = proxy.validate('custom', { value: -123, reg: /^-\d+\.?\d{0,2}$/ }, true)
*/
export function validate(type = 'required', rules = {}, pureValid = false) {
  // 如果第一个参数是对象, 则相当于重载
  if (getType(type) === 'object') {
    pureValid = rules || false
    rules = type
    type = 'required'
  }
  // let trigger = rules.trigger || ['blur', 'change']
  let trigger = rules.trigger || []
  const typeMaps = ['required', 'pwd', 'number', 'mobile', 'between', 'length', 'same', 'ip', 'port', 'custom']
  let parseRequired = rules.required ?? true

  // 如果不包含typeMaps中的类型, 直接将第一个参数作为message
  if (!typeMaps.includes(type)) {
    return {
      required: parseRequired,
      message: type,
      trigger: trigger,
    }
  }
  if (type === 'required') {
    return {
      required: parseRequired,
      message: rules.message ?? '请输入',
      trigger: trigger,
    }
  }

  // validator: this.validateName,
  if (type === 'password') {
    const validateName = (rule, value, callback) => {
      let validFlag = /^[a-zA-Z0-9_-]+$/.test(value)
      if (!validFlag) {
        callback(new Error(rules.message || '密码只能由英文、数字、下划线、中划线组成'))
      } else {
        callback()
      }
    }
    return {
      validator: validateName,
      trigger: trigger,
    }
  }
  if (type === 'positive' || type === 'number') {
    // 正整数
    return _validValue(rules, '请输入正整数', pureValid, /^[1-9]+\d*$/)
  }
  if (type === 'zeroPositive') {
    // 正整数且包含0
    return _validValue(rules, '请输入非负整数', pureValid, /^(0|[1-9]+\d*)$/)
  }
  // 整数, 包含负数和0
  if (type === 'integer') {
    return _validValue(rules, '请输入整数', pureValid, /^(0|[-]?[1-9]\d*)$/)
  }
  // 非负数, 整数和最多2位小数
  if (type === 'decimal') {
    return _validValue(rules, '请输入非负数字, 包含小数且最多2位', pureValid, /(0|[1-9]\d*)(\.\d{1, 2})?|0\.\d{1,2}/)
  }
  if (type === 'mobile') {
    return _validValue(rules, '请输入正确的手机号', pureValid, /^[1][0-9]{10}$/)
  }
  if (type === 'ip') {
    return _validValue(
      rules,
      '请输入正确的ip地址',
      pureValid,
      /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
    )
  }
  if (type === 'between') {
    let min = rules.min || 1
    let max = rules.max || 10
    const validateBetween = (rule, value, callback) => {
      let validFlag = /^[0-9]+$/.test(value)
      if (!validFlag) {
        callback(new Error('请输入数字'))
      }
      if (value < min) {
        callback(new Error(`数字不能小于${min}`))
      }
      if (value > max) {
        callback(new Error(`数字不能大于${max}`))
      }
      callback()
    }
    return {
      validator: validateBetween,
      trigger: trigger,
      required: parseRequired,
    }
  }
  if (type === 'length') {
    return {
      min: rules.min,
      max: rules.max,
      message: rules.message ?? `请输入${rules.min}到${rules.max}个字符`,
      trigger: trigger,
      required: parseRequired,
    }
  }
  if (type === 'port') {
    return _validValue(
      rules,
      '请输入1-65535的端口号',
      pureValid,
      /^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-5][0-5][0-3][0-5])$/,
    )
  }

  if (type === 'same') {
    const validateSame = (rule, value, callback) => {
      let isSame = value === rules.value
      if (!isSame) {
        const errMessage = rules.message || '密码和确认密码要一致'
        callback(new Error(errMessage))
      }
      if (parseRequired && !value) {
        callback(new Error(rules.message || '请输入'))
      }
      callback()
    }
    let res = {
      validator: validateSame,
      trigger: trigger,
      required: parseRequired,
    }
    return res
  }
  if (type === 'custom') {
    //  _validValue(rules, '请输入正确的手机号', pureValid, /^[1][0-9]{10}$/)
    if (pureValid) {
      return _validValue(rules.value, rules.message, pureValid, rules.reg)
    } else {
      return _validValue(rules, rules.message, pureValid, rules.reg)
    }
  }

  function _validValue(rules, msg, pureValid, reg) {
    if (pureValid === true) {
      return reg.test(rules)
    }
    const validatePhone = (rule, value, callback) => {
      let validFlag = reg.test(value)
      if (!validFlag) {
        callback(new Error(rules.message ?? msg))
      } else {
        callback()
      }
    }
    return {
      validator: validatePhone,
      required: rules.required ?? true,
      trigger: trigger,
    }
  }
}

/**
 * 
const { res, err } = await proxy.asyncWrapper(listTests, pickForm);
if (err) {
	return;
}
 */
export async function asyncWrapper(func, ...args) {
  try {
    const res = await func(...args)
    return { res }
  } catch (err) {
    return { err }
  }
}

/** 获取assets静态资源
 * @example
 *  proxy.formatImg('1.png')
 *  proxy.formatImg('1.png', 'menu')
 *  */
export function formatImg(photoName, addPath = '', { basePath = 'assets/images' } = {}) {
  if (photoName.startsWith('http') || photoName.startsWith('https')) {
    return photoName
  }
  if (photoName.indexOf('.') === -1) {
    photoName = photoName + '.png'
  }
  const addLastSlash = addPath.endsWith('/') || !addPath ? addPath : `${addPath}/`
  const addLastBasePathSlash = basePath.endsWith('/') || !basePath ? basePath : `${basePath}/`
  let mergeSrc = `${addLastSlash}${photoName}`
  // '../assets/images/1.png'
  let res = new URL(`../${addLastBasePathSlash}${mergeSrc}`, import.meta.url).href
  return res
}

/**
 * 复制文本
 *
 * copy('这是要复制的文本');
 * copy('这是要复制的文本', {duration: 500});
 *
 *  */
export const copy = (text, toastParams = {}) => {
  const textarea = document.createElement('textarea')
  textarea.value = text
  textarea.style.position = 'fixed'
  document.body.appendChild(textarea)
  textarea.select()
  document.execCommand('copy')
  document.body.removeChild(textarea)
  if (!toastParams.hideToast) {
    $toast(text + '复制成功', toastParams)
  }
}

/**
 * 1234 => 1,234
 * 1234b => 1,234b
 * 1234.12b => 1,234.12b
 * @param number 加千分位
 * @returns
 */
export function formatThousands(number) {
  // 提取数字部分、小数点和小数部分
  let matches = ('' + number).match(/^([\d,]+)(\.?)(\d+)?(\D+)?$/)
  if (!matches) {
    return number // 如果没有找到匹配,则返回原始输入
  }

  let numericString = matches[1].replace(/\D/g, '') // 仅保留数字
  let decimalString = matches[3] ? `.${matches[3]}` : '' // 小数部分,如果没有则为空字符串
  let unit = matches[4] || '' // 单位部分,如果没有则为空字符串

  // 添加千位分隔符
  let numberWithSeparator = numericString.replace(/\B(?=(\d{3})+(?!\d))/g, ',')

  // 拼接数字、小数点、小数部分和单位,并返回结果
  return `${numberWithSeparator}${decimalString}${unit}`
}
/*
*
* @example
const str = ref(11)
proxy.log(`variableStr`, variableStr, "/cyrd/oeos-components/packages/utils/index.ts");
 */
export function log(variableStr, variable, otherInfo = '') {
  const stack = new Error().stack.split('\n')[2].trim() // 获取调用堆栈的第二行
  const matchResult = stack.match(/\((.*):(\d+):(\d+)\)/)
  let fileInfo = ''
  try {
    if (matchResult && otherInfo) {
      const lineNumber = matchResult[2]
      fileInfo = `vscode://file${JSON.parse(otherInfo)}:${lineNumber}`
    }
  } catch (error) {
    fileInfo = otherInfo
  }

  if (isRef(variable)) {
    let unrefVariable = unref(variable)
    _log(toRaw(unrefVariable))
  } else {
    _log(variable)
  }
  function _log(consoleData) {
    if (getType(consoleData) === 'object' || getType(consoleData) === 'array') {
      consola.log(
        `%c${variableStr} `,
        'background:#fff; color: blue;font-size: 0.8em',
        JSON.stringify(consoleData, null, '\t'),
        `${fileInfo}`,
      )
    } else {
      consola.log(`%c${variableStr} `, 'background:#fff; color: blue;font-size: 0.8em', consoleData, `${fileInfo}`)
    }
  }
  function getType(type) {
    if (typeof type === 'object') {
      const objType = Object.prototype.toString.call(type).slice(8, -1).toLowerCase()
      return objType
    } else {
      return typeof type
    }
  }
}

/**
 * 生成指定范围内的随机整数
 *
 * @param min 最小值,默认为0
 * @param max 最大值,默认为10
 * @returns 返回生成的随机整数
 */
export function random(min = 0, max = 10) {
  return Math.floor(Math.random() * (max - min + 1)) + min
}

/**
 * 将文本转换为带有连接符的行文本
 *
 * @param text 要转换的文本
 * @param connect 连接符,默认为'-'
 * @returns 返回转换后的行文本
 * toLine('NameAndy') // name-andy
 * toLine('nameAndy') // name-andy
 * toLine('_nameAndy') // _name-andy
 */
export function toLine(text, connect = '-') {
  let translateText = text
    .replace(/([A-Z])/g, (match, p1, offset, origin) => {
      if (offset === 0) {
        return `${match.toLocaleLowerCase()}`
      } else {
        return `${connect}${match.toLocaleLowerCase()}`
      }
    })
    .toLocaleLowerCase()
  return translateText
}

// processWidth(200) // { width: '200px' }
// processWidth('200', true) // 200px
// processWidth('200.33px') // { width: '200.33px' }
// processWidth('') // {}
export function processWidth(initValue, isBase = false) {
  let value = unref(initValue)
  let res = ''
  if (!value) {
    return isBase ? value : {}
  } else if (typeof value === 'number') {
    value = String(value)
  }
  if (value === '') {
    return isBase ? value : {}
  } else if (typeof value === 'string' && !isNaN(value)) {
    res = value + 'px'
  } else if (typeof value === 'string' && /^[0-9]+(\.[0-9]+)?(px|%|em|rem|vw|vh|ch)*$/.test(value)) {
    res = value
  } else {
    console.warn(`${value} is Invalid unit provided`)
    return value
  }
  if (isBase) {
    return res
  }
  return { width: res }
}

/**
 * 增加小数点
 * toFixed(22) -> '22.00'
 * toFixed('22') -> '22.00'
 * toFixed('22', 4) -> '22.0000'
 * toFixed('22', 2) -> 22
 * toFixed('22 TB', {prefix: '$', suffix: '%', unit: false}) -> $22.00%
 */
export function toFixed(
  value: any,
  options: { digit?: number; prefix?: string; suffix?: string; unit?: boolean } | number = {},
) {
  // 如果第二个参数是数字,则将其视为 digit
  if (typeof options === 'number') {
    options = { digit: options }
  }

  // 默认为 digit=2, prefix='', suffix=''
  let { digit = 2, prefix = '', suffix = '', unit = true } = options
  // 提取数字部分、小数点和小数部分
  let matches = ('' + value).match(/^([\d,]+)(\.?)(\d+)?(\D+)?$/)
  if (!matches) {
    return value // 如果没有找到匹配,则返回原始输入
  }

  let numericString = matches[1].replace(/\D/g, '') // 仅保留数字
  let decimalString = matches[3] ? `.${matches[3]}` : '' // 小数部分,如果没有则为空字符串
  let finalUnit = matches[4] || '' // 单位部分,如果没有则为空字符串

  let res = numericString
  if (isStringNumber(numericString) || isNumber(numericString)) {
    res = Number(numericString + decimalString).toFixed(digit)
  }
  if (!unit) {
    finalUnit = ''
  }
  return `${prefix}${res}${finalUnit}${suffix}`
}
/**
 * 格式化字节单位
 * @param bytes - 字节数
 * @param options - 配置项
 * @param options.digit - 小数位数(默认2)
 * @param options.thousands - 是否千分位分隔(默认true)
 * @param options.prefix - 前缀(默认空)
 * @param options.suffix - 后缀(默认空)
 * @param options.roundType - 取整方式:'floor'(向下, 默认) | 'ceil'(向上) | 'round'(四舍五入)
 */
export function formatBytes(
  bytes: number | string,
  options: {
    digit?: number
    thousands?: boolean
    prefix?: string
    suffix?: string
    roundType?: 'floor' | 'ceil' | 'round'
  } = {},
) {
  let { digit = 2, thousands = true, prefix = '', suffix = '', roundType = 'floor' } = options
  // 校验输入
  if (isStringNumber(bytes as any) || isNumber(bytes)) {
    bytes = Number(bytes)
  } else {
    return bytes
  }
  if (bytes <= 1) {
    return Math[roundType](bytes * Math.pow(10, digit)) / Math.pow(10, digit) + ' B'
  }

  const k = 1024
  const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
  const i = Math.floor(Math.log(bytes) / Math.log(k))

  // 根据 roundType 选择取整方式
  const power = Math.pow(k, i)
  let num = bytes / power

  num = Math[roundType](num * Math.pow(10, digit)) / Math.pow(10, digit)

  let res = num.toFixed(digit) + ' ' + sizes[i]

  if (thousands) {
    res = formatThousands(res)
  }

  return `${prefix}${res}${suffix}`
}

/**
 * 字节转数字
 * @param oBytes
 * @param param1
 * @returns number
 * formatBytesConvert('0.5GB') 536870912
 * formatBytesConvert('1,234 GB') 1324997410816
 * formatBytesConvert('1,234 GB', {thousand: true}) 1,324,997,410,816
 * formatBytesConvert('1,234 GB', {toFixed: 2}) 1324997410816.00
 */
export function formatBytesConvert(oBytes, { thounsands = false, digit = 0 } = {}) {
  let bytes = oBytes
  if (isStringNumber(oBytes) || isNumber(oBytes) || getType(oBytes) !== 'string') {
    return parseDigitThounsands(oBytes)
  }
  if (!oBytes) {
    return parseDigitThounsands(oBytes)
  }

  // 如果有千分位, 先将千分位的,去掉
  const regex = /^\d{1,3}(,\d{3})*(\.\d+)?[a-zA-Z ]*$/
  if (regex.test(oBytes)) {
    bytes = oBytes.replace(/,/g, '')
    if (isStringNumber(bytes) || isNumber(bytes) || getType(bytes) !== 'string') {
      return parseDigitThounsands(bytes)
    }
  }

  const bytesRegex = /^(\d+(?:\.\d+)?)\s*([BKMGTPEZY]?B|Byte)$/i
  const units = {
    B: 1,
    BYTE: 1,
    KB: 1024,
    MB: 1024 ** 2,
    GB: 1024 ** 3,
    TB: 1024 ** 4,
    PB: 1024 ** 5,
    EB: 1024 ** 6,
    ZB: 1024 ** 7,
    YB: 1024 ** 8,
  }

  const match = bytes.match(bytesRegex)
  if (!match) {
    console.warn("Invalid bytes format. Please provide a valid bytes string, like '100GB'.")
    return
  }

  const size = parseFloat(match[1])
  const unit = match[2].toUpperCase()

  if (!units.hasOwnProperty(unit)) {
    console.warn(
      "Invalid bytes unit. Please provide a valid unit, like 'B', 'BYTE', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', or 'YB'.",
    )
    return
  }
  function parseDigitThounsands(val) {
    let finalRes = val
    if (digit) {
      finalRes = Number(finalRes).toFixed(digit)
    }
    if (thounsands) {
      finalRes = formatThousands(finalRes)
    }
    return finalRes
  }
  return parseDigitThounsands(size * units[unit])
}

export function throttle(fn, delay = 1000) {
  // last为上一次触发毁掉的时间,timer是定时器
  let last = 0
  let timer = null
  // 将throttle处理结果当做函数返回
  return function () {
    // 保留调用时的this上下文
    let context = this
    // 保留调用时传入的参数
    let args = arguments
    // 记录本次触发回调的时间
    let now = +new Date()
    // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
    if (now - last < delay) {
      // 如果时间间隔小于设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
      clearTimeout(timer)
      timer = setTimeout(function () {
        last = now
        fn.apply(context, args)
      }, delay)
    } else {
      // 如果时间间隔超出了设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
      last = now
      fn.apply(context, args)
    }
  }
}

/**
 * 封装 Promise 执行,提供自动 loading 状态管理
 * @param promise 需要执行的 Promise
 * @param sendLoading 可选的 loading 状态(支持 Ref<boolean> 或 boolean)
 * @returns Promise<{ data: T | null; error: any }>
 * @example1
 * const loading = ref(false);
 * const { data, error } = await tryCatch(fetchUserData(), loading);
 * @example2 // 无视 loading 状态
 * const { data, error } = await tryCatch(fetchUserData());
 */
export function tryCatch<T>(
  promise: Promise<T>,
  sendLoading?: Ref<boolean> | null,
): Promise<{ data: T | null; error: any }> {
  const updateLoading = (value: boolean): void => {
    if (isRef(sendLoading)) {
      sendLoading.value = value
    } else if (sendLoading !== null) {
      console.warn('Cannot modify non-ref sendLoading directly!')
    }
  }

  // 初始化 loading 状态
  updateLoading(true)

  return promise
    .then((data: T) => {
      updateLoading(false)
      return { data, error: null }
    })
    .catch((error: any) => {
      updateLoading(false)
      return { data: null, error }
    })
}

export function debounce(fn, delay = 1000) {
  let timer = null
  return function () {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments)
      timer = null
    }, delay)
  }
}

/**
 * proxy.confirm('确定删除吗?')
 * proxy.confirm('哈哈', { icon: 'el-icon-plus' })
 * close-on-click-modal: 是否可通过点击遮罩层关闭 MessageBox 默认true
 * lock-scroll: 是否在 MessageBox 出现时将 body 滚动锁定. 默认true
 * 设置宽度, 内容使用组件
   import GWarning from '@/autoComponents/gWarning.vue'
    await proxy.confirm('', {
      dangerouslyUseHTMLString: true,
      customStyle: {
        maxWidth: '600px',
      },
      message: h(GWarning, {
        content:
          '对于光存储开启保持原始对象名称后,对象将作为独立文件在光存储介质直接存储。<br>注意:当桶内文件大小普遍较小(<100MB)或过大(>5GB)时不推荐打开此功能!您确定开启此功能吗?',
      }),
      showCancelButton: true,
      cancelButtonText: '取消',
      appendTo: '#highSettingsForm',
    })
 * 如果是多个dialog嵌套, 可以给上层的dom设置个id如highSettingsForm, 然后appendTo: '#highSettingsForm'
 */
export function confirm(message, options) {
  const resolvedMessage = typeof message === 'function' ? message() : message
  // 关键点:直接访问 Element Plus 内部维护的全局上下文
  const elContext =
    ElMessageBox.install?.context || ElMessageBox._context || document.querySelector('#app')?._vue_app?._context
  const mergeOptions = {
    title: '提示',
    draggable: true,
    showCancelButton: false,
    confirmButtonText: '确定',
    dangerouslyUseHTMLString: true, // 允许 HTML
    appContext: elContext, // 强制注入 Element Plus 的上下文
    ...options,
  }
  return ElMessageBox.confirm(resolvedMessage, mergeOptions)
}

/**
 * 格式化字符串中的换行符和制表符
 * @param str 待格式化的字符串
 * @returns 格式化后的字符串,如果输入的不是字符串或为空,则返回原字符串
 * @example
$toast(
  formatNewLines(
    'Example file\n  File : 111.jpeg\n  CreateTime : 1721011155921 2024-07-15 10:39:15\n  LastUpdateTime : 1721011155921 2024-07-15 10:39:15\n------------------------------------------------------------------------\nExtract:\n  aa=231\n------------------------------------------------------------------------\n',
  ),
  {
    duration: 5000,
    dangerouslyUseHTMLString: true,
  },
)
 */
export function formatNewLines(str) {
  // 如果 str 为空或者不是字符串类型,则直接返回 str
  if (!str || typeof str !== 'string') {
    return str
  }
  // 替换换行符 \n 为 <br> 标签
  str = str.replace(/\n/g, '<br>')
  // 替换制表符 \t 为四个空格或其他适当的字符
  str = str.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
  return str
}

/** Function to get a CSS custom property value
 *
 * getVariable('--blue');
 *  */
export function getVariable(propertyName) {
  let res = getComputedStyle(document.documentElement).getPropertyValue(propertyName).trim()
  return res
}