最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • ES(四)用Promise封装一下IndexedDB

    正文概述 掘金(金色海洋)   2021-02-04   528

    indexedDB

    IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据,它可以被网页脚本创建和操作。 IndexedDB 允许储存大量数据,提供查找接口,还能建立索引,这些都是 LocalStorage 所不具备的。 就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。 其他的介绍就不搬运了,大家可以自行百度,后面有参考资料。

    需求

    我想更好的实现文档驱动的想法,发现需要实现前端存储的功能,于是打算采用 IndexedDB 来实现前端存储的功能。但是看了一下其操作方式比较繁琐,所以打算封装一下。

    官网给了几个第三方的封装库,我也点过去看了看,结果没看懂。想了想还是自己动手丰衣足食吧。

    关于重复制造轮子的想法:

    • 首先要有制造轮子能能力。
    • 自己造的轮子,操控性更好。

    功能设计

    按照官网的功能介绍,把功能整理了一下: 如图: ES(四)用Promise封装一下IndexedDB

    就是建库、增删改查那一套。看到有些第三方的封装库,可以实现支持sql语句方式的查询,真的很厉害。目前没有这种需求,好吧,能力有限实现不了。 总之,先满足自己的需求,以后在慢慢改进。

    代码实现

    还是简单粗暴,直接上代码吧,基础知识的介绍,网上有很多了,可以看后面的参考资料。官网介绍的也比较详细,还有中文版的。

    配置文件

    nf-indexedDB.config

    const config = {
      dbName: 'dbTest',
      ver: 1,
      debug: true,
      objectStores: [ // 建库依据
        {
          objectStoreName: 'blog',
          index: [ // 索引 , unique 是否可以重复
            { name: 'groupId', unique: false }
          ]
        }
      ],
      objects: { // 初始化数据
        blog: [
          {
            id: 1,
            groupId: 1,
            title: '这是一个博客',
            addTime: '2020-10-15',
            introduction: '这是博客简介',
            concent: '这是博客的详细内容<br>第二行',
            viewCount: 1,
            agreeCount: 1
          },
          {
            id: 2,
            groupId: 2,
            title: '这是两个博客',
            addTime: '2020-10-15',
            introduction: '这是博客简介',
            concent: '这是博客的详细内容<br>第二行',
            viewCount: 10,
            agreeCount: 10
          }
        ]
      }
    }
    
    export default config
    
    • dbName

    指定数据库名称

    • ver

    指定数据库版本

    • debug

    指定是否要打印状态

    • objectStores

    对象仓库的描述,库名、索引等。

    • objects

    初始化数据,如果建库后需要添加默认数据的话,可以在这里设置。

    这里的设置不太完善,有些小问题现在还没想好解决方法。以后想好了再改。

    内部成员

     /**
       * IndexedDB 数据库对象
       * 判断浏览器是否支持
       * */
      const myIndexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB
      if (!myIndexedDB) {
        console.log('你的浏览器不支持IndexedDB')
      }
    
      let _db // 内部保存的 indexed 数据库 的实例
    
      /**
      * 把vue的ref、reactive转换成原始对象
      */
      const _vueToObject = (vueObject) => {
        let _object = vueObject
        // 针对Vue3做的类型判断
        if (Vue.isRef(_object)) {
          // 如果是 vue 的 ref 类型,替换成 ref.value
          _object = _object.value
        }
        if (Vue.isReactive(_object)) {
          // 如果是 vue 的 reactive 类型,那么获取原型,否则会报错
          _object = Vue.toRaw(_object)
        }
        return _object
      }
    
    • myIndexedDB

    兼容浏览器的写法,适应不同的浏览器。

    • _db 内部的 IDBOpenDBRequest 用于检查是否打开数据库,以及数据库的相关操作。

    • _vueToObject

    这是一个兼容Vue的对象转换函数。vue的reactive直接存入的话会报错,需要获取原型才能存入,我又不想每次保存的时候都多一步操作,所以就写了这个转换函数。 如果非vue3环境,可以直接返回参数,不影响其他功能。

    建立对象库以及打开数据库

      // ======== 数据库操作 ================
    /**
      * 打开 indexedDB 数据库。
      * dbName:数据库名称;
      * version:数据库版本。
      * 可以不传值。
      */
      const dbOpen = (dbName, version) => {
        // 创建数据库,并且打开
        const name = config.dbName || dbName
        const ver = config.ver || version
        const dbRequest = myIndexedDB.open(name, ver)
        // 记录数据库版本是否变更
        let isChange = false
        /* 该域中的数据库myIndex */
        if (config.debug) {
          console.log('dbRequest - 打开indexedDb数据库:', dbRequest)
        }
        // 打开数据库的 promise
        const dbPromise = new Promise((resolve, reject) => {
          // 数据库打开成功的回调
          dbRequest.onsuccess = (event) => {
            // _db = event.target.result
            // 数据库成功打开后,记录数据库对象
            _db = dbRequest.result
            if (isChange) { // 如果变更,则设置初始数据
              setup().then(() => {
                resolve(_db)
              })
            } else {
              resolve(_db)
            }
          }
    
          dbRequest.onerror = (event) => {
            reject(event) // 返回参数
          }
        })
    
        // 创建表
        // 第一次打开成功后或者版本有变化自动执行以下事件,一般用于初始化数据库。
        dbRequest.onupgradeneeded = (event) => {
          isChange = true
          _db = event.target.result /* 数据库对象 */
          // 建立对象表
          for (let i = 0; i < config.objectStores.length; i++) {
            const object = config.objectStores[i]
            // 验证有没有,没有的话建立一个对象表
            if (!_db.objectStoreNames.contains(object.objectStoreName)) {
              const objectStore = _db.createObjectStore(object.objectStoreName, { keyPath: 'id' }) /* 创建person仓库(表) 主键 */
              // objectStore = _db.createObjectStore('person',{autoIncrement:true});/*自动创建主键*/
              // 建立索引
              for (let i = 0; i < object.index.length; i++) {
                const index = object.index[i]
                objectStore.createIndex(index.name, index.name, { unique: index.unique })
              }
              if (config.debug) {
                console.log('onupgradeneeded - 建立了一个新的对象仓库:', objectStore)
              }
            }
          }
        }
    
        // 返回 Promise 实例 —— 打开Indexed库
        return dbPromise
      }
    

    这段代码有点长,因为有两个功能,一个是打开数据库,一个是创建数据库。

    indexedDB 的逻辑是这样的,在open数据库的时候判断本地有没有数据库,如果没有数据库则触发 onupgradeneeded 事件,创建数据库,然后打开数据库。 如果有数据库的话,判断版本号,如果高于本地数据库,那么也会触发 onupgradeneeded 事件。所以open和 onupgradeneeded 就联系在了一起。

    初始化对象

      /**
      * 设置初始数据
      */
      const setup = () => {
        // 定义一个 Promise 的实例
        const objectPromise = new Promise((resolve, reject) => {
          const arrStore = []
          // 遍历,获取表名集合,便于打开事务
          for (const key in config.objects) {
            arrStore.push(key)
          }
          const tranRequest = _db.transaction(arrStore, 'readwrite')
    
          // 遍历,添加数据(对象)
          for (const key in config.objects) {
            const objectArror = config.objects[key]
            const store = tranRequest.objectStore(key)
            // 清空数据
            store.clear().onsuccess = (event) => {
              // 遍历添加数据
              for (let i = 0; i < objectArror.length; i++) {
                store
                  .add(objectArror[i])
                  .onsuccess = (event) => {
                    if (config.debug) {
                      console.log(`添加成功!key:${key}-i:${i}`)
                    }
                  }
              }
            }
          }
    
          // 遍历后统一返回
          tranRequest.oncomplete = (event) => {
            // tranRequest.commit()
            if (config.debug) {
              console.log('setup - oncomplete')
            }
            resolve()
          }
          tranRequest.onerror = (event) => {
            reject(event)
          }
        })
        return objectPromise
      }
    
    

    有的时候需要在建库之后设置一些初始化的数据,于是设计了这个函数。 setup会依据 nf-indexedDB.config 里的配置,把默认对象添加到数据库里面。

    添加对象

    基础的增删改查系列,不管是数据库还是对象库,都躲不开。

    // ======== 增删改操作 ===================================
      /**
      * 添加对象。
      * storeName:对象仓库名;
      * object:要添加的对象
      */
      const addObject = (storeName, object) => {
        const _object = _vueToObject(object)
        // 定义一个 Promise 的实例
        const objectPromise = new Promise((resolve, reject) => {
          // 定义个函数,便于调用
          const _addObject = () => {
            const tranRequest = _db.transaction(storeName, 'readwrite')
            tranRequest
              .objectStore(storeName) // 获取store
              .add(_object) // 添加对象
              .onsuccess = (event) => { // 成功后的回调
                resolve(event.target.result) // 返回对象的ID
              }
            tranRequest.onerror = (event) => {
              reject(event)
            }
          }
    
          // 判断数据库是否打开
          if (typeof _db === 'undefined') {
            dbOpen().then(() => {
              _addObject()
            })
          } else {
            _addObject()
          }
        })
        return objectPromise
      }
    

    这么长的代码,只是实现了把一个对象填到数据库里的操作,可见原本的操作是多么的繁琐。

    好吧,不开玩笑了,其实原本的想法是这样的,想要添加对象要这么写:

    dbOpen().then(() =>{
      addObject('blog',{
        id: 3,
        groupId: 1,
        title: '这是三个博客',
        addTime: '2020-10-15',
        introduction: '这是博客简介',
        concent: '这是博客的详细内容<br>第二行',
        viewCount: 1,
        agreeCount: 1
      })
    })
    

    就是说,每次操作的时候先开库,然后才能进行操作,但是想想这么做是不是有点麻烦? 能不能不管开不开库的,直接开鲁呢? 于是内部实现代码就变得复杂了一点。

    修改对象

      /**
      * 修改对象。
      * storeName:对象仓库名;
      * object:要修改的对象
      */
      const updateObject = (storeName, object) => {
        const _object = _vueToObject(object)
        // 定义一个 Promise 的实例
        const objectPromise = new Promise((resolve, reject) => {
          // 定义个函数,便于调用
          const _updateObject = () => {
            const tranRequest = _db.transaction(storeName, 'readwrite')
            // 按照id获取对象
            tranRequest
              .objectStore(storeName) // 获取store
              .get(_object.id) // 获取对象
              .onsuccess = (event) => { // 成功后的回调
                // 从仓库里提取对象,把修改值合并到对象里面。
                const newObject = { ...event.target.result, ..._object }
                // 修改数据
                tranRequest
                  .objectStore(storeName) // 获取store
                  .put(newObject) // 修改对象
                  .onsuccess = (event) => { // 成功后的回调
                    if (config.debug) {
                      console.log('updateObject -- onsuccess- event:', event)
                    }
                    resolve(event.target.result)
                  }
              }
    
            tranRequest.onerror = (event) => {
              reject(event)
            }
          }
          // 判断数据库是否打开
          if (typeof _db === 'undefined') {
            dbOpen().then(() => {
              _updateObject()
            })
          } else {
            _updateObject()
          }
        })
        return objectPromise
      }
    
    

    修改对象,是新的对象覆盖掉原来的对象,一开始是想直接put,但是后来实践的时候发现,可能修改的时候只是修改其中的一部分属性,而不是全部属性,那么直接覆盖的话,岂不是造成参数不全的事情了吗?

    于是只好先把对象拿出来,然后和新对象合并一下,然后再put回去,于是代码就又变得这么长了。

    删除对象

     /**
      * 依据id删除对象。
      * storeName:对象仓库名;
      * id:要删除的对象的key值,注意类型要准确。
      */
      const deleteObject = (storeName, id) => {
        // 定义一个 Promise 的实例
        const objectPromise = new Promise((resolve, reject) => {
          // 定义个函数,便于调用
          const _deleteObject = () => {
            const tranRequest = _db.transaction(storeName, 'readwrite')
            tranRequest
              .objectStore(storeName) // 获取store
              .delete(id) // 删除一个对象
              .onsuccess = (event) => { // 成功后的回调
                resolve(event.target.result)
              }
            tranRequest.onerror = (event) => {
              reject(event)
            }
          }
          // 判断数据库是否打开
          if (typeof _db === 'undefined') {
            dbOpen().then(() => {
              _deleteObject()
            })
          } else {
            _deleteObject()
          }
        })
        return objectPromise
      }
    
    

    其实吧删除对象,一个 delete 就可以了,但是还是要先判断一下是否打开数据库,于是代码还是短不了。

    清空仓库里的对象

     /**
      * 清空store里的所有对象。
      * storeName:对象仓库名;
      */
      const clearStore = (storeName) => {
        // 定义一个 Promise 的实例
        const objectPromise = new Promise((resolve, reject) => {
          // 定义个函数,便于调用
          const _clearStore = () => {
            const tranRequest = _db.transaction(storeName, 'readwrite')
            tranRequest
              .objectStore(storeName) // 获取store
              .clear() // 清空对象仓库里的对象
              .onsuccess = (event) => { // 成功后的回调
                resolve(event)
              }
            tranRequest.onerror = (event) => {
              reject(event)
            }
          }
          // 判断数据库是否打开
          if (typeof _db === 'undefined') {
            dbOpen().then(() => {
              _clearStore()
            })
          } else {
            _clearStore()
          }
        })
        return objectPromise
      }
    
    • clear()

    清空指定对象仓库里的所有对象,请谨慎操作。

    删除对象仓库

      /**
      * 删除整个store。
      * storeName:对象仓库名;
      */
      const deleteStore = (storeName) => {
        // 定义一个 Promise 的实例
        const objectPromise = new Promise((resolve, reject) => {
          // 定义个函数,便于调用
          const _deleteStore = () => {
            const tranRequest = _db.transaction(storeName, 'readwrite')
            tranRequest
              .objectStore(storeName) // 获取store
              .delete() // 清空对象仓库里的对象
              .onsuccess = (event) => { // 成功后的回调
                resolve(event)
              }
            tranRequest.onerror = (event) => {
              reject(event) // 失败后的回调
            }
          }
          // 判断数据库是否打开
          if (typeof _db === 'undefined') {
            dbOpen().then(() => {
              _deleteStore()
            })
          } else {
            _deleteStore()
          }
        })
        return objectPromise
      }
    

    这个就更厉害了,可以把对象仓库给删掉。更要谨慎。

    删除数据库

      /**
      * 删除数据库。
      * dbName:数据库名;
      */
      const deleteDB = (dbName) => {
        // 定义一个 Promise 的实例
        const objectPromise = new Promise((resolve, reject) => {
          // 删掉整个数据库
          myIndexedDB.deleteDatabase(dbName).onsuccess = (event) => {
            resolve(event)
          }
        })
        return objectPromise
      }
    

    能建立数据库,那么就应该能删除数据库,这个就是。 这个就非常简单了,不用判断是否打开数据库,直接删除就好。 不过前端数据库应该具备这样的功能:整个库删掉后,可以自动恢复状态才行。

    按主键获取对象,或者获取全部

      /**
      * 获取对象。
      * storeName:对象仓库名;
      * id:要获取的对象的key值,注意类型要准确,只能取一个。
      * 如果不设置id,会返回store里的全部对象
      */
      const getObject = (storeName, id) => {
        const objectPromise = new Promise((resolve, reject) => {
          const _getObject = () => {
            const tranRequest = _db.transaction(storeName, 'readonly')
            const store = tranRequest.objectStore(storeName) // 获取store
            let dbRequest
            // 判断是获取一个,还是获取全部
            if (typeof id === 'undefined') {
              dbRequest = store.getAll()
            } else {
              dbRequest = store.get(id)
            }
    
            dbRequest.onsuccess = (event) => { // 成功后的回调
              if (config.debug) {
                console.log('getObject -- onsuccess- event:', id, event)
              }
              resolve(event.target.result) // 返回对象
            }
        
            tranRequest.onerror = (event) => {
              reject(event)
            }
          }
          // 判断数据库是否打开
          if (typeof _db === 'undefined') {
            dbOpen().then(() => {
              _getObject()
            })
          } else {
            _getObject()
          }
        })
    
        return objectPromise
      }
    

    这里有两个功能

    • 依据ID获取对应的对象
    • 获取对象仓库里的所有对象

    不想取两个函数名,于是就依据参数来区分了,传递ID就获取ID的对象,没有传递ID就返回全部。

    查询对象仓库

      /**
      * 依据 索引+游标,获取对象,可以获取多条。
      * storeName:对象仓库名。
      * page:{
      *   start:开始,
      *   count:数量,
      *   description:'next' 
      *   // next 升序
      *   // prev 降序
      *   // nextunique 升序,只取一
      *   // prevunique 降序,只取一
      * }
      * findInfo = {
      *   indexName: 'groupId',
      *   indexKind: '=', // '>','>=','<','<=','between',
      *   indexValue: 1,
      *   betweenInfo: {
      *     v1:1,
      *     v2:2,
      *     v1isClose:true,
      *     v2isClose:true,
      *   },
      *   where:(object) => {
      *     reutrn true/false
      *   }
      * }
      */
      const findObject = (storeName, findInfo = {}, page = {}) => {
        const _start = page.start || 0
        const _count = page.count || 0
        const _end = _start + _count
        const _description = page.description || 'prev' // 默认倒序
    
        // 查询条件,按照主键或者索引查询
        let keyRange = null
        if (typeof findInfo.indexName !== "undefined") {
          if (typeof findInfo.indexKind !== "undefined") {
            const id = findInfo.indexValue
            const dicRange = {
              "=":IDBKeyRange.only(id),
              ">":IDBKeyRange.lowerBound(id, true),
              ">=":IDBKeyRange.lowerBound(id),
              "<":IDBKeyRange.upperBound(id, true),
              "<=":IDBKeyRange.upperBound(id)
            }
            switch (findInfo.indexKind) {
              case '=':
              case '>':
              case '>=':
              case '<':
              case '<=':
                keyRange = dicRange[findInfo.indexKind]
                break
              case 'between':
                const betweenInfo = findInfo.betweenInfo
                keyRange = IDBKeyRange.bound(betweenInfo.v1,betweenInfo.v2,betweenInfo.v1isClose,betweenInfo.v2isClose)
                break
            }
          }
        }
        console.log('findObject - keyRange', keyRange)
    
        const objectPromise = new Promise((resolve, reject) => {
          // 定义个函数,便于调用
          const _findObjectByIndex = () => {
            const dataList = []
            let cursorIndex = 0
            const tranRequest = _db.transaction(storeName, 'readonly')
            const store = tranRequest.objectStore(storeName)
            let cursorRequest 
            // 判断是否索引查询
            if (typeof findInfo.indexName === "undefined") {
              cursorRequest = store.openCursor(keyRange, _description)
            } else {
              cursorRequest = store
                .index(findInfo.indexName)
                .openCursor(keyRange, _description)
            }
    
            cursorRequest.onsuccess = (event) => {
              const cursor = event.target.result
              if (cursor) {
                if (_end === 0 || (cursorIndex >= _start && cursorIndex < _end)) {
                  // 判断钩子函数
                  if (typeof findInfo.where === 'function') {
                    if (findInfo.where(cursor.value, cursorIndex)) {
                      dataList.push(cursor.value)
                      cursorIndex++
                    }
                  } else { // 没有设置查询条件
                    dataList.push(cursor.value)
                    cursorIndex++
                  }
                }
                cursor.continue()
              }
              // tranRequest.commit()
            }
    
            tranRequest.oncomplete = (event) => {
              if (config.debug) {
                console.log('findObjectByIndex - dataList', dataList)
              }
              resolve(dataList)
            }
            tranRequest.onerror = (event) => {
              console.log('findObjectByIndex - onerror', event)
              reject(event)
            }
          }
    
          // 判断数据库是否打开
          if (typeof _db === 'undefined') {
            dbOpen().then(() => {
              _findObjectByIndex()
            })
          } else {
            _findObjectByIndex()
          }
        })
        return objectPromise
      }
    
    

    打开指定的对象仓库,然后判断是否设置了索引查询,没有的话打开仓库的游标,如果设置了,打开索引的游标。 可以用钩子实现其他属性的查询。 可以分页获取数据,方法类似于mySQL的 limit。

    功能测试

    封装完毕,要写个测试代码来跑一跑,否则怎么知道到底好不好用呢。 于是写了一个比较简单的测试代码。

    建立对象库

    dbOpen().then(() =>{
        // 建表初始化之后,获取全部对象
        getAll()
    })
    
    • dbOpen

    打开数据库,同时判断是否需要建立数据库,如果需要的话,会根据配置信息自动建立数据库

    然后我们按F12,打开Application标签,可以找到我们建立的数据库,如图: ES(四)用Promise封装一下IndexedDB

    我们可以看一下索引的情况,如图: ES(四)用Promise封装一下IndexedDB

    添加对象

            addObject('blog',{
              id: new Date().valueOf(),
              groupId: 1,
              title: '这是三个博客',
              addTime: '2020-10-15',
              introduction: '这是博客简介',
              concent: '这是博客的详细内容<br>第二行',
              viewCount: 1,
              agreeCount: 1
            }).then((data) => {
              re.value = data
              getAll()
            })
    
    • 仓库名

    第一个参数是对象仓库的名称,目前暂时采用字符串的形式。

    • 对象

    第二个参数是要添加的对象,其属性必须有主键和索引,其他随意。

    • 返回值

    成功后会返回对象ID

    点右键可以刷新数据,如图: ES(四)用Promise封装一下IndexedDB

    更新后的数据,如图: ES(四)用Promise封装一下IndexedDB

    修改对象

            updateObject('blog',blog).then((data) => {
              re.value = data
              getAll()
            })
    
    • 仓库名

    第一个参数是对象仓库的名称,目前暂时采用字符串的形式。

    • 对象

    第二个参数是要修改的对象,属性可以不全。

    • 返回值

    成功后会返回对象ID

    删除对象

            deleteObject('blog',id).then((data) => {
              re.value = data
              getAll()
            })
    
    • 仓库名

    第一个参数是对象仓库的名称,目前暂时采用字符串的形式。

    • 对象

    第二个参数是要删除的对象的ID。

    • 返回值

    成功后会返回对象ID

    清空仓库里的对象

            clearStore('blog').then((data) => {
              re.value = data
              getAll()
            })
    
    • 仓库名

    第一个参数是对象仓库的名称,目前暂时采用字符串的形式。

    • 返回值

    成功后会返回对象ID

    删除对象仓库

            deleteStore('blog').then((data) => {
              re.value = data
              getAll()
            })
    
    • 仓库名

    第一个参数是对象仓库的名称,目前暂时采用字符串的形式。

    • 返回值

    成功后会返回对象ID

    删除数据库

             deleteDB('dbTest').then((data) => {
              re.value = data
              getAll()
            })
    
    • 数据库名称

    第一个参数是数据库的名称

    查询功能

           // 查询条件
          const findInfo = {
            indexName: 'groupId',
            indexKind: '=', // '>','>=','<','<=','between',
            indexValue: 1,
            betweenInfo: {
              v1:1,
              v2:2,
              v1isClose:true,
              v2isClose:true,
            },
            where: (object) => {
              if (findKey.value == '') return true
              let re = false
              if (object.title.indexOf(findKey.value) >= 0) {
                re = true
              }
              if (object.introduction.indexOf(findKey.value) >= 0) {
                re = true
              }
              if (object.concent.indexOf(findKey.value) >= 0) {
                re = true
              }
              return re
            }
          }
    
          const find = () => {
            findObject('blog', findInfo).then((data) => {
              findRe.value = data
            })
          }
    
    • findInfo

    查询信息的对象,把需要查询的信息都放在这里

    • indexName

    索引名称,可以不设置。

    • indexKind

    索引属性的查询方式,如果设置indexName,则必须设置。

    • indexValue

    索引字段的查询值

    • betweenInfo

    如果 indexKind = 'between' 的话,需要设置。

    • v1

    开始值

    • v2

    结束值

    • v1isClose

    是否闭合区间

    • v2isClose

    是否闭合区间

    • where

    钩子函数,可以不设置。 内部打开游标后,会把对象返回来,然后我们就可以在这里进行各种条件判断。

    全部代码就不贴了,感兴趣的话可以去GitHub看。 贴一个折叠后的效果图吧: ES(四)用Promise封装一下IndexedDB

    就是先把相关的功能和在一起,写一个操作类,然后在setup里面应用这个类就可以了,然后写点代码把各个类关联起来即可。

    这样代码好维护多了。

    源码

    github.com/naturefwvue…

    在线演示

    naturefwvue.github.io/nf-vue-cnd/…

    参考资料

    官网:https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API

    阮一峰的网络日志:http://www.ruanyifeng.com/blog/2018/07/indexeddb.html

    谦行: www.cnblogs.com/dolphinX/p/…


    起源地下载网 » ES(四)用Promise封装一下IndexedDB

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元