/** * 文件定义:浏览器相关操作 */ 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 返回处理结果 */ private handleRequest(request: IDBRequest): Promise { 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} 返回数据库连接实例 * @throws {Error} 连接失败时抛出错误 */ async connect(): Promise { 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} 返回新添加数据的主键 */ async add(storeName: string, data: T): Promise { const { store } = await this.getTransactionAndStore(storeName, 'readwrite') return this.handleRequest(store.add(data)) } /** * 更新数据 * @description 更新指定对象仓库中的数据,如果数据不存在则添加 * @template T 数据类型 * @param {string} storeName 仓库名称 * @param {T} data 要更新的数据 * @returns {Promise} 返回更新数据的主键 */ async put(storeName: string, data: T): Promise { const { store } = await this.getTransactionAndStore(storeName, 'readwrite') return this.handleRequest(store.put(data)) } /** * 删除数据 * @description 从指定对象仓库中删除数据 * @param {string} storeName 仓库名称 * @param {IDBValidKey} key 要删除数据的主键 * @returns {Promise} 删除成功时解析 */ async delete(storeName: string, key: IDBValidKey): Promise { const { store } = await this.getTransactionAndStore(storeName, 'readwrite') return this.handleRequest(store.delete(key)) } /** * 通过主键获取数据 * @description 从指定对象仓库中获取指定主键的数据 * @template T 返回数据类型 * @param {string} storeName 仓库名称 * @param {IDBValidKey} key 主键值 * @returns {Promise} 返回查询到的数据 */ async get(storeName: string, key: IDBValidKey): Promise { 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} 返回查询到的数据 */ async getByIndex(storeName: string, indexName: string, key: IDBValidKey): Promise { 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} 返回所有数据的数组 */ async getAll(storeName: string): Promise { const { store } = await this.getTransactionAndStore(storeName) return this.handleRequest(store.getAll()) } /** * 使用游标遍历数据 * @description 使用游标遍历对象仓库中的数据 * @template T 数据类型 * @param {string} storeName 仓库名称 * @param {(item: T) => void} callback 处理每条数据的回调函数 * @returns {Promise} */ async forEach(storeName: string, callback: (item: T) => void): Promise { 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} */ async addBatch(storeName: string, items: T[]): Promise { 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} */ async clear(storeName: string): Promise { const { store } = await this.getTransactionAndStore(storeName, 'readwrite') return this.handleRequest(store.clear()) } /** * 关闭数据库连接 * @description 安全地关闭数据库连接,释放资源 */ close(): void { if (this.db) { this.db.close() this.db = null } } }