• 本地数据库IndexedDB - 学员管理系统之条件筛选(四)


            IndexedDB是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB允许存储大量数据,提供查找接口,还能建立索引。这些都是LocalStorage或Cookie不具备的。就数据库类型而言,IndexedDB不属于关系型数据库(不支持SQL查询语句),更接近NoSQL数据库。

            这篇将讲解下关于IndexedDB的条件筛选,通过内置功能函数,相对增加查询速度。在之前一篇中,是通过在utils.js工具包中定义分页函数,来实现分页的,这里我们将通过IndexedDB提供的相关函数来实现分页功能。

            效果如下图:

    一、通过utils工具类实现分页

    1.1 getAll()

            IDBObjectStore接口的getAll()方法返回一个IDBRequest对象,该对象包含对象存储中与指定参数匹配的所有对象,如果没有给出参数,则返回存储中的所有对象。

            如果成功找到一个值,则创建该值的结构化克隆,并将其设置为请求对象的结果。

            此方法产生相同的结果:

    • 数据库中不存在的记录
    • 具有未定义值的记录

            如果要分别获取,可以使用以下方法:    

    1. 使用相同键的openCursor()方法。如果记录存在,该方法提供一个游标,如果记录不存在,则不提供游标。
    2. 具有相同键的count()方法,如果行存在,该方法将返回1,如果行不存在,则返回0。

    语法:

    1. getAll()
    2. getAll(query)
    3. getAll(query, count)

    参数:

    名称描述
    query要查询的键或IDBKeyRange。如果不传递任何信息,则默认为选择此对象存储中的所有记录的键范围。
    count指定如果找到多个值要返回的值的数量。如果它小于0或大于2^32 - 1,则会抛出TypeError异常。

     返回值:

            一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。
     

    1.2 获取全部数据

            这里是通过getAll()函数,一次拿到所有数据,再通过utils工具类中genderPage()函数对数据进行筛选分页。

            原方utils/utils.js工具包中方法:

    1. /**
    2. * 生成分页数据
    3. */
    4. export const genderPage = (data, param) => {
    5. //判断分页数据是否存在,否则赋值默认参数
    6. param = param && 'undefined'!==typeof param['page'] && 'undefined'!==typeof param['pageSize'] ? param : {
    7. page: 1,
    8. pageSize: 10
    9. }
    10. let newData = data.map(item => item),
    11. start = (param.page - 1) * param.pageSize,
    12. end = newData.length - start < param.pageSize ? newData.length : start + param.pageSize;
    13. return newData.slice(start, end);
    14. }

            原数据库操作类db/model/student.js分页查询函数代码如下:

    1. /**
    2. * 获取 学员列表 - 分页模式
    3. * @param name 查询关键词
    4. * @param param 分页参数
    5. */
    6. export const loadStudentPage = (name, param) => {
    7. return new Promise((resolve, reject) => {
    8. //打开游标
    9. let {store} = openTransactionIndex(storeName);
    10. //获取所有数据
    11. let alls = store.getAll();
    12. alls.onerror = function(e){
    13. reject(
    14. rJson(0, e, '查询出错了~')
    15. );
    16. }
    17. alls.onsuccess = function(e){
    18. let result = e.target.result;
    19. if(result){
    20. let rData = result;
    21. if(name){
    22. rData = result.filter(item => item.name.includes(name));
    23. }
    24. getClassByIdsCursor(rData.filter(item => item.cid).map(item => item.cid)).then(res => {
    25. rData = rData.map(item => {
    26. if('undefined'!==typeof res[item.cid]){
    27. item['classify'] = res[item.cid]['name'];
    28. }
    29. return item;
    30. })
    31. //通过genderPageData函数进行分页处理
    32. resolve(
    33. rJson(1, {
    34. list: genderPage(rData, param),
    35. total: rData.length
    36. }, '获取成功~')
    37. )
    38. }).catch(e => {
    39. reject(
    40. rJson(0, e, '查询出错了~')
    41. )
    42. });
    43. }else{
    44. reject(
    45. rJson(0, null, '未查询到数据~')
    46. )
    47. }
    48. // console.log('store', result);
    49. }
    50. });
    51. }

    二、IndexedDB内置函数实现分页

    2.1 getAll()

            以上方法每次切换分页,都需要一次获取所有表中数据,获取到数据后,再进行分页处理。这种方法在数据量不大情况下可以使用,一旦数据量过大则查询非常慢,这里getAll()函数已提供了相应参数,具体入参前面已列出。

            我们通过以下两步即可改造好分页功能,具体如下:

    第一步:打开db/model/student.js,修改loadStudentPage()函数,使用getAllKeys()获取所有索引,通过分页参数计算出分页起始位置ID,再通过IDBKeyRange.lowerBound指定查询起始位置,代码如下:

    1. /**
    2. * 获取 学员列表 - 分页模式
    3. * @param param 参数
    4. * @description 通过IndexedDB内置函数实现分页
    5. */
    6. export const loadStudent2Page = param => {
    7. //判断分页数据是否存在,否则赋值默认参数
    8. param = param && 'undefined'!==typeof param['page'] && 'undefined'!==typeof param['pageSize'] ? param : {
    9. page: 1,
    10. pageSize: 10
    11. };
    12. return new Promise((resolve, reject) => {
    13. //打开游标
    14. let {store} = openTransactionIndex(storeName);
    15. //获取所有keys
    16. let keys = store.getAllKeys();
    17. //获取当前表数量总条数
    18. let countResult = store.count();
    19. keys.onerror = function(e){
    20. reject(
    21. rJson(0, e, '查询出错了~')
    22. );
    23. }
    24. keys.onsuccess = function(e){
    25. let allKeys = e.target.result;
    26. //获取当前分页开始位置ID
    27. let start = (param.page - 1) * param.pageSize;
    28. //获取分页起始位
    29. let lowerKey = IDBKeyRange.lowerBound(allKeys[start]);
    30. //获取分页数据
    31. let alls = store.getAll(lowerKey, param.pageSize);
    32. alls.onerror = function(e){
    33. reject(
    34. rJson(0, e, '查询出错了~')
    35. );
    36. }
    37. alls.onsuccess = function(e){
    38. let result = e.target.result;
    39. if(result){
    40. //获取班级信息
    41. getClassByIdsCursor(result.filter(item => item.cid).map(item => item.cid)).then(res => {
    42. result = result.map(item => {
    43. if('undefined'!==typeof res[item.cid]){
    44. item['classify'] = res[item.cid]['name'];
    45. }
    46. return item;
    47. })
    48. //通过genderPageData函数进行分页处理
    49. resolve(
    50. rJson(1, {
    51. list: result,
    52. total: countResult.result,
    53. }, '获取成功~')
    54. )
    55. }).catch(e => {
    56. reject(
    57. rJson(0, e, '查询出错了~')
    58. )
    59. });
    60. }else{
    61. reject(
    62. rJson(0, null, '未查询到数据~')
    63. )
    64. }
    65. }
    66. }
    67. //keys end
    68. });
    69. }

    第三步:打开/db/api/index.js,添加loadStudent2Page()请求,代码如下:

    1. import {
    2. loadStudent2Page
    3. } from '@/db/model/student'
    4. /**
    5. * 获取分页数据,不带筛选
    6. */
    7. export const getStudentPage2List = (param) => {
    8. return loadStudent2Page(param);
    9. }

    第三步:打开src/pages/student/index.vue,修改updateList()函数;引入接口getStudentPage2List()函数,列表页代码如下:

    1. updateList(){
    2. getStudentPage2List({
    3. page: this.page,
    4. pageSize: this.pageSize
    5. }).then(res => {
    6. if(res.code==1){
    7. this.pageTotal = res.data['total'];
    8. this.tableList = res.data['list'].map(item => {
    9. item['createtime'] = formatDate(item.createtime);
    10. item['updatetime'] = formatDate(item.updatetime);
    11. if(item['avatar']){
    12. item.avatar = URL.createObjectURL(item.avatar);
    13. }
    14. return item;
    15. });
    16. }
    17. }).catch(e => {
    18. console.error(e);
    19. })
    20. }

            实现后效果如下:

             此时,切换分页效果和使用utils工具包中genderPage()函数是一样的,而且是在指定范围内查询,遇到数据量大的情况下,速度得到大大的提升。

    2.2 倒序排列

            想必在前面章节中,大家就应该发现数据是正序排列的,最新数据一直排在后面,我们可以在“二、getAll实现分页”的基本上,稍微调整下即可实现倒叙排列。

            原代码如下:

    1. let allKeys = e.target.result;
    2. //获取当前分页开始位置ID
    3. let start = (param.page - 1) * param.pageSize;
    4. //获取分页起始位
    5. let lowerKey = IDBKeyRange.lowerBound(allKeys[start]);

            将以上代码,先把allKeys作个反转,通过分页参数计算出当前分页区间,再将获取的开始和结束位置的索引,赋值到IDBKeyRange.bound中即可,修改后代码如下:

    1. let allKeys = e.target.result.reverse();
    2. //获取当前分页开始位置ID
    3. let end = (param.page - 1) * param.pageSize,
    4. start = (allKeys.length - end < param.pageSize ? allKeys.length : end + param.pageSize) - 1;
    5. //获取分页起始位
    6. let lowerKey = IDBKeyRange.bound(allKeys[start], allKeys[end]);

            此时会发现列表中显示数据,已经是倒叙查询出来的,但是数据还是以正序结果在显示。正常情况应该是“刘博”排在第一位,但是现在是”陈可“,如下图:

             其实解决这个问题很简单,还是将最终结果反转一下即可,result后面添加reverse()函数,代码如下:

    1. resolve(
    2. rJson(1, {
    3. list: result.reverse(),
    4. total: countResult.result,
    5. }, '获取成功~')
    6. )

            此时显示则为倒序排列了,如下图:

     2.3 关键词搜索

            IDBObjectStore 接口的 openCursor ()方法返回一个 IDBRequest 对象,并在一个单独的线程中返回一个新的 IDBCursorWithValue 对象。用于使用游标遍历对象存储区。

            语法:

    1. openCursor()
    2. openCursor(query)
    3. openCursor(query, direction)

            参数:

    名称描述
    query要查询的键或IDBKeyRange。如果传递了一个有效键,则默认为只包含该键的范围。如果不传递任何信息,则默认为选择此对象存储中的所有记录的键范围。
    direction

    指示光标移动方向的字符串。默认值是next。有效值:

    next:游标在存储的开始处打开;然后,光标按键的递增顺序返回所有记录,甚至是重复的记录。

    nextunique:游标在存储的开始处打开;然后,游标按键的递增顺序返回所有非重复的记录。

    prev:游标在存储的开始处打开;然后,光标按键的递减顺序返回所有记录,甚至是重复的记录。

    prevunique:游标在存储的开始处打开;然后,游标按键的递减顺序返回所有不重复的记录。

    返回值:

            一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。

            写到这里有些人可能还未发现,改造完后的列表中关键词条件筛选已失效了。细心朋友应该已经发现getAll()无法满足模糊条件查询,此时我们改用openCursor()游标来实现 模糊分页查询功能。使用openCursor()方便之处在于倒序查询直接可以通过它来实现了,上面已列出了入参值,在第二个参数位置填入”prev”即可。

            列表页pages/student/index.vue中updateList()函数中添加name关键词,代码如下:

    1. /**
    2. * 查询数据
    3. */
    4. searchEvent(){
    5. this.page = 1;
    6. this.updateList();
    7. },
    8. /**
    9. * 获取列表数据
    10. */
    11. updateList(){
    12. getStudentPage2List({
    13. page: this.page,
    14. pageSize: this.pageSize,
    15. name: this.keyword
    16. }).then(res => {
    17. if(res.code==1){
    18. this.pageTotal = res.data['total'];
    19. this.tableList = res.data['list'].map(item => {
    20. item['createtime'] = formatDate(item.createtime);
    21. item['updatetime'] = formatDate(item.updatetime);
    22. if(item['avatar']){
    23. item.avatar = URL.createObjectURL(item.avatar);
    24. }
    25. return item;
    26. });
    27. }
    28. }).catch(e => {
    29. console.error(e);
    30. })
    31. },

             打开db/model/student.js,修改loadStudentPage()函数,代码如下:

    1. /**
    2. * 获取 学员列表 - 分页模式
    3. * @param param 参数
    4. * @description 通过IndexedDB内置函数实现分页,倒序查询,以及关键词模糊查询
    5. */
    6. export const loadStudent4Page = param => {
    7. //判断分页数据是否存在,否则赋值默认参数
    8. param = param && 'undefined'!==typeof param['page'] && 'undefined'!==typeof param['pageSize'] ? param : {
    9. page: 1,
    10. pageSize: 10,
    11. name: ""
    12. };
    13. return new Promise((resolve, reject) => {
    14. //打开游标
    15. let {store} = openTransactionIndex(storeName);
    16. //返回结果集
    17. let rData = [];
    18. //结果区间
    19. let start = (param.page - 1) * param.pageSize,
    20. end = start + param.pageSize,
    21. index = 0;
    22. store.openCursor(null, 'prev').onsuccess = function(e){
    23. let cursor = e.target.result;
    24. if(cursor){
    25. //判断关键词是否被包含
    26. if(cursor.value.name.includes(param.name)){
    27. if(index>=start&&index
    28. rData.push(cursor.value);
    29. }
    30. index++;
    31. }
    32. cursor.continue();
    33. }else{
    34. //获取班级信息
    35. getClassByIdsCursor(rData.filter(item => item.cid).map(item => item.cid)).then(res => {
    36. rData = rData.map(item => {
    37. if('undefined'!==typeof res[item.cid]){
    38. item['classify'] = res[item.cid]['name'];
    39. }
    40. return item;
    41. })
    42. //通过genderPageData函数进行分页处理
    43. resolve(
    44. rJson(1, {
    45. list: rData,
    46. total: index,
    47. }, '获取成功~')
    48. )
    49. }).catch(e => {
    50. reject(
    51. rJson(0, e, '查询出错了~')
    52. )
    53. });
    54. }
    55. }
    56. });
    57. }

     此时我们在搜索框中输入“刘”,则可以模糊查询出结果了,如下图:

     总结:

            不管是通过getAll()一次性获取所有数据,还是通过IDBKeyRange实现条件查询,或是通过openCursor()游标查询,实现过程中都遇到短板。其实仔细分析新增的loadStudent4Page()函数,虽然使用openCursor()游标查询,已通过start和end将分页数据查询完成,但还需要将所有数据遍历一次,来获取当前模糊查询结果的总条数。相比getAll()函数一次获取再通过utils工具包中genderPage()实现分页,性能是相差不大的。

            由于IndexedDB是非关系性数据库,使用SQL习惯的程序员对此不太适应,用起来比较别扭,可根据实际需求,使用适合自己的方法来实现相应的业务。

  • 相关阅读:
    windows下node升级版本(亲测有效)
    第4章 R语言编程基础——数据整理与预处理
    Ubuntu 2204 搭建 nextcloud 个人网盘
    KBP310-ASEMI开关电源整流桥KBP310
    Python爬虫之续Urllib && Jsonpath库的使用
    必备表格软件-FineReport正则表达式简介
    leetcode-22. 括号生成
    【CS.PL】Lua 编程之道: 基础语法和数据类型 - 进度16%
    IP 地址的分类
    详解卷积神经网络结构
  • 原文地址:https://blog.csdn.net/jiciqiang/article/details/127712345