mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-10 00:31:10 +08:00
586 lines
16 KiB
TypeScript
586 lines
16 KiB
TypeScript
/**
|
||
* 文件定义:浏览器相关操作
|
||
*/
|
||
|
||
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
|
||
}
|
||
}
|
||
}
|