/**
 * IndexedDB
 */
const IndexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;

/**
 * 数据库结构
 * 对象空间
 */
const IDB_STRUCTURE = [
    // 上传任务
    {
        IDB_STORE_NAME: "表名",
        IDB_STORE_OPTIONS: { keyPath: '主键名', autoIncrement: true },
        IDB_STORE_INDEX: [
            // 主键 (唯一)
            { name: "主键名", prop: "字段属性", options: { unique: true } },
            // 字段名称 
            { name: "字段名称", prop: "字段属性", options: { unique: false } },
        ]
    }
];

/**
 * IDBInstence
 * @class IDBInstence
 * @description IDBInstence
 */
class IDBInstence {
    /**
     * 构造函数
     */
    constructor(IDB_NAME = null) {
        /**
         * 数据库名称
         */
        this.IDB_NAME = IDB_NAME || null;
        /**
         * 数据库版本
         */
        this.IDB_VERSION = null;
        /**
         * 数据库实例
         */
        this.IDB_DATABASE = null;
        /**
        * 是否就绪
        */
        this.IDB_READY = false;
    }

    /**
     * 初始化数据库
     * @param {string} IDB_NAME 
     */
    initialization(IDB_NAME = null) {
        return new Promise((resolve, reject) => {

            // 数据库名称
            if (!!IDB_NAME) this.IDB_NAME = IDB_NAME;

            // 是否支持indexedDB
            if (!!IndexedDB === false) return reject("IndexedDB is not supported.");

            // 是否已经开启
            // if (this.IDB_READY) return resolve();
            if (this.IDB_READY) {
                this.IDB_READY = false;
                this.IDB_DATABASE?.close();
            }

            // 打开数据库
            let request = IndexedDB.open(this.IDB_NAME, this.IDB_VERSION || undefined);

            // 打开成功
            request.onsuccess = events => {
                // 数据库对象
                this.IDB_DATABASE = events.target.result;

                // 检查是否有不存在的对象空间
                if (IDB_STRUCTURE.findIndex(element => !!this.IDB_DATABASE.objectStoreNames.contains(element.IDB_STORE_NAME) === false) !== -1) {
                    // 数据库版本升级
                    this.IDB_VERSION = this.IDB_DATABASE.version + 1;
                    // 关闭当前连接
                    this.IDB_DATABASE.close();
                    // 置空对象
                    this.IDB_DATABASE = null;
                    // 回调连接并创建对象空间
                    this.initialization(IDB_NAME).then(resolve).catch(reject);
                } else {
                    this.IDB_READY = true;
                    resolve();
                }
            };

            // 打开失败
            request.onerror = events => (this.IDB_DATABASE = events.target, reject(events.target));

            // 创建对象仓库
            request.onupgradeneeded = events => {
                // 遍历创建
                for (let index = 0; index < IDB_STRUCTURE.length; index++) {
                    // 当前对象
                    let element = IDB_STRUCTURE[index];
                    // 检查对象空间是否存在
                    if (!!events.currentTarget.result.objectStoreNames.contains(element.IDB_STORE_NAME)) continue;
                    // 创建并创建主键(idbid)自增
                    let store = events.currentTarget.result.createObjectStore(element.IDB_STORE_NAME, element.IDB_STORE_OPTIONS);
                    // 创建字段索引
                    element.IDB_STORE_INDEX.forEach(itemIndex => store.createIndex(itemIndex.name, itemIndex.prop, itemIndex.options));
                }
            };
        });
    }

    /**
     * 获取表格结构
     * @param {*} IDB_STORE_NAME 表名
     */
    obtainTableStructure(IDB_STORE_NAME) {
        return IDB_STRUCTURE[IDB_STRUCTURE.findIndex(table => table.IDB_STORE_NAME === IDB_STORE_NAME)];
    }
}

/**
 * IDBInstence
 */
const idbInstence = new IDBInstence();



/**
 * IDBKeyRange
 */
const IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;

/**
 * 数据库结构
 * 对象空间
 */
class TableInstence {
    /**
     * 构造函数
     */
    constructor(IDB_STORE_NAME = null) {
        /**
         * 对象仓库名称
         */
        this.IDB_STORE_NAME = IDB_STORE_NAME || null;

        /**
         * 是否就绪
         */
        this.IDB_READY = idbInstence.IDB_READY;
    }

    /**
     * 创建服务
     * @param model 模式
     */
    createService(model = "readonly") {
        return idbInstence.IDB_DATABASE.transaction(this.IDB_STORE_NAME, model).objectStore(this.IDB_STORE_NAME);
    }

    /**
     * 插入一条数据
     * @param {Object} data 
     * @returns 
     */
    insert(data = {}) {
        return new Promise((resolve, reject) => {
            // 创建事务
            let request = this.createService('readwrite').add(data);
            // 插入成功
            request.onsuccess = events => resolve(events.target.result);
            // 插入失败
            request.onerror = () => reject(request.error);
        });
    }

    /**
     * 根据主键的值查询数据
     * @param {*} idbid 主键的值
     * @returns 
     */
    find(idbid = 0) {
        return new Promise((resolve, reject) => {
            // 创建事务
            let request = this.createService('readonly').get(idbid);
            // 查询成功
            request.onsuccess = () => !!request.result ? resolve(request.result) : reject(`No data with a primary key of "${idbid}" was found in the "${this.IDB_STORE_NAME}" table`);
            // 查询失败
            request.onerror = () => reject(request.error);
        });
    }

    /**
     * 根据索引的值查询数据
     * @param {*} index 索引
     * @param {*} value 索引的值
     * @returns 
     */
    findIndex(index = null, value = null) {
        return new Promise((resolve, reject) => {
            // 创建事务
            let request = this.createService('readonly').index(index).get(value);
            // 查询成功
            request.onsuccess = events => !!events.target.result ? resolve(events.target.result) : reject(`Data with an index(${index}) value of "${value}" was not found in the "${this.IDB_STORE_NAME}" table`);
            // 查询失败
            request.onerror = () => reject(request.error);
        });
    }

    /**
     * 查询全部数据
     * @returns 
     */
    findAll() {
        return new Promise((resolve, reject) => {
            // 创建事务
            let request = this.createService('readonly').getAll();
            // 查询成功
            request.onsuccess = events => resolve(events.target.result);
            // 查询失败
            request.onerror = () => reject(request.error);
        });
    }

    /**
     * 分页查询数据
     * @param {Object} pagination { current: 1, size: 10 }
     * @returns 
     */
    findPagination(pagination = { current: 1, size: 10 }) {
        return new Promise((resolve, reject) => {
            // 校验页码
            if (typeof pagination?.current !== "number") return reject(`Your page parameter "current" is not a valid page number`);
            // 校验一页大小
            if (typeof pagination?.size !== "number") return reject(`Your page parameter "size" is not a valid page size number`);

            // 创建事务
            let request = this.createService().openCursor();

            // 获取总条数
            let countObject = this.createService().count();

            // 响应数据
            let response = {
                // 当前页
                current: paging.current,
                // 一页大小
                size: paging.size,
                // 总条数
                total: 0,
                // 数据列表
                list: []
            };

            // 是否已经回调
            let isComplete = false;
            // 完成
            let complete = () => {
                // 请求是否完成
                if (isComplete || request.readyState !== "done" || countObject.readyState !== "done") return;
                isComplete = true;
                resolve(response);
            }

            // 条数查询完毕
            countObject.onsuccess = events => (response.total = events.target.result, complete());

            // 是否需要跳转
            let advance = response.current > 1;

            // 查询成功
            request.onsuccess = events => {
                // 请求数据
                let result = events.target.result;
                // 是否有数据
                if (!!result) {
                    // 定位到指定位置
                    if (!!advance) return (advance = false, result.advance((response.current - 1) * response.size))
                    // 是否是最后一条数据
                    if (response.list.length === response.size) return complete();
                    // 添加到响应
                    response.list.push(result.value);
                    // 下一条
                    result.continue();
                } else complete();
            };

            // 查询失败
            request.onerror = () => reject(request.error);
        });
    }

    /**
     * 根据条件获取数据
     * @param {Object} parameters { current: 页码, size: 页大小, index: "索引", value: "索引值", filters: row => true }
     * @returns 
     */
    obtainFilters(parameters = { current: 1, size: 10, index: null, value: null, filters: row => true }) {
        return new Promise((resolve, reject) => {
            let options = { current: 1, size: 10, index: null, value: null, filters: row => true };
            // 校验页码
            if (!!parameters?.current && isFinite(parameters?.current)) options.current = parameters.current;
            else return reject(`Your page parameter "current" is not a valid page number`);
            // 校验大小
            if (!!parameters?.size && isFinite(parameters?.size)) options.size = parameters.size;
            else return reject(`Your page parameter "size" is not a valid page size number`);
            // 校验索引
            if (parameters?.index !== null && parameters?.index !== undefined) options.index = `${parameters.index}`;
            // 校验索引值
            if (parameters?.value !== null && parameters?.value !== undefined) options.value = parameters.value;
            // 校验过滤方法
            if (typeof parameters?.filters === "function") options.filters = parameters.filters;

            // 创建事务
            let request = null;
            if (!!options.index) request = this.createService().index(options.index).openCursor(IDBKeyRange.only(options.value));
            else request = this.createService().openCursor();

            // 响应数据
            let response = {
                // 当前页
                current: options.current,
                // 一页大小
                size: options.size,
                // 总条数
                total: 0,
                // 数据列表
                list: []
            };

            // 开始下标
            let previous = (options.current - 1) * options.size;
            // 结束下标
            let behind = previous + options.size - 1;

            // 查询成功
            request.onsuccess = events => {
                // 请求数据
                let result = events.target.result;
                // 是否有数据
                if (!!result) {
                    // 是否满足条件
                    if (options.filters(result.value)) {
                        // 添加到响应
                        if (response.total >= previous && response.total <= behind) response.list.push(result.value);
                        // 增加条数
                        response.total += 1;
                    }
                    // 下一条
                    result.continue();
                } else resolve(response);
            };

            // 查询失败
            request.onerror = () => reject(request.error);
        });
    }

    /**
     * 根据主键的值删除数据
     * @param {*} idbid 主键的值
     * @returns 
     */
    delete(idbid = 0) {
        return new Promise((resolve, reject) => {
            // 创建事务
            let request = this.createService('readwrite').delete(idbid);
            // 查询成功
            request.onsuccess = () => resolve(request.result);
            // 查询失败
            request.onerror = () => reject(request.error);
        });
    }

    /**
     * 根据索引的值删除数据
     * @param {*} index 索引
     * @param {*} value 索引的值
     * @returns 
     */
    deleteIndex(index = null, value = null) {
        return new Promise((resolve, reject) => this.findIndex(index, value).then(result => this.delete(result[idbInstence.obtainTableStructure(this.IDB_STORE_NAME)?.IDB_STORE_OPTIONS.keyPath])).then(resolve).catch(reject));
    }

    /**
     * 根据主键更新数据
     * @param {*} idbid 主键 
     * @param {*} data 更新的值
     * @returns 
     */
    update(idbid = 0, data = {}) {
        return new Promise((resolve, reject) => this.find(idbid).then(result => {
            // 新数据
            let newData = JSON.parse(JSON.stringify(Object.assign(result, data)));
            // 创建事务
            let request = this.createService("readwrite").put(newData);
            // 查询成功
            request.onsuccess = () => resolve(newData);
            // 查询失败
            request.onerror = () => reject(request.error);
        }).catch(reject));
    }

    /**
     * 根据索引的值更新数据
     * @param {*} index 主键 
     * @param {*} value 索引的值 
     * @param {*} data 更新的值
     * @returns 
     */
    updateIndex(index = null, value = null, data = {}) {
        return new Promise((resolve, reject) => this.findIndex(index, value).then(result => {
            // 新数据
            let newData = JSON.parse(JSON.stringify(Object.assign(result, data)));
            // 创建事务
            let request = this.createService("readwrite").put(newData);
            // 查询成功
            request.onsuccess = () => resolve(newData);
            // 查询失败
            request.onerror = () => reject(request.error);
        }).catch(reject));
    }
}

/**
 * 单例实例
 */
const tableInstance = new TableInstence();

/**
 * 单例模式
 * @returns
 */
const IDBTable = IDB_STORE_NAME => {
    tableInstance = new Proxy(new TableInstence(IDB_STORE_NAME), {
        /**
         * get
         * @param {*} target 
         * @param {*} prop 
         * @param {*} receiver 
         * @returns 
         */
        get(target, prop, receiver) {
            // 拦截方法调用
            if (typeof target[prop] === 'function') return function (...args) {
                // 调用之前判断数据库是否就绪
                if (!!idbInstence.IDB_READY === false) return Promise.reject("idbInstence is not ready.");
                const result = target[prop].apply(target, args);
                // 调用之后 
                // ()=>{}
                return result;
            };
            // 如果不是方法调用,则直接返回原始属性
            else return Reflect.get(target, prop, receiver);
        }
    });
    return tableInstance;
};



文章作者: CaptainTwo
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 CaptainTwo
JavaScript JavaScript
喜欢就支持一下吧