mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-11 01:01:09 +08:00
【初始化】前端工程项目
This commit is contained in:
585
frontend/packages/utils/src/browser.ts
Normal file
585
frontend/packages/utils/src/browser.ts
Normal file
@@ -0,0 +1,585 @@
|
||||
/**
|
||||
* 文件定义:浏览器相关操作
|
||||
*/
|
||||
|
||||
import * as R from 'ramda'
|
||||
|
||||
/* -------------- 1、浏览器相关操作 -------------- */
|
||||
|
||||
/**
|
||||
* 获取当前页面 URL
|
||||
*/
|
||||
export const isHttps = (): boolean => window.location.protocol === 'https:'
|
||||
|
||||
/**
|
||||
* 判断是否为开发环境
|
||||
*/
|
||||
export const isDev = (): boolean => process.env.NODE_ENV === 'development'
|
||||
|
||||
/**
|
||||
* 获取浏览器及操作系统信息
|
||||
* @returns {{ browser: string; os: string }} 浏览器和操作系统信息
|
||||
*/
|
||||
export const getBrowserOSInfo = (): { browser: string; os: string } => {
|
||||
const ua = navigator.userAgent
|
||||
|
||||
type Rule = [(str: string) => boolean, () => string]
|
||||
|
||||
// 浏览器识别规则
|
||||
const browserRules: Rule[] = [
|
||||
[R.allPass([R.test(/Chrome/), R.complement(R.test(/Edg/))]), R.always('Chrome')],
|
||||
[R.test(/Firefox/), R.always('Firefox')],
|
||||
[R.allPass([R.test(/Safari/), R.complement(R.test(/Chrome/))]), R.always('Safari')],
|
||||
[R.test(/Edg/), R.always('Edge')],
|
||||
[R.T, R.always('Unknown')],
|
||||
]
|
||||
|
||||
// 操作系统识别规则
|
||||
const osRules: Rule[] = [
|
||||
[R.test(/iPhone|iPad|iPod/), R.always('iOS')],
|
||||
[R.test(/Android/), R.always('Android')],
|
||||
[R.test(/Win/), R.always('Windows')],
|
||||
[R.allPass([R.test(/Mac/), R.complement(R.test(/iPhone|iPad|iPod/))]), R.always('macOS')],
|
||||
[R.test(/Linux/), R.always('Linux')],
|
||||
[R.T, R.always('Unknown')],
|
||||
]
|
||||
|
||||
return {
|
||||
browser: R.cond(browserRules)(ua),
|
||||
os: R.cond(osRules)(ua),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取屏幕信息,分辨率、缩放比例
|
||||
*/
|
||||
export const getScreenInfo = (): {
|
||||
resolution: string
|
||||
scale: number
|
||||
} => {
|
||||
const resolution = `${window.screen.width}x${window.screen.height}`
|
||||
const scale = window.devicePixelRatio
|
||||
return { resolution, scale }
|
||||
}
|
||||
|
||||
/* -------------- 2、浏览器缓存相关操作 -------------- */
|
||||
|
||||
/**
|
||||
* 强制刷新页面并清理所有缓存
|
||||
* 清除 Cache API、localStorage 和 sessionStorage 后刷新
|
||||
*/
|
||||
export const forceRefresh = () => {
|
||||
clearBrowserCache()
|
||||
// window.location.reload()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 URL 参数
|
||||
* @param name 参数名
|
||||
*/
|
||||
export const getUrlParam = (name: string): string | null => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
return params.get(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* 柯里化版本的getUrlParam
|
||||
*/
|
||||
export const getUrlParamCurried: {
|
||||
(name: string): string | null
|
||||
} = R.curry(getUrlParam)
|
||||
|
||||
/**
|
||||
* Cookie 操作辅助函数:根据 HTTPS 协议增加前缀
|
||||
* @param key cookie 键名
|
||||
*/
|
||||
export const cookiePrefixKey = (key: string): string =>
|
||||
R.ifElse(R.always(isHttps()), (k: string) => `https_${k}`, R.identity)(key)
|
||||
|
||||
/**
|
||||
* 设置 Cookie
|
||||
* @param {string} key 键名
|
||||
* @param {string} value 值
|
||||
* @param {number} days 过期天数(可选)
|
||||
*/
|
||||
export const setCookie = (key: string, value: string, days?: number): void => {
|
||||
const prefixedKey = cookiePrefixKey(key)
|
||||
// 获取过期时间
|
||||
const getExpires = (days?: number): string => {
|
||||
if (!days) return ''
|
||||
const date = new Date()
|
||||
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000)
|
||||
return `; expires=${date.toUTCString()}`
|
||||
}
|
||||
const expires = getExpires(days)
|
||||
document.cookie = `${prefixedKey}=${encodeURIComponent(value)}${expires}; path=/`
|
||||
}
|
||||
|
||||
/**
|
||||
* 柯里化版本的setCookie
|
||||
*/
|
||||
export const setCookieCurried: {
|
||||
(key: string, value: string, days?: number): void
|
||||
(key: string): (value: string, days?: number) => void
|
||||
(key: string, value: string): (days?: number) => void
|
||||
} = R.curry(setCookie)
|
||||
|
||||
/**
|
||||
* 获取 Cookie
|
||||
* @param key 键名
|
||||
*/
|
||||
export const getCookie = (key: string, isPrefixKey: boolean = true): string | null => {
|
||||
const prefixedKey = isPrefixKey ? cookiePrefixKey(key) : key
|
||||
const nameEQ = `${prefixedKey}=`
|
||||
const cookies = document.cookie.split(';').map((c) => c.trim())
|
||||
const cookie = cookies.find((c) => c.startsWith(nameEQ))
|
||||
if (cookie) {
|
||||
return decodeURIComponent(cookie.substring(nameEQ.length))
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 柯里化版本的getCookie
|
||||
*/
|
||||
export const getCookieCurried: {
|
||||
(key: string): string | null
|
||||
} = R.curry(getCookie)
|
||||
|
||||
/**
|
||||
* 删除 Cookie
|
||||
* @param key 键名
|
||||
*/
|
||||
export const deleteCookie = (key: string): void => {
|
||||
// 设置过期时间为负值,即删除 Cookie
|
||||
setCookie(key, '', -1)
|
||||
console.log(document.cookie)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空 Cookie
|
||||
*/
|
||||
export const clearCookie = (): void => {
|
||||
const cookies = document.cookie.split(';').map((c) => c.trim()) // 获取所有 Cookie
|
||||
cookies.forEach((c) => {
|
||||
const [key] = c.split('=')
|
||||
if (key) {
|
||||
// 通过设置过期时间为过去来删除cookie
|
||||
document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置存储增强(支持自动序列化)
|
||||
*/
|
||||
export const setStorageItem = (key: string, value: any, storage: Storage): void => {
|
||||
const serializedValue = JSON.stringify(value)
|
||||
storage.setItem(key, serializedValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* 柯里化版本的setStorageItem
|
||||
*/
|
||||
export const setStorageItemCurried: {
|
||||
(key: string, value: any, storage: Storage): void
|
||||
(key: string): (value: any, storage: Storage) => void
|
||||
(key: string, value: any): (storage: Storage) => void
|
||||
} = R.curry(setStorageItem)
|
||||
|
||||
/**
|
||||
* 获取存储增强(支持自动反序列化)
|
||||
*/
|
||||
export const getStorageItem = (key: string, storage: Storage): any => {
|
||||
const value = storage.getItem(key)
|
||||
return value ? JSON.parse(value) : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 柯里化版本的getStorageItem
|
||||
*/
|
||||
export const getStorageItemCurried: {
|
||||
(key: string, storage: Storage): any
|
||||
(key: string): (storage: Storage) => any
|
||||
} = R.curry(getStorageItem)
|
||||
|
||||
/**
|
||||
* 删除存储
|
||||
* @param key 键名
|
||||
* @param storage 存储类型(可选)
|
||||
*/
|
||||
export const removeStorageItem = (key: string, storage: Storage = localStorage): void => storage.removeItem(key)
|
||||
|
||||
/**
|
||||
* 设置 sessionStorage 数据
|
||||
* @param key 键名
|
||||
* @param value 值
|
||||
*/
|
||||
export const setSessionItem = (key: string, value: any): void => setStorageItem(key, value, sessionStorage)
|
||||
|
||||
/**
|
||||
* 获取 sessionStorage 数据
|
||||
* @param key 键名
|
||||
*/
|
||||
export const getSessionItem = (key: string): any => getStorageItem(key, sessionStorage)
|
||||
|
||||
/**
|
||||
* 删除 sessionStorage 数据
|
||||
* @param key 键名
|
||||
*/
|
||||
export const removeSessionItem = (key: string): void => sessionStorage.removeItem(key)
|
||||
|
||||
/**
|
||||
* 清空 sessionStorage 中所有数据
|
||||
*/
|
||||
export const clearSession = (): void => sessionStorage.clear()
|
||||
|
||||
/**
|
||||
* 设置 localStorage 数据
|
||||
* @param key 键名
|
||||
* @param value 值
|
||||
*/
|
||||
export const setLocalItem = (key: string, value: any): void => setStorageItem(key, value, localStorage)
|
||||
|
||||
/**
|
||||
* 获取 localStorage 数据
|
||||
* @param key 键名
|
||||
*/
|
||||
export const getLocalItem = (key: string): any => getStorageItem(key, localStorage)
|
||||
|
||||
/**
|
||||
* 删除 localStorage 数据
|
||||
* @param key 键名
|
||||
*/
|
||||
export const removeLocalItem = (key: string): void => localStorage.removeItem(key)
|
||||
|
||||
/**
|
||||
* 清空 localStorage 中所有数据
|
||||
*/
|
||||
export const clearLocal = (): void => localStorage.clear()
|
||||
|
||||
/**
|
||||
* 清空浏览器缓存
|
||||
*/
|
||||
export const clearBrowserCache = (): void => {
|
||||
clearSession()
|
||||
clearLocal()
|
||||
clearCookie()
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建过期时间的存储,支持sessionStorage和localStorage
|
||||
* @param key 键名
|
||||
* @param value 值
|
||||
* @param time 过期时间(可选),支持new Date()、时间戳、时间字符串
|
||||
* @param storage 存储类型(可选)
|
||||
*/
|
||||
export const setExpiredStorageItem = (
|
||||
key: string,
|
||||
value: any,
|
||||
time?: Date | number | string,
|
||||
storage: Storage = localStorage,
|
||||
): void => {
|
||||
// 如果没有设置过期时间,直接存储值
|
||||
if (!time) {
|
||||
setStorageItem(key, value, storage)
|
||||
return
|
||||
}
|
||||
|
||||
// 转换过期时间为时间戳
|
||||
let expires: number
|
||||
if (time instanceof Date) {
|
||||
expires = time.getTime()
|
||||
} else if (typeof time === 'number') {
|
||||
expires = time
|
||||
} else {
|
||||
expires = new Date(time).getTime()
|
||||
}
|
||||
|
||||
// 存储数据和过期时间
|
||||
const data = {
|
||||
value,
|
||||
expires,
|
||||
}
|
||||
|
||||
setStorageItem(key, data, storage)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取过期时间的存储
|
||||
* @param key 键名
|
||||
*/
|
||||
export const getExpiredStorageItem = (key: string, storage: Storage = localStorage): any => {
|
||||
const data = getStorageItem(key, storage)
|
||||
if (!data) return null
|
||||
// 检查是否过期
|
||||
if (data.expires && data.expires < Date.now()) {
|
||||
removeStorageItem(key, storage)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------- 3、IndexedDB 相关操作 -------------- */
|
||||
|
||||
/**
|
||||
* IndexedDB 相关类型定义
|
||||
* @interface IndexedDBConfig
|
||||
* @description 数据库配置接口,用于定义数据库的基本结构
|
||||
* @property {string} dbName - 数据库名称
|
||||
* @property {number} version - 数据库版本号,用于数据库升级
|
||||
* @property {Object} stores - 存储对象配置,key 为存储对象名称
|
||||
*/
|
||||
export interface IndexedDBConfig {
|
||||
dbName: string
|
||||
version: number
|
||||
stores: {
|
||||
[key: string]: {
|
||||
/** 主键路径,用于唯一标识记录 */
|
||||
keyPath: string
|
||||
/** 索引配置数组,用于优化查询性能 */
|
||||
indexes?: Array<{
|
||||
/** 索引名称 */
|
||||
name: string
|
||||
/** 索引的键路径 */
|
||||
keyPath: string
|
||||
/** 索引选项,如是否唯一等 */
|
||||
options?: IDBIndexParameters
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* IndexedDB 管理类
|
||||
* @class IndexedDBManager
|
||||
* @description 提供 IndexedDB 数据库操作的统一接口,支持异步操作和类型安全
|
||||
*/
|
||||
export class IndexedDBManager {
|
||||
/** 数据库连接实例 */
|
||||
private db: IDBDatabase | null = null
|
||||
/** 数据库配置信息 */
|
||||
private config: IndexedDBConfig
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param config 数据库配置对象
|
||||
*/
|
||||
constructor(config: IndexedDBConfig) {
|
||||
this.config = config
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一的事件处理器
|
||||
* @description 处理 IDBRequest 的成功和错误事件
|
||||
* @template T 返回数据类型
|
||||
* @param request IDBRequest 实例
|
||||
* @returns Promise<T> 返回处理结果
|
||||
*/
|
||||
private handleRequest<T>(request: IDBRequest<T>): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => resolve(request.result)
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取事务和对象仓库
|
||||
* @description 创建事务并获取对象仓库
|
||||
* @param storeName 仓库名称
|
||||
* @param mode 事务模式
|
||||
* @returns 包含事务和对象仓库的对象
|
||||
*/
|
||||
private async getTransactionAndStore(
|
||||
storeName: string,
|
||||
mode: IDBTransactionMode = 'readonly',
|
||||
): Promise<{
|
||||
transaction: IDBTransaction
|
||||
store: IDBObjectStore
|
||||
}> {
|
||||
await this.connect()
|
||||
const transaction = this.db!.transaction(storeName, mode)
|
||||
const store = transaction.objectStore(storeName)
|
||||
return { transaction, store }
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库连接
|
||||
* @description 创建或打开数据库连接,如果数据库不存在则自动创建
|
||||
* @returns {Promise<IDBDatabase>} 返回数据库连接实例
|
||||
* @throws {Error} 连接失败时抛出错误
|
||||
*/
|
||||
async connect(): Promise<IDBDatabase> {
|
||||
if (this.db) return this.db
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(this.config.dbName, this.config.version)
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => {
|
||||
this.db = request.result
|
||||
resolve(request.result)
|
||||
}
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result
|
||||
|
||||
Object.entries(this.config.stores).forEach(([storeName, storeConfig]) => {
|
||||
if (!db.objectStoreNames.contains(storeName)) {
|
||||
const store = db.createObjectStore(storeName, { keyPath: storeConfig.keyPath })
|
||||
|
||||
storeConfig.indexes?.forEach((index) => {
|
||||
store.createIndex(index.name, index.keyPath, index.options)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加数据
|
||||
* @description 向指定的对象仓库添加新数据
|
||||
* @template T 数据类型
|
||||
* @param {string} storeName 仓库名称
|
||||
* @param {T} data 要添加的数据
|
||||
* @returns {Promise<IDBValidKey>} 返回新添加数据的主键
|
||||
*/
|
||||
async add<T>(storeName: string, data: T): Promise<IDBValidKey> {
|
||||
const { store } = await this.getTransactionAndStore(storeName, 'readwrite')
|
||||
return this.handleRequest(store.add(data))
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
* @description 更新指定对象仓库中的数据,如果数据不存在则添加
|
||||
* @template T 数据类型
|
||||
* @param {string} storeName 仓库名称
|
||||
* @param {T} data 要更新的数据
|
||||
* @returns {Promise<IDBValidKey>} 返回更新数据的主键
|
||||
*/
|
||||
async put<T>(storeName: string, data: T): Promise<IDBValidKey> {
|
||||
const { store } = await this.getTransactionAndStore(storeName, 'readwrite')
|
||||
return this.handleRequest(store.put(data))
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据
|
||||
* @description 从指定对象仓库中删除数据
|
||||
* @param {string} storeName 仓库名称
|
||||
* @param {IDBValidKey} key 要删除数据的主键
|
||||
* @returns {Promise<void>} 删除成功时解析
|
||||
*/
|
||||
async delete(storeName: string, key: IDBValidKey): Promise<void> {
|
||||
const { store } = await this.getTransactionAndStore(storeName, 'readwrite')
|
||||
return this.handleRequest(store.delete(key))
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键获取数据
|
||||
* @description 从指定对象仓库中获取指定主键的数据
|
||||
* @template T 返回数据类型
|
||||
* @param {string} storeName 仓库名称
|
||||
* @param {IDBValidKey} key 主键值
|
||||
* @returns {Promise<T | undefined>} 返回查询到的数据
|
||||
*/
|
||||
async get<T>(storeName: string, key: IDBValidKey): Promise<T | undefined> {
|
||||
const { store } = await this.getTransactionAndStore(storeName)
|
||||
return this.handleRequest(store.get(key))
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过索引查询数据
|
||||
* @description 使用索引从指定对象仓库中查询数据
|
||||
* @template T 返回数据类型
|
||||
* @param {string} storeName 仓库名称
|
||||
* @param {string} indexName 索引名称
|
||||
* @param {IDBValidKey} key 索引值
|
||||
* @returns {Promise<T | undefined>} 返回查询到的数据
|
||||
*/
|
||||
async getByIndex<T>(storeName: string, indexName: string, key: IDBValidKey): Promise<T | undefined> {
|
||||
const { store } = await this.getTransactionAndStore(storeName)
|
||||
const index = store.index(indexName)
|
||||
return this.handleRequest(index.get(key))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有数据
|
||||
* @description 获取指定对象仓库中的所有数据
|
||||
* @template T 返回数据类型
|
||||
* @param {string} storeName 仓库名称
|
||||
* @returns {Promise<T[]>} 返回所有数据的数组
|
||||
*/
|
||||
async getAll<T>(storeName: string): Promise<T[]> {
|
||||
const { store } = await this.getTransactionAndStore(storeName)
|
||||
return this.handleRequest(store.getAll())
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用游标遍历数据
|
||||
* @description 使用游标遍历对象仓库中的数据
|
||||
* @template T 数据类型
|
||||
* @param {string} storeName 仓库名称
|
||||
* @param {(item: T) => void} callback 处理每条数据的回调函数
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async forEach<T>(storeName: string, callback: (item: T) => void): Promise<void> {
|
||||
const { store } = await this.getTransactionAndStore(storeName)
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = store.openCursor()
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
request.onsuccess = () => {
|
||||
const cursor = request.result
|
||||
if (cursor) {
|
||||
callback(cursor.value)
|
||||
cursor.continue()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加数据
|
||||
* @description 向指定的对象仓库批量添加数据
|
||||
* @template T 数据类型
|
||||
* @param {string} storeName 仓库名称
|
||||
* @param {T[]} items 要添加的数据数组
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async addBatch<T>(storeName: string, items: T[]): Promise<void> {
|
||||
const { store } = await this.getTransactionAndStore(storeName, 'readwrite')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
items.forEach((item) => store.add(item))
|
||||
resolve()
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空对象仓库
|
||||
* @description 删除指定对象仓库中的所有数据
|
||||
* @param {string} storeName 仓库名称
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async clear(storeName: string): Promise<void> {
|
||||
const { store } = await this.getTransactionAndStore(storeName, 'readwrite')
|
||||
return this.handleRequest(store.clear())
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭数据库连接
|
||||
* @description 安全地关闭数据库连接,释放资源
|
||||
*/
|
||||
close(): void {
|
||||
if (this.db) {
|
||||
this.db.close()
|
||||
this.db = null
|
||||
}
|
||||
}
|
||||
}
|
||||
314
frontend/packages/utils/src/business.ts
Normal file
314
frontend/packages/utils/src/business.ts
Normal file
@@ -0,0 +1,314 @@
|
||||
/**
|
||||
* 文件定义:业务处理
|
||||
*/
|
||||
|
||||
import * as R from 'ramda'
|
||||
import { isArray } from './type'
|
||||
|
||||
/* -------------- 1、常用正则验证 -------------- */
|
||||
// 常量定义区域
|
||||
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
const PHONE_REGEX = /^1[3-9]\d{9}$/
|
||||
const ID_CARD_REGEX = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/
|
||||
const URL_REGEX = /^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+/
|
||||
const DOMAIN_REGEX = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/
|
||||
|
||||
// 增强版域名正则表达式 - 支持国际化域名和更多顶级域名
|
||||
const ENHANCED_DOMAIN_REGEX =
|
||||
/^(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)|(?:\*))\.)+(?:[a-zA-Z\u00a1-\uffff]{2,}|xn--[a-zA-Z0-9]+)$/
|
||||
|
||||
// 通配符域名正则表达式 - 支持通配符域名格式 (如 *.example.com)
|
||||
const WILDCARD_DOMAIN_REGEX = /^\*\.(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/
|
||||
|
||||
// IPv4正则表达式 - 更精确的数字范围
|
||||
const IPV4_SEGMENT = '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])'
|
||||
const IPV4_REGEX = new RegExp(`^${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}$`)
|
||||
|
||||
// IPv6正则表达式 - 更精确的十六进制表示
|
||||
const IPV6_HEX_4DIGIT = '[0-9A-Fa-f]{1,4}'
|
||||
const IPV6_REGEX = new RegExp(
|
||||
[
|
||||
// 标准IPv6地址
|
||||
`^(${IPV6_HEX_4DIGIT}:){7}${IPV6_HEX_4DIGIT}$`,
|
||||
// 压缩形式
|
||||
`^(${IPV6_HEX_4DIGIT}:){1,7}:$`,
|
||||
'^:((:[0-9A-Fa-f]{1,4}){1,7}|:)$',
|
||||
// 混合形式
|
||||
`^(${IPV6_HEX_4DIGIT}:){1,6}:${IPV6_HEX_4DIGIT}$`,
|
||||
`^(${IPV6_HEX_4DIGIT}:){1,5}(:${IPV6_HEX_4DIGIT}){1,2}$`,
|
||||
`^(${IPV6_HEX_4DIGIT}:){1,4}(:${IPV6_HEX_4DIGIT}){1,3}$`,
|
||||
`^(${IPV6_HEX_4DIGIT}:){1,3}(:${IPV6_HEX_4DIGIT}){1,4}$`,
|
||||
`^(${IPV6_HEX_4DIGIT}:){1,2}(:${IPV6_HEX_4DIGIT}){1,5}$`,
|
||||
`^${IPV6_HEX_4DIGIT}:(:${IPV6_HEX_4DIGIT}){1,6}$`,
|
||||
// 特殊形式
|
||||
`^fe80:(:[0-9A-Fa-f]{1,4}){0,4}%[0-9A-Za-z]{1,}$`,
|
||||
// IPv4映射到IPv6
|
||||
`^::((ffff(:0{1,4})?:)?${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT})$`,
|
||||
`^(${IPV6_HEX_4DIGIT}:){1,4}:${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}$`,
|
||||
].join('|'),
|
||||
)
|
||||
|
||||
// IP段正则表达式
|
||||
const IPS_REGEX = new RegExp(
|
||||
`^${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}(\\/([1-2][0-9]|3[0-2]|[1-9]))?$`,
|
||||
)
|
||||
|
||||
// MAC地址正则表达式
|
||||
const MAC_REGEX = /^([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}$/
|
||||
|
||||
// 中文正则表达式
|
||||
const CHINESE_REGEX = /^[\u4e00-\u9fa5]+$/
|
||||
|
||||
// 端口正则表达式 - 更精确的数字范围
|
||||
const PORT_REGEX = /^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/
|
||||
|
||||
/**
|
||||
* 判断是否为邮箱
|
||||
* @param {string} email - 要判断的邮箱
|
||||
* @returns {boolean} 如果邮箱是有效的,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isEmail = R.test(EMAIL_REGEX)
|
||||
|
||||
/**
|
||||
* 判断是否为手机号
|
||||
* @param {string} phone - 要判断的手机号
|
||||
* @returns {boolean} 如果手机号是有效的,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isPhone = R.test(PHONE_REGEX)
|
||||
|
||||
/**
|
||||
* 判断是否为身份证号
|
||||
* @param {string} idCard - 要判断的身份证号
|
||||
* @returns {boolean} 如果身份证号是有效的,则返回 true,否则返 false
|
||||
*/
|
||||
export const isIdCard = R.test(ID_CARD_REGEX)
|
||||
|
||||
/**
|
||||
* 判断是否为URL
|
||||
* @param {string} url - 要判断的url
|
||||
* @returns {boolean} 如果url是有效的,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isUrl = R.test(URL_REGEX)
|
||||
|
||||
/**
|
||||
* 判断是否为IPv4地址
|
||||
* @param {string} ip - 要判断的IP地址
|
||||
* @returns {boolean} 如果是有效的IPv4地址,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isIpv4 = R.test(IPV4_REGEX)
|
||||
|
||||
/**
|
||||
* 判断是否为IPv6地址
|
||||
* @param {string} ip - 要判断的IP地址
|
||||
* @returns {boolean} 如果是有效的IPv6地址,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isIpv6 = R.test(IPV6_REGEX)
|
||||
|
||||
/**
|
||||
* 判断是否为IP地址(IPv4或IPv6)
|
||||
* @param {string} ip - 要判断的IP地址
|
||||
* @returns {boolean} 如果IP地址是有效的,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isIp = (ip: string): boolean => isIpv4(ip) || isIpv6(ip)
|
||||
|
||||
/**
|
||||
* 判断是否为IP段
|
||||
* @param {string} ips - 要判断的IP段
|
||||
* @returns {boolean} 如果IP段是有效的,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isIps = R.test(IPS_REGEX)
|
||||
|
||||
/**
|
||||
* 判断端口
|
||||
* @param {string} port - 判断端口
|
||||
* @returns {boolean} 如果端口是有效的,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isPort = R.test(PORT_REGEX)
|
||||
|
||||
/**
|
||||
* 判断是否为MAC地址
|
||||
* @param {string} mac - 要判断的MAC地址
|
||||
* @returns {boolean} 如果MAC地址是有效的,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isMac = R.test(MAC_REGEX)
|
||||
|
||||
/**
|
||||
* 判断是否为中文
|
||||
* @param {string} str - 要判断的字符串
|
||||
* @returns {boolean} 如果字符串是中文,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isChinese = R.test(CHINESE_REGEX)
|
||||
|
||||
/**
|
||||
* 判断是否为域名
|
||||
* @param {string} domain - 要判断的域名
|
||||
* @returns {boolean} 如果域名是有效的,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isDomain = R.test(DOMAIN_REGEX)
|
||||
|
||||
/**
|
||||
* 判断是否为域名(增强版)
|
||||
* @param {string} domain - 要判断的域名,支持国际化域名和更多顶级域名
|
||||
* @returns {boolean} 如果域名是有效的,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isEnhancedDomain = R.test(ENHANCED_DOMAIN_REGEX)
|
||||
|
||||
/**
|
||||
* 判断是否为通配符域名
|
||||
* @param {string} domain - 要判断的通配符域名
|
||||
* @returns {boolean} 如果通配符域名是有效的,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isWildcardDomain = R.test(WILDCARD_DOMAIN_REGEX)
|
||||
|
||||
/**
|
||||
* 判断域名组,通过特定字符串分割
|
||||
* @param {string} domain - 要判断的域名
|
||||
* @param {string} separator - 分割符
|
||||
* @returns {boolean} 如果域名组是有效的,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isDomainGroup = (domain: string, separator: string = ',') => {
|
||||
return R.all(
|
||||
R.equals(true),
|
||||
R.map(
|
||||
(item: string) => isDomain(item) || isWildcardDomain(item) || isEnhancedDomain(item),
|
||||
R.split(separator, domain),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/* -------------- 2、常用业务操作 -------------- */
|
||||
|
||||
/**
|
||||
* 手机号加密
|
||||
* @param {string} phone - 要加密的手机号
|
||||
* @returns {string} 加密后的手机号
|
||||
*/
|
||||
export const encryptPhone = (phone: string): string => phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
||||
|
||||
/**
|
||||
* 身份证号加密
|
||||
* @param {string} idCard - 要加密的身份证号(18位,最后一位可以是X)
|
||||
* @returns {string} 加密后的身份证号
|
||||
*/
|
||||
export const encryptIdCard = (idCard: string): string => idCard.replace(/(\d{6})\d{8}([\dXx]{4})/, '$1****$2')
|
||||
|
||||
/**
|
||||
* 版本号比较
|
||||
* @param {string} version1 - 版本号1
|
||||
* @param {string} version2 - 版本号2
|
||||
* @returns {number} 如果版本号1大于版本号2,则返回1,如果版本号1小于版本号2,则返回-1,如果版本号1等于版本号2,则返回0
|
||||
*/
|
||||
export const compareVersion = (version1: string, version2: string): number => {
|
||||
// 使用Ramda的pipe函数组合操作
|
||||
const parseVersion = R.pipe(
|
||||
R.split('.'),
|
||||
R.map((v: string) => parseInt(v || '0', 10)),
|
||||
)
|
||||
const v1 = parseVersion(version1) // 解析版本号1
|
||||
const v2 = parseVersion(version2) // 解析版本号2
|
||||
|
||||
// 确保两个数组长度相同
|
||||
const len = Math.max(v1.length, v2.length)
|
||||
|
||||
// 使用Ramda的repeat和take函数来填充数组
|
||||
const paddedV1 = R.concat(v1, R.repeat(0, len - v1.length))
|
||||
const paddedV2 = R.concat(v2, R.repeat(0, len - v2.length))
|
||||
|
||||
// 使用Ramda的zipWith比较每个版本号段
|
||||
const comparisons = R.zipWith((a: number, b: number) => (a === b ? 0 : a > b ? 1 : -1), paddedV1, paddedV2)
|
||||
|
||||
// 找到第一个非零的比较结果
|
||||
const result = R.find(R.complement(R.equals(0)), comparisons)
|
||||
return result ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节转换
|
||||
* @param {number} bytes - 要转换的字节数
|
||||
* @param {number} [fixed=2] - 保留小数位数
|
||||
* @param {boolean} [isUnit=true] - 是否显示单位
|
||||
* @param {string} [endUnit=''] - 指定结束单位,如果指定则转换到该单位为止
|
||||
* @returns {string} 转换后的字节数
|
||||
*/
|
||||
export const formatBytes = (bytes: number, fixed: number = 2, isUnit: boolean = true, endUnit: string = ''): string => {
|
||||
if (bytes === 0) return isUnit ? '0 B' : '0'
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const c = 1024
|
||||
|
||||
// 使用Ramda的递归函数进行单位转换
|
||||
const convert = (value: number, unitIndex: number): string => {
|
||||
const unit = units[unitIndex]
|
||||
const formattedValue = unitIndex === 0 || fixed === 0 ? Math.round(value).toString() : value.toFixed(fixed)
|
||||
// 如果指定了结束单位或者已经是最小单位
|
||||
if ((endUnit && unit === endUnit) || value < c || unitIndex >= units.length - 1) {
|
||||
return isUnit ? `${formattedValue} ${unit}` : formattedValue
|
||||
}
|
||||
// 继续转换到下一个单位
|
||||
return convert(value / c, unitIndex + 1)
|
||||
}
|
||||
return convert(bytes, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 柯里化版本的formatBytes
|
||||
* @param {number} bytes - 要转换的字节数
|
||||
* @param {number} [fixed=2] - 保留小数位数
|
||||
* @param {boolean} [isUnit=true] - 是否显示单位
|
||||
* @param {string} [endUnit=''] - 指定结束单位,如果指定则转换到该单位为止
|
||||
* @returns {string} 转换后的字节数
|
||||
*/
|
||||
export const formatBytesCurried: {
|
||||
(bytes: number, fixed: number, isUnit: boolean, endUnit: string): string
|
||||
(bytes: number): (fixed?: number, isUnit?: boolean, endUnit?: string) => string
|
||||
(bytes: number, fixed: number): (isUnit?: boolean, endUnit?: string) => string
|
||||
(bytes: number, fixed: number, isUnit: boolean): (endUnit?: string) => string
|
||||
} = R.curry(formatBytes)
|
||||
|
||||
/**
|
||||
* 分页字符串转换
|
||||
* @param {string} page - 分页字符串
|
||||
* @returns {string} 转换后的分页字符串
|
||||
*/
|
||||
export const formatPage = (page: string): number => {
|
||||
const newPage = page.match(/class='Pcount'>共([0-9]*)条</)
|
||||
if (isArray(newPage) && newPage.length >= 2) return Number(newPage[1])
|
||||
return 0
|
||||
}
|
||||
|
||||
/* -------------- 3、代理函数 -------------- */
|
||||
|
||||
export type ProxyConfig = {
|
||||
requestTime: number
|
||||
requestToken: string
|
||||
request_time: number
|
||||
request_token: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 代理配置,仅在开发环境生效
|
||||
* @param {string} proxyKey - 代理密钥
|
||||
* @param {string} usage 使用场景 "query" | "params"
|
||||
* @returns {Object} 返回对象包含 request_time 和 request_token
|
||||
*/
|
||||
export const getProxyConfig = async (proxyKey: string, usage: 'query' | 'params' = 'params') => {
|
||||
const md5 = await import('md5')
|
||||
const request_time = Date.now()
|
||||
const request_token = md5.default(String(request_time).concat(md5.default(proxyKey)))
|
||||
if (usage === 'params') {
|
||||
return { request_time, request_token, requestTime: request_time, requestToken: request_token }
|
||||
}
|
||||
return `request_time=${request_time}&request_token=${request_token}`
|
||||
}
|
||||
|
||||
/** -------------- 4、接口缓存配置 -------------- */
|
||||
|
||||
/**
|
||||
* 接口缓存配置
|
||||
* @param {function} method - 接口请求方法
|
||||
* @param {string} params - 接口请求参数
|
||||
* @param {Record<string, any>} options - 接口请求配置
|
||||
* @returns {string} 返回数据
|
||||
*/
|
||||
export const getCacheConfig = (method: Function, params: string, options: Record<string, any> = {}) => {
|
||||
console.log(method, params, options)
|
||||
}
|
||||
300
frontend/packages/utils/src/data.ts
Normal file
300
frontend/packages/utils/src/data.ts
Normal file
@@ -0,0 +1,300 @@
|
||||
/**
|
||||
* 文件定义:数据处理方法
|
||||
* 包含:1、数据类型检查。2、数据转换。3、日期处理。4、数据校验。5、数据过滤与重组。6、特殊场景处理
|
||||
*/
|
||||
|
||||
import * as R from 'ramda'
|
||||
|
||||
// =============== 数据转换 ===============
|
||||
|
||||
/**
|
||||
* 将对象的所有值转换为字符串
|
||||
* @param {Record<string, any>} obj - 要转换的对象
|
||||
* @returns {Record<string, string>} 转换后的对象
|
||||
*/
|
||||
export const objectToString = R.map(String)
|
||||
|
||||
/**
|
||||
* 将数组转换为对象,使用指定的 key
|
||||
* @param {string} key - 要转换的 key
|
||||
* @param {Record<string, any>[]} array - 要转换的数组
|
||||
* @returns {Record<string, Record<string, any>>} 转换后的对象
|
||||
*/
|
||||
export const arrayToObject = R.curry((key: string, array: Record<string, any>[]) => R.indexBy(R.prop(key), array)) as <
|
||||
T extends Record<string, any>,
|
||||
>(
|
||||
key: string,
|
||||
array: T[],
|
||||
) => Record<string, T>
|
||||
|
||||
/**
|
||||
* 深度扁平化对象(建议深度嵌套的对象使用)
|
||||
* @param {Record<string, any>} obj - 要扁平化的对象
|
||||
* @returns {Record<string, any>} 扁平化后的对象
|
||||
*/
|
||||
export const flattenObject = (obj: Record<string, unknown>): Record<string, unknown> => {
|
||||
const result: Record<string, unknown> = {}
|
||||
|
||||
const flatten = (obj: Record<string, any>, prefix: string = '') => {
|
||||
for (const key in obj) {
|
||||
const value = obj[key]
|
||||
const newKey = prefix ? `${prefix}.${key}` : key
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
flatten(value, newKey)
|
||||
} else {
|
||||
result[newKey] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flatten(obj)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证字符串是否符合正则表达式
|
||||
* @param {RegExp} pattern - 要验证的正则表达式
|
||||
* @param {string} str - 要验证的字符串
|
||||
* @returns {boolean} 如果字符串符合正则表达式,则返回 true,否则返回 false
|
||||
*/
|
||||
export const matchesPattern = R.curry((pattern: RegExp, str: string) => R.test(pattern, str)) as <T extends RegExp>(
|
||||
pattern: T,
|
||||
str: string,
|
||||
) => boolean
|
||||
|
||||
/**
|
||||
* 验证对象是否包含所有必需的键
|
||||
* @param {Record<string, any>} obj - 要验证的对象
|
||||
* @param {string[]} requiredKeys - 要验证的键
|
||||
* @returns {boolean} 如果对象包含所有必需的键,则返回 true,否则返回 false
|
||||
*/
|
||||
export const hasRequiredKeys = R.curry((obj: Record<string, unknown>, requiredKeys: string[]) =>
|
||||
R.all(R.flip(R.has)(obj), requiredKeys),
|
||||
) as {
|
||||
(obj: Record<string, unknown>): (requiredKeys: string[]) => boolean
|
||||
(obj: Record<string, unknown>, requiredKeys: string[]): boolean
|
||||
}
|
||||
|
||||
// ... existing code ...
|
||||
/**
|
||||
* 验证值是否在指定范围内
|
||||
* @param {number} min - 最小值
|
||||
* @param {number} max - 最大值
|
||||
* @param {number} value - 要验证的值
|
||||
* @returns {boolean} 如果值在指定范围内,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isInRange = R.curry((min: number, max: number, value: number) =>
|
||||
R.both(R.gte(R.__, min), R.lte(R.__, max))(value),
|
||||
) as <T extends number>(min: T, max: T, value: T) => boolean
|
||||
|
||||
// =============== 数据过滤与重组 ===============
|
||||
|
||||
/**
|
||||
* 根据条件过滤对象的属性
|
||||
* @param {Function} predicate - 要过滤的条件
|
||||
* @param {Record<string, any>} obj - 要过滤的对象
|
||||
* @returns {Record<string, any>} 过滤后的对象
|
||||
*/
|
||||
export const filterObject = R.curry(
|
||||
<T extends Record<string, any>>(predicate: (value: T[keyof T]) => boolean, obj: T) =>
|
||||
Object.fromEntries(Object.entries(obj).filter(([_, value]) => predicate(value))),
|
||||
) as {
|
||||
<T extends Record<string, any>>(predicate: (value: T[keyof T]) => boolean): (obj: T) => Partial<T>
|
||||
<T extends Record<string, any>>(predicate: (value: T[keyof T]) => boolean, obj: T): Partial<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照指定的键对数组进行分组
|
||||
* @param {string} key - 要分组的键
|
||||
* @param {Record<string, any>[]} array - 要分组的数组
|
||||
* @returns {Record<string, Record<string, any>[]>} 分组后的对象
|
||||
*/
|
||||
export const groupByKey = R.curry(<T extends Record<string, any>>(key: string, array: T[]) =>
|
||||
R.groupBy(R.prop(key), array),
|
||||
) as <T extends Record<string, any>>(key: string, array: T[]) => Record<string, T[]>
|
||||
|
||||
/**
|
||||
* 从对象数组中提取指定的键值
|
||||
* @param {string[]} path - 要提取的键
|
||||
* @param {Record<string, any>[]} list - 要提取的对象数组
|
||||
* @returns {Record<string, any>[]} 提取后的对象数组
|
||||
*/
|
||||
export const pluckDeep = R.curry(<T>(path: string[], list: T[]) => R.map(R.path(path), list)) as <
|
||||
T extends Record<string, any>,
|
||||
>(
|
||||
path: string[],
|
||||
list: T[],
|
||||
) => T[]
|
||||
|
||||
/**
|
||||
* 对嵌套数组进行扁平化和去重
|
||||
* @param {any[]} array - 要扁平化和去重的数组
|
||||
* @returns {any[]} 扁平化和去重后的数组
|
||||
*/
|
||||
export const flattenAndUniq = R.pipe(R.flatten, R.uniq) as <T>(array: T[]) => T[]
|
||||
|
||||
// =============== 数据映射 ===============
|
||||
type MapperOption = {
|
||||
inherit?: string[] // 继承字段
|
||||
deep?: boolean // 深度映射
|
||||
ignore?: string[] // 忽略字段
|
||||
}
|
||||
|
||||
type MapperType = [string, string][] | Record<string, string>
|
||||
type DataType = Record<string, unknown> | Record<string, unknown>[]
|
||||
|
||||
/**
|
||||
* 对象/数组映射,根据映射表,将数组或对象映射为新的对象和数组
|
||||
* 支持继承/过滤,通过参数继承/过滤,选取自己需要的数据
|
||||
* 增加异常处理,如果值不存在,则抛出异常。
|
||||
* 返回新的对象/数组
|
||||
*/
|
||||
export const mapData = (mapper: MapperType, data: DataType, options: MapperOption = { deep: true }): DataType => {
|
||||
const { inherit, deep, ignore } = options
|
||||
|
||||
// 验证 inherit 和 ignore 不能同时使用
|
||||
if (inherit && ignore) {
|
||||
throw new Error('inherit 和 ignore 选项不能同时使用')
|
||||
}
|
||||
|
||||
// 将 mapper 转换为对象形式
|
||||
const mapperObj = Array.isArray(mapper)
|
||||
? mapper.reduce<Record<string, string>>((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
||||
: mapper
|
||||
|
||||
// 处理数组
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((item) => mapData(mapperObj, item, options) as Record<string, unknown>)
|
||||
}
|
||||
|
||||
// 处理对象
|
||||
if (typeof data === 'object' && data !== null) {
|
||||
// 根据选项过滤 mapper
|
||||
let finalMapper = { ...mapperObj }
|
||||
if (inherit) {
|
||||
finalMapper = Object.entries(mapperObj)
|
||||
.filter(([key]) => inherit.includes(key))
|
||||
.reduce<Record<string, string>>((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
||||
} else if (ignore) {
|
||||
finalMapper = Object.entries(mapperObj)
|
||||
.filter(([key]) => !ignore.includes(key))
|
||||
.reduce<Record<string, string>>((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
||||
}
|
||||
|
||||
return Object.entries(finalMapper).reduce<Record<string, unknown>>((result, [sourceKey, targetKey]) => {
|
||||
// 处理嵌套路径
|
||||
const value = sourceKey.split('.').reduce<unknown>((obj, key) => {
|
||||
if (obj === undefined || obj === null) {
|
||||
throw new Error(`映射键 "${sourceKey}" 不存在于源数据中`)
|
||||
}
|
||||
return (obj as Record<string, unknown>)[key]
|
||||
}, data)
|
||||
|
||||
// 处理值不存在的情况
|
||||
if (value === undefined) {
|
||||
throw new Error(`映射键 "${sourceKey}" 的值不存在`)
|
||||
}
|
||||
|
||||
// 处理深度映射
|
||||
if (deep && typeof value === 'object' && value !== null) {
|
||||
const nestedMapper = Object.entries(mapperObj)
|
||||
.filter(([key]) => key.startsWith(`${sourceKey}.`))
|
||||
.reduce<Record<string, string>>(
|
||||
(acc, [key, val]) => ({
|
||||
...acc,
|
||||
[key.slice(sourceKey.length + 1)]: val,
|
||||
}),
|
||||
{},
|
||||
)
|
||||
|
||||
if (Object.keys(nestedMapper).length > 0) {
|
||||
return {
|
||||
...result,
|
||||
[targetKey]: mapData(nestedMapper, value as Record<string, unknown>, options),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理嵌套目标路径
|
||||
const targetPath = (targetKey as string).split('.')
|
||||
const finalKey = targetPath.pop()!
|
||||
const targetObj = targetPath.reduce<Record<string, unknown>>((obj, key) => {
|
||||
if (!(key in obj)) {
|
||||
obj[key] = {}
|
||||
}
|
||||
return obj[key] as Record<string, unknown>
|
||||
}, result)
|
||||
|
||||
if (finalKey && targetObj) {
|
||||
targetObj[finalKey] = value
|
||||
}
|
||||
|
||||
return result
|
||||
}, {})
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 生成映射表,将所有字段转换为小驼峰
|
||||
* @param {Record<string, unknown>} obj - 要转换的对象
|
||||
* @returns {Record<string, unknown>} 转换后的对象
|
||||
*/
|
||||
export const generateMapper = (obj: Record<string, unknown>) => {
|
||||
return Object.entries(obj).map(([key, value]) => [
|
||||
key.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()),
|
||||
value,
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象转换为查询字符串
|
||||
* @param {Record<string, any>} obj - 要转换的对象
|
||||
* @returns {string} 转换后的查询字符串
|
||||
*/
|
||||
export const objectToQueryString = (obj: Record<string, any>) => {
|
||||
return Object.entries(obj)
|
||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||
.join('&')
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 深度合并两个对象
|
||||
* @param {Record<string, any>} target - 目标对象
|
||||
* @param {Record<string, any>} source - 源对象
|
||||
* @returns {Record<string, any>} 合并后的对象
|
||||
*/
|
||||
export const deepMerge = <T extends Record<string, any>>(target: T, source: T, isMergeArray: boolean = true): T => {
|
||||
const result = { ...target }
|
||||
|
||||
for (const key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
const sourceValue = source[key]
|
||||
const targetValue = target[key]
|
||||
|
||||
if (Array.isArray(sourceValue) && Array.isArray(targetValue)) {
|
||||
// 如果是数组,则合并数组
|
||||
result[key] = isMergeArray ? [...targetValue, ...sourceValue] : sourceValue
|
||||
} else if (isObject(sourceValue) && isObject(targetValue)) {
|
||||
// 如果是对象,则递归合并
|
||||
result[key] = deepMerge(targetValue, sourceValue)
|
||||
} else {
|
||||
// 其他情况直接覆盖
|
||||
result[key] = sourceValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为对象
|
||||
* @param {any} value - 要判断的值
|
||||
* @returns {boolean} 是否为对象
|
||||
*/
|
||||
const isObject = (value: any): boolean => {
|
||||
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
||||
}
|
||||
191
frontend/packages/utils/src/date.ts
Normal file
191
frontend/packages/utils/src/date.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* 文件定义:日期处理
|
||||
*/
|
||||
|
||||
import * as R from 'ramda'
|
||||
|
||||
/* -------------- 1、日期处理 -------------- */
|
||||
/**
|
||||
* 格式化时间格式
|
||||
* @param {string | number | Date} date - 日期字符串、时间戳、Date 对象
|
||||
* @param {string} format - 格式化字符串
|
||||
* @returns {string} 格式化后的日期字符串
|
||||
*/
|
||||
export const formatDate = (date: string | number | Date, format: string = 'yyyy-MM-dd HH:mm:ss'): string => {
|
||||
// 处理秒级时间戳
|
||||
const timestamp = !!Number(date) && date.toString().length === 10 ? new Date(Number(date) * 1000) : new Date(date)
|
||||
|
||||
// 使用Ramda创建日期映射
|
||||
const dateMap = R.zipObj(
|
||||
['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss'],
|
||||
[
|
||||
timestamp.getFullYear(),
|
||||
timestamp.getMonth() + 1,
|
||||
timestamp.getDate(),
|
||||
timestamp.getHours(),
|
||||
timestamp.getMinutes(),
|
||||
timestamp.getSeconds(),
|
||||
],
|
||||
)
|
||||
|
||||
// 使用Ramda的reduce函数替换格式字符串中的占位符
|
||||
return R.reduce(
|
||||
(result: string, key: string) => {
|
||||
const value = dateMap[key as keyof typeof dateMap]
|
||||
// 将单位数的月、日、时、分、秒前面补0
|
||||
const formattedValue = key !== 'yyyy' && value < 10 ? `0${value}` : `${value}`
|
||||
// 使用正则表达式全局替换所有匹配项
|
||||
return result.replace(new RegExp(key, 'g'), formattedValue)
|
||||
},
|
||||
format,
|
||||
R.keys(dateMap),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取两个日期之间的天数差
|
||||
* @param {string | number | Date} startDate - 开始日期
|
||||
* @param {string | number | Date} endDate - 结束日期
|
||||
* @returns {number} 天数差
|
||||
*/
|
||||
export const getDaysDiff = (startDate: string | number | Date, endDate: string | number | Date): number => {
|
||||
const start = new Date(startDate)
|
||||
const end = new Date(endDate)
|
||||
const startDay = new Date(start.getFullYear(), start.getMonth(), start.getDate())
|
||||
const endDay = new Date(end.getFullYear(), end.getMonth(), end.getDate())
|
||||
const diff = endDay.getTime() - startDay.getTime()
|
||||
return Math.floor(diff / (1000 * 60 * 60 * 24))
|
||||
}
|
||||
|
||||
/**
|
||||
* 柯里化版本的getDaysDiff
|
||||
* @param {string | number | Date} startDate - 开始日期
|
||||
* @param {string | number | Date} endDate - 结束日期
|
||||
* @returns {number} 天数差
|
||||
*/
|
||||
export const getDaysDiffCurried: {
|
||||
(startDate: string | number | Date, endDate: string | number | Date): number
|
||||
(startDate: string | number | Date): (endDate: string | number | Date) => number
|
||||
} = R.curry(getDaysDiff)
|
||||
|
||||
/**
|
||||
* 判断日期是否在指定范围内
|
||||
* @param {string | number | Date} date - 要判断的日期
|
||||
* @param {string | number | Date} startDate - 开始日期
|
||||
* @param {string | number | Date} endDate - 结束日期
|
||||
* @returns {boolean} 是否在范围内
|
||||
*/
|
||||
export const isDateInRange = (
|
||||
date: string | number | Date,
|
||||
startDate: string | number | Date,
|
||||
endDate: string | number | Date,
|
||||
): boolean => {
|
||||
const targetTime = new Date(date).getTime()
|
||||
const startTime = new Date(startDate).getTime()
|
||||
const endTime = new Date(endDate).getTime()
|
||||
return targetTime >= startTime && targetTime <= endTime
|
||||
}
|
||||
|
||||
/**
|
||||
* 柯里化版本的isDateInRange
|
||||
* @param {string | number | Date} date - 要判断的日期
|
||||
* @param {string | number | Date} startDate - 开始日期
|
||||
* @param {string | number | Date} endDate - 结束日期
|
||||
* @returns {boolean} 是否在范围内
|
||||
*/
|
||||
export const isDateInRangeCurried: {
|
||||
(date: string | number | Date, startDate: string | number | Date, endDate: string | number | Date): boolean
|
||||
(date: string | number | Date): {
|
||||
(startDate: string | number | Date, endDate: string | number | Date): boolean
|
||||
(startDate: string | number | Date): (endDate: string | number | Date) => boolean
|
||||
}
|
||||
(date: string | number | Date, startDate: string | number | Date): (endDate: string | number | Date) => boolean
|
||||
} = R.curry(isDateInRange)
|
||||
|
||||
/**
|
||||
* 获取指定日期的开始时间(00:00:00)
|
||||
* @param {string | number | Date} date - 日期
|
||||
* @returns {Date} 日期的开始时间
|
||||
*/
|
||||
export const getStartOfDay = (date: string | number | Date): Date => {
|
||||
const d = new Date(date)
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期的结束时间(23:59:59)
|
||||
* @param {string | number | Date} date - 日期
|
||||
* @returns {Date} 日期的结束时间
|
||||
*/
|
||||
export const getEndOfDay = (date: string | number | Date): Date => {
|
||||
const d = new Date(date)
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999)
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加天数到指定日期
|
||||
* @param {number} days - 要添加的天数
|
||||
* @param {string | number | Date} date - 日期
|
||||
* @returns {Date} 新日期
|
||||
*/
|
||||
export const addDays = (days: number, date: string | number | Date): Date => {
|
||||
const result = new Date(date)
|
||||
result.setDate(result.getDate() + days)
|
||||
return result
|
||||
}
|
||||
|
||||
// 柯里化版本的addDays
|
||||
export const addDaysCurried: {
|
||||
(days: number, date: string | number | Date): Date
|
||||
(days: number): (date: string | number | Date) => Date
|
||||
} = R.curry(addDays)
|
||||
|
||||
/**
|
||||
* 格式化相对时间(如:刚刚、x分钟前、x小时前、x天前)
|
||||
* @param {string | number | Date} date - 日期
|
||||
* @returns {string} 格式化后的相对时间
|
||||
*/
|
||||
export const formatRelativeTime = (date: string | number | Date): string => {
|
||||
const now = new Date().getTime()
|
||||
const target = new Date(date).getTime()
|
||||
const diff = now - target
|
||||
|
||||
if (diff < 1000 * 60) {
|
||||
return '刚刚'
|
||||
} else if (diff < 1000 * 60 * 60) {
|
||||
return `${Math.floor(diff / (1000 * 60))}分钟前`
|
||||
} else if (diff < 1000 * 60 * 60 * 24) {
|
||||
return `${Math.floor(diff / (1000 * 60 * 60))}小时前`
|
||||
} else if (diff < 1000 * 60 * 60 * 24 * 30) {
|
||||
return `${Math.floor(diff / (1000 * 60 * 60 * 24))}天前`
|
||||
} else {
|
||||
return formatDate(date, 'YYYY-MM-DD')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期是星期几
|
||||
* @param {string | number | Date} date - 日期
|
||||
* @returns {string} 星期几
|
||||
*/
|
||||
export const getDayOfWeek = (date: string | number | Date): string => {
|
||||
const days = ['日', '一', '二', '三', '四', '五', '六']
|
||||
return `星期${days[new Date(date).getDay()]}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定距离到期时间
|
||||
* @param {string | number | Date} date - 日期
|
||||
* @param {string | number | Date} expirationDate - 到期日期, 默认当前时间
|
||||
* @returns {string} 距离到期时间
|
||||
*/
|
||||
export const getDaysUntilExpiration = (
|
||||
date: string | number | Date,
|
||||
expirationDate: string | number | Date = new Date(),
|
||||
): string => {
|
||||
const target = new Date(date)
|
||||
const expiration = new Date(expirationDate)
|
||||
const diff = expiration.getTime() - target.getTime()
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
||||
return days > 0 ? `${days}天` : '已过期'
|
||||
}
|
||||
86
frontend/packages/utils/src/encipher.ts
Normal file
86
frontend/packages/utils/src/encipher.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 文件定义:加密解密
|
||||
*/
|
||||
|
||||
// import JSEncrypt from 'jsencrypt'
|
||||
/* -------------- 1、加密解密 -------------- */
|
||||
|
||||
/**
|
||||
* 生成2048位RSA密钥对
|
||||
* @returns {{ publicKey: string, privateKey: string }} 包含公钥和私钥的对象
|
||||
*/
|
||||
export const generateKeyPair = async () => {
|
||||
const { JSEncrypt } = await import('jsencrypt')
|
||||
const encrypt = new JSEncrypt({ default_key_size: '2048' })
|
||||
encrypt.getKey()
|
||||
|
||||
return {
|
||||
publicKey: encrypt.getPublicKey() as string,
|
||||
privateKey: encrypt.getPrivateKey() as string,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA加密
|
||||
* @param {string} str - 需要加密的字符串
|
||||
* @param {string} publicKey - 公钥
|
||||
* @returns {string} 加密后的字符串
|
||||
*/
|
||||
export const rsaEncrypt = async (str: string, publicKey: string): Promise<string> => {
|
||||
const { JSEncrypt } = await import('jsencrypt')
|
||||
// 基础验证
|
||||
if (!str || !publicKey || publicKey.length < 10) return str
|
||||
// 检查字符串长度(2048位RSA密钥最大可加密245字节)
|
||||
const byteLength = new TextEncoder().encode(str).length
|
||||
if (byteLength > 245) {
|
||||
console.error('RSA加密失败: 数据长度超过245字节限制')
|
||||
return str
|
||||
}
|
||||
|
||||
try {
|
||||
const encrypt = new JSEncrypt()
|
||||
encrypt.setPublicKey(publicKey)
|
||||
const encrypted = encrypt.encrypt(str)
|
||||
|
||||
// 确保加密结果有效
|
||||
if (!encrypted) {
|
||||
console.error('RSA加密失败')
|
||||
return str
|
||||
}
|
||||
|
||||
return encrypted
|
||||
} catch (error) {
|
||||
console.error('RSA加密出错:', error)
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA解密
|
||||
* @param {string} str - 需要解密的字符串
|
||||
* @param {string} privateKey - 私钥
|
||||
* @returns {string} 解密后的字符串
|
||||
*/
|
||||
export const rsaDecrypt = async (str: string, privateKey: string): Promise<string> => {
|
||||
const { JSEncrypt } = await import('jsencrypt')
|
||||
|
||||
// 基础验证
|
||||
if (!str || !privateKey || privateKey.length < 10) return str
|
||||
|
||||
try {
|
||||
const decrypt = new JSEncrypt()
|
||||
decrypt.setPrivateKey(privateKey)
|
||||
const decrypted = decrypt.decrypt(str)
|
||||
|
||||
// 确保解密结果有效
|
||||
if (!decrypted) {
|
||||
console.error('RSA解密失败')
|
||||
return str
|
||||
}
|
||||
|
||||
return decrypted
|
||||
} catch (error) {
|
||||
console.error('RSA解密出错:', error)
|
||||
return str
|
||||
}
|
||||
}
|
||||
99
frontend/packages/utils/src/random.ts
Normal file
99
frontend/packages/utils/src/random.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 文件定义:随机数生成
|
||||
*/
|
||||
|
||||
import * as R from 'ramda'
|
||||
|
||||
/* -------------- 1、随机数生成 -------------- */
|
||||
|
||||
/**
|
||||
* 生成指定范围内的随机整数
|
||||
* @param {number} min - 最小值
|
||||
* @param {number} max - 最大值
|
||||
* @returns {number} 随机整数
|
||||
*/
|
||||
export const randomInt = (min: number, max: number): number => {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成指定长度的随机字符串(默认32位,包括大小写字母和数字,去除0oO1Ii)
|
||||
* @param {number} length - 字符串长度
|
||||
* @param {object} options - 选项
|
||||
* @param {boolean} options.isSpecial - 是否包含特殊字符 (默认不包含)
|
||||
* @param {boolean} options.isLower - 是否包含小写字母(默认包含)
|
||||
* @param {boolean} options.isUpper - 是否包含大写字母(默认包含)
|
||||
* @param {boolean} options.isNumber - 是否包含数字(默认包含)
|
||||
* @returns {string} 随机字符串
|
||||
*/
|
||||
export const randomChart = (
|
||||
length: number = 32,
|
||||
options: { isSpecial?: boolean; isLower?: boolean; isUpper?: boolean; isNumber?: boolean } = {},
|
||||
): string => {
|
||||
const { isSpecial = false, isLower = true, isUpper = true, isNumber = true } = options
|
||||
let chars = ''
|
||||
if (isSpecial) chars += '!@#$%^&*?'
|
||||
if (isLower) chars += 'abcdefghijklmnopqrstuvwxyz'
|
||||
if (isUpper) chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
if (isNumber) chars += '0123456789'
|
||||
const result = Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('')
|
||||
console.log('result', result)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串,进阶版,支持包含字符最小长度,
|
||||
* @param {number} length - 字符串长度
|
||||
* @param {object} options - 选项
|
||||
* @param {number} options.minUpper - 大写字母最小长度(默认0)
|
||||
* @param {number} options.minLower - 小写字母最小长度(默认0)
|
||||
* @param {number} options.minNumber - 数字最小长度(默认0)
|
||||
* @param {number} options.minSpecial - 特殊字符最小长度(默认0)
|
||||
*/
|
||||
export const randomChartWithMinLength = (
|
||||
length: number = 32,
|
||||
options: { minUpper?: number; minLower?: number; minNumber?: number; minSpecial?: number } = {},
|
||||
): string => {
|
||||
const { minUpper = 1, minLower = 1, minNumber = 1, minSpecial = 0 } = options // 解构赋值,默认值为0
|
||||
let result = ''
|
||||
const upperChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
const lowerChars = 'abcdefghijklmnopqrstuvwxyz'
|
||||
const numberChars = '0123456789'
|
||||
const specialChars = '!@#$%^&*?'
|
||||
|
||||
// 计算已确定的最小字符数
|
||||
const minTotal = minUpper + minLower + minNumber + minSpecial
|
||||
if (minTotal > length) {
|
||||
throw new Error('最小长度要求总和超过了指定的总长度')
|
||||
}
|
||||
|
||||
// 生成必需的字符
|
||||
const getRandomChars = (chars: string, count: number): string => {
|
||||
return Array.from({ length: count }, () => chars[Math.floor(Math.random() * chars.length)]).join('')
|
||||
}
|
||||
|
||||
// 添加必需的字符
|
||||
if (minUpper > 0) result += getRandomChars(upperChars, minUpper)
|
||||
if (minLower > 0) result += getRandomChars(lowerChars, minLower)
|
||||
if (minNumber > 0) result += getRandomChars(numberChars, minNumber)
|
||||
if (minSpecial > 0) result += getRandomChars(specialChars, minSpecial)
|
||||
|
||||
// 计算剩余需要填充的长度
|
||||
const remainingLength = length - minTotal
|
||||
|
||||
// 创建可用字符集合
|
||||
let availableChars = ''
|
||||
if (minUpper >= 0) availableChars += upperChars
|
||||
if (minLower >= 0) availableChars += lowerChars
|
||||
if (minNumber >= 0) availableChars += numberChars
|
||||
if (minSpecial >= 0) availableChars += specialChars
|
||||
|
||||
// 填充剩余长度
|
||||
result += getRandomChars(availableChars, remainingLength)
|
||||
|
||||
// 打乱最终结果
|
||||
return result
|
||||
.split('')
|
||||
.sort(() => Math.random() - 0.5)
|
||||
.join('')
|
||||
}
|
||||
113
frontend/packages/utils/src/string.ts
Normal file
113
frontend/packages/utils/src/string.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 文件定义:字符串处理
|
||||
*/
|
||||
|
||||
import * as R from 'ramda'
|
||||
|
||||
/* -------------- 1、字符串处理 -------------- */
|
||||
|
||||
/**
|
||||
* url字符串转换为对象
|
||||
* @param {string} url - 要转换的url字符串
|
||||
* @returns {Record<string, string>} 转换后的对象
|
||||
*/
|
||||
export const urlToObject = (url: string): Record<string, string> => {
|
||||
const urlObj = new URL(url)
|
||||
return Object.fromEntries(urlObj.searchParams.entries())
|
||||
}
|
||||
|
||||
/**
|
||||
* 柯里化版本的urlToObject
|
||||
* @param {string} url - 要转换的url字符串
|
||||
* @returns {Record<string, string>} 转换后的对象
|
||||
*/
|
||||
export const urlToObjectCurried: {
|
||||
(url: string): Record<string, string>
|
||||
(url: string): (url: string) => Record<string, string>
|
||||
} = R.curry(urlToObject)
|
||||
|
||||
/**
|
||||
* html转义,支持反转义
|
||||
* @param {string} str - 要转义的html字符串
|
||||
* @param {boolean} isReverse - 是否反转义
|
||||
* @returns {string} 转义后的html字符串
|
||||
*/
|
||||
export const htmlEscape = (str: string, isReverse: boolean = false): string => {
|
||||
const escapeMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
}
|
||||
// 将escapeMap组合成正则表达式(反转义,将转义后的字符串转换为原始字符串)
|
||||
const repReg = isReverse ? R.invertObj(escapeMap) : R.map(R.identity, escapeMap)
|
||||
// 将repReg组合成正则表达式
|
||||
const repRegStr = Object.keys(repReg).join('|')
|
||||
// 使用正则表达式替换
|
||||
return str.replace(new RegExp(repRegStr, 'g'), (match: string) => {
|
||||
return repReg[match as keyof typeof repReg]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 小驼峰转下划线
|
||||
* @param {string} str - 要转换的驼峰字符串
|
||||
* @returns {string} 转换后的下划线字符串
|
||||
*/
|
||||
export const camelToUnderline = (str: string): string => {
|
||||
return str.replace(/([A-Z])/g, '_$1').toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* 下划线转小驼峰
|
||||
* @param {string} str - 要转换的下划线字符串
|
||||
* @returns {string} 转换后的驼峰字符串
|
||||
*/
|
||||
export const underlineToCamel = (str: string): string => {
|
||||
return str.replace(/_([a-z])/g, (_, char: string) => {
|
||||
return char.toUpperCase()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 下划线转大驼峰
|
||||
* @param {string} str - 要转换的下划线字符串
|
||||
* @returns {string} 转换后的驼峰字符串
|
||||
*/
|
||||
export const underlineToBigCamel = (str: string): string => {
|
||||
return str.replace(/_([a-z])/g, (_, char: string) => {
|
||||
return char.toUpperCase()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 大驼峰转下划线
|
||||
* @param {string} str - 要转换的驼峰字符串
|
||||
* @returns {string} 转换后的下划线字符串
|
||||
*/
|
||||
export const bigCamelToUnderline = (str: string): string => {
|
||||
return str.replace(/([A-Z])/g, '_$1').toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 驼峰转短横线
|
||||
* @param {string} str - 要转换的驼峰字符串
|
||||
* @returns {string} 转换后的短横线字符串
|
||||
*/
|
||||
export const kebabCase = (str: string): string => {
|
||||
return bigCamelToSmallCamel(str)
|
||||
.replace(/([A-Z])/g, '-$1')
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 大驼峰转小驼峰
|
||||
* @param {string} str - 要转换的短横线字符串
|
||||
* @returns {string} 转换后的驼峰字符串
|
||||
*/
|
||||
export const bigCamelToSmallCamel = (str: string): string => {
|
||||
return str.replace(/^([A-Z])/, (_, char: string) => {
|
||||
return char.toLowerCase()
|
||||
})
|
||||
}
|
||||
108
frontend/packages/utils/src/type.ts
Normal file
108
frontend/packages/utils/src/type.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 文件定义:数据类型检查
|
||||
*/
|
||||
|
||||
import * as R from 'ramda'
|
||||
|
||||
// =============== 1. 数据类型检查 ===============
|
||||
|
||||
/**
|
||||
* 检查值是否为数字
|
||||
* @param {any} value - 要检查的值
|
||||
* @returns {boolean} 如果值是数字,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isNumber = R.is(Number) as (value: unknown) => value is number
|
||||
|
||||
/**
|
||||
* 检查值是否为字符串
|
||||
* @param {any} value - 要检查的值
|
||||
* @returns {boolean} 如果值是字符串,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isString = R.is(String) as (value: unknown) => value is string
|
||||
|
||||
/**
|
||||
* 检查值是否为对象
|
||||
* @param {any} value - 要检查的值
|
||||
* @returns {boolean} 如果值是对象,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isObject = R.both(R.is(Object), R.complement(R.is(Array))) as (value: unknown) => value is object
|
||||
|
||||
/**
|
||||
* 检查是否为布尔值
|
||||
* @param {any} value - 要检查的值
|
||||
* @returns {boolean} 如果值是布尔值,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isBoolean = R.is(Boolean) as (value: unknown) => value is boolean
|
||||
|
||||
/**
|
||||
* 检查值是否为数组
|
||||
* @param {any} value - 要检查的值
|
||||
* @returns {boolean} 如果值是数组,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isArray = R.is(Array) as (value: unknown) => value is any[]
|
||||
|
||||
/**
|
||||
* 检查是否为Porime函数
|
||||
* @param {any} value - 要检查的值
|
||||
* @returns {boolean} 如果值是Porime函数,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isPromise = R.is(Promise) as (value: unknown) => value is Promise<unknown>
|
||||
|
||||
/**
|
||||
* 检查是否为函数
|
||||
* @param {any} value - 要检查的值
|
||||
* @returns {boolean} 如果值是函数,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isFunction = R.is(Function) as (value: unknown) => value is Function
|
||||
|
||||
/**
|
||||
* 检查是否为正则表达式
|
||||
* @param {any} value - 要检查的值
|
||||
* @returns {boolean} 如果值是正则表达式,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isRegExp = R.is(RegExp) as (value: unknown) => value is RegExp
|
||||
|
||||
/**
|
||||
* 检查是否为日期
|
||||
* @param {any} value - 要检查的值
|
||||
* @returns {boolean} 如果值是日期,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isDate = R.is(Date) as unknown as (value: unknown) => value is Date
|
||||
|
||||
/**
|
||||
* 检查是否为null(和undefined区分)
|
||||
* @param {any} value - 要检查的值
|
||||
* @returns {boolean} 如果值是null,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isNull = R.isNil as (value: unknown) => value is null
|
||||
|
||||
/**
|
||||
* 检查是否为undefined
|
||||
* @param {any} value - 要检查的值
|
||||
* @returns {boolean} 如果值是undefined,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isUndefined = R.isNil as (value: unknown) => value is undefined
|
||||
|
||||
/**
|
||||
* 检查值是否为空('', [], {}),排除null和undefined
|
||||
* @param {any} value - 要检查的值
|
||||
* @returns {boolean} 如果值是空,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isEmpty = R.both(R.complement(R.isNil), R.isEmpty) as (value: unknown) => value is '' | any[] | object
|
||||
|
||||
/* 获取值的类型
|
||||
* @param {any} value - 要获取类型的值
|
||||
* @returns {string} 值的类型
|
||||
*/
|
||||
export const getType = R.type as (value: unknown) => string
|
||||
|
||||
/**
|
||||
* 检查值是否为指定类型
|
||||
* @param {string} type - 要检查的类型
|
||||
* @param {any} value - 要检查的值
|
||||
* @returns {boolean} 如果值是指定类型,则返回 true,否则返回 false
|
||||
*/
|
||||
export const isType = R.curry((type: string, value: unknown) => R.equals(getType(value), type)) as <T>(
|
||||
type: string,
|
||||
value: unknown,
|
||||
) => value is T
|
||||
Reference in New Issue
Block a user