【初始化】前端工程项目

This commit is contained in:
chudong
2025-05-09 15:11:21 +08:00
parent c012704c9a
commit d7c556c3b0
524 changed files with 55595 additions and 112 deletions

View 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
}
}
}

View 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)
}

View 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)
}

View 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}` : '已过期'
}

View 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
}
}

View 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('')
}

View 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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&apos;',
}
// 将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()
})
}

View 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