【同步】前端项目源码

【修复】工作流兼容问题
This commit is contained in:
chudong
2025-05-10 11:53:11 +08:00
parent c514471adc
commit f1a75afaba
584 changed files with 55714 additions and 110 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
}
}
}