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

IndexedDB具有以下特点:

  1. 键值对存储:IndexedDB内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括JS对象。对象仓库中,数据以”键值对“的形式保存,每一个数据记录都对应的主键,主键是独一无二的,不能有重复,否则会抛出错误。
  2. 异步:IndexedDB操作时不会锁死浏览器,用户依然可以进行其他操作,这与LocalStorage形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
  3. 支持事务:IndexedDB支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
  4. 同源限制:IndexedDB受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
  5. 存储空间大:IndexedDB的存储空间比LocalStore大很多,一般来说不少于250MB,甚至没有上限。
  6. 支持二进制存储:IndexedDB不仅可以存储字符串,还可以存储二进制数据(ArrayBuffer对象和Blob对象)。

通过上篇讲解,大家已经熟悉了IndexedDB的一些基本用法。这里将使用IndexedDB写个简单的”学员管理系统(本地版)“,如对IndexedDB基础还不了解的,请翻看上一篇,地址如下:

本地数据库IndexedDB - 初学者(一)_觉醒法师的博客-CSDN博客

使用的技术框架是Vue.js + element-ui + vuex + axios + sass + indexedDB,系统栏目分类有:管理员列表、年级管理、班级管理、教师列表和学员列表。

如下图所示:

一、搭建项目

Vue项目的搭建这里也不再阐述,如有对vue全家桶不了解的同学,可以先阅读以下几篇文章:

Vue.js快速入门之一:安装和配置_觉醒法师的博客-CSDN博客_vue.js配置

Vue.js快速入门之二:使用状态管理工具Vuex_觉醒法师的博客-CSDN博客

Vue.js快速入门之三:Vue-Router路由_觉醒法师的博客-CSDN博客

Vue.js快速入门之四:axios安装和使用_觉醒法师的博客-CSDN博客_axios怎么安装

二、数据库创建

Vue项目搭建好后,需要在src目录下创建db目录,用来存储操作IndexedDB数据库文件。比如数据库操作文件、对应表封装类等。

2.1 定义表

虽然IndexedDB不属性于“关系性数据库”,这里还是按“关系性数据库”进行表的定义和关联。

系统管理员表:

字段名 类型 描述
id int ID
name varchar 管理员账号
password varchar 密码
phone varchar 手机号
createtime datetime 创建日期
updatetime datetime 更新日期

年级表:

字段名 类型 描述
id int ID
name varchar 年级名称
createtime datetime 创建日期
updatetime datetime 更新日期

班级表:

字段名 类型 描述
id int ID
name varchar 班级名称
mid varchar 班主任
gid int 关联年级
ts Array 关联授权教师
createtime datetime 创建日期
updatetime datetime 更新日期

教师表:

字段名 类型 描述
id int ID
name varchar 教师姓名
registration varchar 户籍
address varchar 现居住地
phone varchar 手机号
cids char 关联班级ID,通过逗号分隔
birthday datetime 出生年月日
createtime datetime 创建日期
updatetime datetime 更新日期

学员表:

字段名 类型 描述
id int ID
name varchar 学员姓名
registration varchar 户籍
address varchar 现居住地
phone varchar 手机号
cid int 关联班级ID
birthday datetime 出生年月日
createtime datetime 创建日期
updatetime datetime 更新日期

2.2 MD5加密

由于IndexedDB是浏览器中数据库,当项目运行后可直接查看数据库中的数据,这时相关私密性的内容就需要通过加密进行处理了。所以在创建数据库表前,我们先在src目录下创建utils工具类目录,添加md5.js文件,用来给登录密码进行加密处理。

md5.js代码如下:


/** Configurable variables. You may need to tweak these to be compatible with* the server-side, but the defaults work in most cases.*/
var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      *//** These are the functions you'll usually want to call* They take string arguments and return either hex or base-64 encoded strings*/
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }/** Perform a simple self-test to see if the VM is working*/
function md5_vm_test()
{return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}/** Calculate the MD5 of an array of little-endian words, and a bit length*/
function core_md5(x, len)
{/* append padding */x[len >> 5] |= 0x80 << ((len) % 32);x[(((len + 64) >>> 9) << 4) + 14] = len;var a =  1732584193;var b = -271733879;var c = -1732584194;var d =  271733878;for(var i = 0; i < x.length; i += 16){var olda = a;var oldb = b;var oldc = c;var oldd = d;a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);c = md5_ff(c, d, a, b, x[i+10], 17, -42063);b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);a = safe_add(a, olda);b = safe_add(b, oldb);c = safe_add(c, oldc);d = safe_add(d, oldd);}return Array(a, b, c, d);}/** These functions implement the four basic operations the algorithm uses.*/
function md5_cmn(q, a, b, x, s, t)
{return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}/** Calculate the HMAC-MD5, of a key and some data*/
function core_hmac_md5(key, data)
{var bkey = str2binl(key);if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);var ipad = Array(16), opad = Array(16);for(var i = 0; i < 16; i++){ipad[i] = bkey[i] ^ 0x36363636;opad[i] = bkey[i] ^ 0x5C5C5C5C;}var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);return core_md5(opad.concat(hash), 512 + 128);
}/** Add integers, wrapping at 2^32. This uses 16-bit operations internally* to work around bugs in some JS interpreters.*/
function safe_add(x, y)
{var lsw = (x & 0xFFFF) + (y & 0xFFFF);var msw = (x >> 16) + (y >> 16) + (lsw >> 16);return (msw << 16) | (lsw & 0xFFFF);
}/** Bitwise rotate a 32-bit number to the left.*/
function bit_rol(num, cnt)
{return (num << cnt) | (num >>> (32 - cnt));
}/** Convert a string to an array of little-endian words* If chrsz is ASCII, characters >255 have their hi-byte silently ignored.*/
function str2binl(str)
{var bin = Array();var mask = (1 << chrsz) - 1;for(var i = 0; i < str.length * chrsz; i += chrsz)bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);return bin;
}/** Convert an array of little-endian words to a string*/
function binl2str(bin)
{var str = "";var mask = (1 << chrsz) - 1;for(var i = 0; i < bin.length * 32; i += chrsz)str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);return str;
}/** Convert an array of little-endian words to a hex string.*/
function binl2hex(binarray)
{var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";var str = "";for(var i = 0; i < binarray.length * 4; i++){str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);}return str;
}/** Convert an array of little-endian words to a base-64 string*/
function binl2b64(binarray)
{var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var str = "";for(var i = 0; i < binarray.length * 4; i += 3){var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)| (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )|  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);for(var j = 0; j < 4; j++){if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);}}return str;
}export {hex_md5,b64_md5,str_md5
}

这里除了直接引用MD5文件外,也可以通过npm或yarn等一些包管理工具,使用命令进行安装,这部分自行选择适合自己项目的方法即可。

2.2 打开数据库

以上将所有表结构定义好后,咱们可以封装操作数据库文件了。在src目录下创建db目录,然后新建index.js文件,用来定义数据库打开及表结构创建等等。

在index.js文件中,我们封装一个DBIsLoadSuccess函数,用来判断IndexedDB是否打开成功,因为open请求不会立即打开数据库或者开始一个事务,而是在onsuccess执行时;并且结合Promise,来实现此功能。具体代码如下:

let Database;
let Request = window.indexedDB.open("TestDatabase", 2);
let isError = false;// 数据库打开失败
Request.onerror = function(event) {isError = true;throw "Why didn't you allow my web app to use IndexedDB?!";
};// 数据库打开成功
Request.onsuccess = function(event) {isError = false;Database = event.target.result;
};let handle = null;/*** 数据库增删改查 封装*/
export const DBIsLoadSuccess = () => {return new Promise((resolve, reject) => {//如果数据库打开成功,直接回调resolve函数,并返回实例对象if(Database){resolve(Database);}//如果数据库还未打开,添加定时器 进行查询,打开成功后,else{handle = setInterval(() => {if(Database){clearInterval(handle);resolve(Database);}//如果数据库打开失败,执行reject函数else if(isError){clearInterval(handle);reject();}}, 200);}});
}

2.3 表结构创建

在onupgradeneeded事件中,允许你在处理函数中更新数据库模式;所以我们在onupgradeneeded执行时,来创建对应的表及表结构。

在管理员表创建时,需要默认添加一个管理员账号,并且通过hex_md5对密码进行处理后再存储到用户表中。创建管理员账号时,无须添加accesstoken键值对,因为此时用户还未登录,无token信息;如默认添加空值,则索引accesstoken中则会有相应关联值;这里accesstoken索引是用来判断用户是否登录的,是唯一的,只有在登录状态下才会赋值。

在index.js文件中增加以下代码:

import { hex_md5 } from '@/utils/md5'//数据库表名
let usersName = "users";        //用户表
let gradeName = "grade";        //年级表
let classifyName = "classify";  //班级表
let studentName = "student";    //学生表
let teacherName = "teacher";    //老师表
let buildingName = "building";  //楼栋// 执行success前执行函数
Request.onupgradeneeded = function(e){let db = e.target.result;//用户表if(!db.objectStoreNames.contains(usersName)){let store = db.createObjectStore(usersName, {keyPath: "id", autoIncrement: true});//创建索引 - 用户名store.createIndex('name', 'name', {unique: true});//创建索引 - 登录tokenstore.createIndex('accesstoken', 'accesstoken', {unique: true});//添加默认账号store.add({name: "admin",password: hex_md5('123456'),phone: "13233332222",cratetime: new Date().getTime(),updatetime: new Date().getTime()});}//年级表if(!db.objectStoreNames.contains(gradeName)){let store = db.createObjectStore(gradeName, {keyPath: "id", autoIncrement: true});//创建索引 - 年级名称(年级为唯一,故不能重复)store.createIndex('name', 'name', {unique: true});}//班级表if(!db.objectStoreNames.contains(classifyName)){let store = db.createObjectStore(classifyName, {keyPath: "id", autoIncrement: true});//创建索引 - 班级名称store.createIndex('name', 'name', {unique: false});//创建索引  - 年级名称store.createIndex('grade', 'grade', {unique: true});}//学生表if(!db.objectStoreNames.contains(studentName)){let store = db.createObjectStore(studentName, {keyPath: "id", autoIncrement: true});//创建索引 - 学生姓名store.createIndex('name', 'name', {unique: false});}//老师表if(!db.objectStoreNames.contains(teacherName)){let store = db.createObjectStore(teacherName, {keyPath: "id", autoIncrement: true});//创建索引 - 老师姓名store.createIndex('name', 'name', {unique: false});}//楼栋表if(!db.objectStoreNames.contains(buildingName)){let store = db.createObjectStore(buildingName, {keyPath: "id", autoIncrement: true});//创建索引 - 楼栋名称store.createIndex('name', 'name', {unique: true, multiEntry: true});}//if end
}

注意:onupgradeneeded只会在第一次打开数据库时触发,后期想要触onupgradeneeded监听事件,则需要修改open中第二位参数的版本号。

我们在项目中引用index.js文件并执行,此时我们F12显示控制台,切换到“Application”,在IndexedDB中,则可以看到TestDatabase数据库了,以及创建相应的表及索引。

如下图:

三、项目结构

在项目中,如下图在src目录下,创建相应的项目文件。

在router以上部分,会在后面会详细讲解,这里我们先讲下如果定义路由和状态管理仓库。

3.1 vue-ls

Vue-ls用来控制数据存储在localStorage或者sessionStorage中,并且可以控制其存储时效性。

安装:

npm install vue-ls --save

main.js中引入:

import Storage from 'vue-ls'Vue.use(Storage, {namespace: 'system_',name: 'ls',storeage: 'local'
});

基本用法如下:

 //存储数据
Vue.ls.set('foo', 'boo', 60 * 60 * 1000); //缓存1小时
Vue.ls.get('foo');
Vue.ls.get('boo', 10); //如果没有获取到boo数据,默认返回10Vue.ls.on('foo', callback) //监听foo值变化,触发Callback回调函数
Vue.ls.off('foo', callback) //卸载foo监听事件//移除foo对应缓存数据
Vue.ls.remove('foo');

3.2 store状态管理

Vuex是一个专为Vue.js应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件状态,并在相应的规则保证状态以一种可预测的方式发生变化。

什么情况下会使用到Vuex:比如登录的用户信息,接口的统一访问令牌等,会频繁使用和全站通用的数据,可以寄存在状态管理器中。

现在在store目录创建相应文件:

3.2.1 创建state.js文件

代码如下:

/*** 状态,变量库*/
const state = {/*** 访问令牌*/token: "",/*** 用户信息*/userInfo: null
}export default state;

3.2.2 创建mutationsType.js文件

代码如下:

/*** 用户信息*/
export const USERINFO = "USERINFO";/*** 访问令牌*/
export const TOKEN = "TOKEN";

3.2.3 创建mutations.js文件

代码如下:

import { USERINFO, TOKEN } from './mutationsType'const mutations = {/*** 修改访问令牌信息*/[TOKEN](state, param){state.token = param;},/*** 修改用户信息*/[USERINFO](state, param){state.userInfo = param;}
}
export default mutations;

3.2.4 创建getters.js文件

代码如下:

const getters = {/*** 用户信息*/userInfo(state){return state.userInfo;},/*** 访问令牌*/accessToken(state){return state.token;}
}
export default getters;

3.2.5 创建actions.js文件

代码如下:

import Vue from 'vue'
import { USERINFO, TOKEN } from './mutationsType'/*** 业务层*/
const actions = {/*** 检查是否登录*/checkIsLogin(){let token = Vue.ls.get(TOKEN);return new Promise((resolve, reject) => {if(token){resolve();}else{reject();}});},/*** 保存登录信息*/saveLoginInfo({commit}, param){if(param['token']) {commit(TOKEN, param.token);Vue.ls.set(TOKEN, param.token);}if(param['userinfo']) {commit(USERINFO, param.userinfo);Vue.ls.set(USERINFO, param.userinfo);}},/*** 退出登录*/exitLogin({commit}, param){commit(TOKEN, '');commit(USERINFO, '');Vue.ls.remove(TOKEN);Vue.ls.remove(USERINFO);}
}export default actions;

3.2.6 创建index.js文件

代码如下:

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import getters from './getters'
import actions from './actions'
import mutations from './mutations'Vue.use(Vuex);export default new Vuex.Store({state,getters,actions,mutations
})

3.2.7 main.js中引入

代码如下:

import Vue from 'vue'
import App from './App'
import elementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import 'element-ui/lib/theme-chalk/base.css'
import Storage from 'vue-ls'
import store from '@/store/index'Vue.use(elementUI);Vue.use(Storage, {namespace: 'system_',name: 'ls',storeage: 'local'
});Vue.config.productionTip = false/* eslint-disable no-new */
new Vue({el: '#app',store,components: { App },template: '<App/>'
})

这里store本地状态管理器中文件就创建和定义完了,后续可直接使用this.$store进行调用即可。

3.3 router路由定义

在定义路由前,先在pages目录下,对应创建所有页面文件,如管理员列表页、年级列表页、班级列表页、教师列表页、学员列表页,以及错误页和登录页。

另外,我们需要在components组件中,新建Layout.vue文件,用来定义菜单和内容区域,作为公共部分。代码如下:

html代码部分:

<template><div class="layout-wrap"><el-container class="container"><el-aside width="200px" class="aside-box"><div class="title"><h3>学员管理系统</h3></div><el-menu default-active="1" class="el-menu-vertical" :router="true"><el-menu-item index="1" :route="{path: '/sys/mange'}"><i class="el-icon-s-custom"></i><span slot="title">管理员列表</span></el-menu-item><el-menu-item index="2" :route="{path: '/sys/grade'}"><i class="el-icon-house"></i><span slot="title">年级管理</span></el-menu-item><el-menu-item index="3" :route="{path: '/sys/classify'}"><i class="el-icon-collection-tag"></i><span slot="title">班级管理</span></el-menu-item><el-menu-item index="4" :route="{path: '/sys/teacher'}"><i class="el-icon-user"></i><span slot="title">教师列表</span></el-menu-item><el-menu-item index="5" :route="{path: '/sys/student'}"><i class="el-icon-user"></i><span slot="title">学员列表</span></el-menu-item></el-menu><div class="btn-box"><el-button type="danger" size="mini" @click="logoutEvent">退出登录</el-button></div></el-aside><el-main><transition name="el-zoom-in-center"><router-view /></transition></el-main></el-container></div>
</template>

JS部分:

<script>
export default {name: 'Layout',data () {return { }},methods: {/*** 退出登录*/logoutEvent(){}}
}
</script>

样式部分:

<style lang="scss" scoped>.container{ height: 100vh; }.el-menu{ border-right: 0; }.el-menu-vertical{ height: 100%; }.aside-box{position: relative;padding-top: 80px;border-right: 1px solid #e6e6e6;.title{width: 100%;padding: 30px 0;text-align: center;position: absolute;left: 0;top: 0;z-index: 10;h3{font-size: 20px;color: #409EFF;}}}
</style>

待这些页页创建成功后,我们将在router/index.js中引入这些模块,用来定义页面跳转路由路径,代码如下:

import Vue from 'vue'
import Router from 'vue-router'
import Layout from '@/components/Layout'
import Error404 from '@/pages/Error/err404'
import Index from '@/pages/index'
import Student from '@/pages/student'
import Mange from '@/pages/mange'
import Grade from '@/pages/grade'
import Classify from '@/pages/classify'
import Teacher from '@/pages/teacher'
import Login from '@/pages/login'
import store from '@/store'Vue.use(Router);let _router = new Router({routes: [{path: '/',name: "Home",component: Layout,redirect: '/sys/index',children: [{path: '/sys/index',name: 'Index',component: Index,},{path: '/sys/student',name: 'Student',component: Student,},{path: '/sys/mange',name: 'Mange',component: Mange,},{path: '/sys/grade',name: 'Grade',component: Grade,},{path: '/sys/classify',name: 'Classify',component: Classify,},{path: '/sys/teacher',name: 'Teacher',component: Teacher,}]},{path: '/login',name: 'Login',component: Login,},{path: '*',name: 'Error404',component: Error404,},]
});_router.beforeEach((toRoute, fromRoute, next) => {next();
});export default _router;

此时,我们可以点击页面左侧菜单,进行页面跳转了。关于登录功能,和登录页面跳转,将会在后面继续讲解。

四、登录功能

4.1 权限校验

在前面我们定义好了路由相关跳转路径,那我们如何跳转到登录页呢。此时大家可以往前翻看“3.2.5 创建actions.js文件”中,定义了checkIsLogin函数,用来判断用户是否登录了。在里我们则可以对router/index.js中的 路由卫士进行 稍微调整即可,代码如下:

_router.beforeEach((toRoute, fromRoute, next) => {store.dispatch('checkIsLogin').then(() => {next();}).catch(() => {if(toRoute.path=='/login'){next();}else{next('/login');}});
});

通过执行checkIsLogin函数,来判断用户是否已登录,如登录则直接跳转到下一路由页面中,否则跳转到登录页,完成登录后,则可以正常访问系统页面。为什么要在此判断呢,因为所有页面的访问都要经过“路由卫士”,系统登录是有时效性的,一旦超时token则会自动失效;所以当token失败时,用户点击下一步操作,则会自动跳转到登录页面。

另外,由于这里引用了Promise,所以会出现以下错误:

vue-router Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current

这时按以下方法修改即可:

//存储push
let originPush=Router.prototype.push
let originReplace=Router.prototype.replace//重写
Router.prototype.push=function(location,resole,reject){if(resole&&reject){originPush.call(this,location,resole,reject)}else{originPush.call(this,location,()=>{},()=>{})}
}
Router.prototype.replace=function(location,resole,reject){if(resole&&reject){originReplace.call(this,location,resole,reject)}else{originReplace.call(this,location,()=>{},()=>{})}
}_router.beforeEach((toRoute, fromRoute, next) => {store.dispatch('checkIsLogin').then(() => {next();}).catch(() => {if(toRoute.path=='/login'){next();}else{next('/login');}});
});

4.2 登录页面

此时开始着手完成登录页部分的代码,这里直接贴代码了。

html部分:

<template><div class="login-box"><h3>学员管理系统</h3><h4><span>———</span> <span class="tit">安全登录</span> <span>———</span></h4><el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm"><el-form-item label="用户名" prop="username"><el-input type="text" v-model="ruleForm.username" autocomplete="off"></el-input></el-form-item><el-form-item label="密码" prop="password"><el-input type="password" v-model="ruleForm.password" autocomplete="off"></el-input></el-form-item><el-form-item><el-button type="primary" :disabled="disabledButton" @click="submitForm('ruleForm')">登录</el-button><el-button :disabled="disabledButton" @click="resetForm('ruleForm')">重置</el-button></el-form-item></el-form></div>
</template>

js部分:

<script>export default {data(){var validateUsername = (rule, value, callback) => {if (value === '') {callback(new Error('请输入用户名'));} else {callback();}};var validatePass = (rule, value, callback) => {if (value === '') {callback(new Error('请输入密码'));} else {if (this.ruleForm.checkPass !== '') {this.$refs.ruleForm.validateField('checkPass');}callback();}};return {disabledButton: false,    //是否禁用按钮ruleForm: {username: '',password: '',},rules: {username: [{ validator: validateUsername, trigger: 'blur' }],password: [{ validator: validatePass, trigger: 'blur' }]},}},methods: {/*** 提交数据* @param {Object} formName*/submitForm(formName) {this.$refs[formName].validate((valid) => {if (valid) {console.log(this.ruleForm);} else {console.log('error submit!!');return false;}});},/*** 重置表单* @param {Object} formName*/resetForm(formName) {this.$refs[formName].resetFields();}}}
</script>

样式部分:

.login-box{width: 600px;height: 390px;padding: 50px 70px;box-sizing: border-box;box-shadow: 0 0 10px rgba(0, 0, 0, .1);border-radius: 10px;overflow: hidden;position: absolute;left: 50%;top: 50%;margin-left: -300px;margin-top: -200px;z-index: 10;h3, h4{font-family: "Microsoft YaHei","微软雅黑",Arial,sans-serif;text-align: center;}h3{font-size: 26px;color: #409eff;}h4{font-size: 14px;color: #999999;font-weight: normal;padding: 10px 0 40px;span{display: inline-block;vertical-align: middle;&.tit{padding: 0 26px;}}}
}

登录页界面如下图:

4.3 添加打开事务功能

在2.2中我们完成了“数据库打开”操作文件的代码,此时需要在内部添加两个打开事务的执行函数,以及一个读写的常量值(IndexedDB中有对应常量值,但已废弃,这里自己定义即可),在db/index.js文件中增加代码如下:

/*** 返回是只读或读写模式*/
export const CONST_READ = {READONLY: "readonly",READWRITE: "readwrite"
}/*** 打开索引用户名游标*/
export const openTransactionIndex = (storeName, indexName, mode) => {if(!storeName){throw '请指定打开事务的表名!';}if(!indexName){throw '请指定需查询索引名!';}mode = mode || CONST_READ.READONLY;//开启事务let transaction = Database.transaction([storeName], mode);//连接对象仓库let store = transaction.objectStore(storeName);//获取对应索引let index = store.index(indexName);//返回游标let cursor = index.openCursor();return {store,index,cursor}
}

4.4 api请求定义

在src目录下新建api/index.js文件,用来定义接口请求功能函数。我们先在db/model目录下创建user.js,用来操作管理员表的增删改查;创建好后,先定义好login和ogout函数,用来处理登录和退出功能,代码如下:

/*** 通过索引获取对应数据*/
export const  login = data => {}/*** 退出登录*/
export const logout = token => {}

在db/model/user.js创建好后,我们可以在api/index.js中定义登录和退出接口请求了,代码如下:

import { login, logout } from '@/db/model/user'/*** 登录*/
export const loginInfo = (params) => {return login(params);
}/*** 退出*/
export const logoutInfo = params => {return logout(params);
}

4.5 登录和退出业务功能

在4.3中,我们在db/index.js中添加了添加了处理索引事务函数,我们将其引入到db/model/user.js表中,用来实现登录用户查询和accesstoken信息查询。

另外,用户密码保存时是通过md5加密处理的,所以登录时匹配用户密码时,也需要使用到md5处理函数,这里也需要引入。

用户登录成功时候,需要生成accesstoken随机字符串,这里咱们把这类功能函数放在utils/utils.js工具类中;还有在所有数据请求成功后,需要返回一个统一的数据格式JSON文件,我们也在工具类文件中定义个rJson函数,工具类文件代码如下:

/*** 随机生成字符串* @param {*} _len*/
export const randomStrName = _len => {let _string = 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ1234567890',_returnStr = '';_len = 'number'===typeof _len ? _len : 50;for(var i = 0; i < _len; i++){_returnStr += _string.charAt( Math.floor(Math.random() * (_string.length-2)) )}return _returnStr;
}/*** 返回数据对象* @param code 状态码, 0 表示错误, 1表示正常* @param data 返回数据集* @param msg 返回提示信息*/
export const rJson = (code, data, msg) => {code = code || '0';data = data || null;msg = msg || '';return {code,data,msg}
}

以上准备工具做好后,我们还要在user.js文件中定义storeName,指定当前文件操作的表。login和logout功能函数实现上,使用Promise来异步操作,这样使回调数据更为灵活点。

代码如下:

import { openTransactionIndex, CONST_READ } from '@/db'
import { rJson, randomStrName } from '@/utils/utils'
import { hex_md5 } from '@/utils/md5'let storeName = 'users';/*** 通过索引获取对应数据*/
export const  login = data => {return new Promise((resolve, reject) => {});
}/*** 退出登录*/
export const logout = token => {return new Promise((resolve, reject) => {});
}

4.6 实现登录功能

现在各项关系的文件都创建好了,并且已关联上,咱们可以找到“4.2 登录页面”的js部分,引入api/index.js中的loginInfo,来实现登录数据上传并校验。另外,之前在“3.2.5 创建actions.js文件”中,定义了登录保存用户信息业务函数saveLoginInfo,在登录成功后,需调用此函数将用户信息和accesstoken保存为全局变量中,以及缓存本地存储中。打开pages/login/index.vue文件,代码如下:

<script>import { loginInfo } from '@/api'export default {data(){// ...return {disabledButton: false,    //是否禁用按钮ruleForm: {username: '',password: '',},rules: {username: [{ validator: validateUsername, trigger: 'blur' }],password: [{ validator: validatePass, trigger: 'blur' }]},}},methods: {/*** 提交数据*/submitForm(formName) {this.$refs[formName].validate((valid) => {if (valid) {loginInfo(this.ruleForm).then(res => {if(res.code==1){//缓存数据this.$store.dispatch('saveLoginInfo', {token: res.data.accesstoken,userinfo: res.data.userinfo})}this.$message.success(res.msg);this.$router.push('/');}).catch(e => {this.$message.error(e.msg);this.resetForm('ruleForm');});} else {return false;}});},// ...}}
</script>

这里输入用户名和密码进行登录,是没有返回数据的,因为我们还没实现数据库查询功能,现在大家打开db/model/user.js文件,实现login函数部分代码。

在前面“2.3 表结构创建”中,有创建name和accesstoken两个索引;又在“4.3 添加事务功能”中新增了事务创建相关功能函数,这里咱们就可以用上了。首先登录用户名是唯一的,这里通过name索引进行匹配查询,这里openTransactionIndex函数获取相应处理对象。

代码如下:

export const  login = data => {return new Promise((resolve, reject) => {//打开事务let { index, store } = openTransactionIndex(storeName, 'name', CONST_READ.READWRITE);//通过用户名,获取用户数据let indexData = index.get(data.username);indexData.onerror = function(e){reject(rJson(0, e, '查询出错了~'));}indexData.onsuccess = function(e){let result = e.target.result;if(result){//判断密码是否一致if(result.password==hex_md5(data.password)){let accesstoken = randomStrName(80);//记录token值result['accesstoken'] = accesstoken;//保存tokenstore.put(result);//返回结果数据resolve(rJson(1, {accesstoken,userinfo: {id: result.id,username: result.name,phone: result.phone}}, '登录成功~'))}else{reject(rJson(0, null, '密码错误~'))}}else{reject(rJson(0, null, '用户名错误~'))}//if end}});
}

以上功能完成后,我们就可以实现登录了,这时我们在登录界面输入用户名和密码,则可以跳转到首页了。在“2.3 表结构创建”中,默认添加了admin账号,暂时可以通过这个账号登录系统,登录后界面如下:

4.7 实现退出功能

在“3.3 router路由定义”中的JS部分,我们调用下退出接口,完成退出功能,并通过“3.2.5 创建actions.js文件”中定义的exitLogin函数,清除本地缓存数据。代码如下:

<script>
import { logoutInfo } from '@/api'
import { TOKEN } from '@/store/mutationsType'
export default {name: 'Layout',data () {return { }},methods: {/*** 退出登录*/logoutEvent(){this.$confirm('确认好退出登录吗?', '提示', {confirmButtonText: '退出',cancelButtonText: '取消',type: 'warning'}).then(() => {logoutInfo(this.$ls.get(TOKEN)).then(res => {this.$store.dispatch('exitLogin');this.$message.success(res.msg);this.$router.push('/login');}).catch(e => {this.$message.error(e.msg);this.$router.push('/login');})}).catch(() => {this.$message({type: 'info',message: '已取消退出'})})}}
}
</script>

同样,现在点击退出按钮不会得到响应,需要将db/model/user.js中的logout功能实现后,才能真正实现退出功能。

代码如下:

export const logout = token => {return new Promise((resolve, reject) => {//打开事务let {index} = openTransactionIndex(storeName, 'accesstoken', CONST_READ.READWRITE);//通过token,获取对应用户数据let indexData = index.get(token);indexData.onerror = function(e){reject(rJson(0, e, '查询出错了~'));}indexData.onsuccess = function(e){let result = e.target.result,source = e.target.source;if(result){let store = source.objectStore;//判断token信息是否匹配if(result.accesstoken == token){//删除accesstokendelete result['accesstoken'];//更新数据store.put(result);resolve(rJson(1, null, '退出成功~'));}else{reject(rJson(0, null, '登录失效~'))}} else{reject(rJson(0, null, '未查询到登录信息~'))}}});
}

这里我们在登录状态下,点击退出按钮,退出成功后就会直接跳转到登录界面了。

4.8 校验缓存数据中的登录信息

我们又要回到 “ 3.2.5 创建actions.js文件 ”中,为什么当初在定里定义函数判断token失效,而不是直接放到 路由卫生中获取token,直接进行判断呢;因为这里除了要校验本地是否缓存token信息,同时也要判断数据库中token是否存在或失效,话不多说,直接上代码。

在db/model/user.js中添加checkToken函数,代码如下:

/*** 校验token是否存在*/
export const checkToken = token => {}

在api/index.js中添加tokenIsFailure函数,代码如下:

import { login, logout, checkToken } from '@/db/model/user'/*** 判断数据库中token是否失效*/
export const tokenIsFailure = token => {return checkToken(token);
}

store/actions.js文件中的checkIsLogin进行改造,代码如下:

checkIsLogin(){let token = Vue.ls.get(TOKEN);return new Promise((resolve, reject) => {if(token){tokenIsFailure(token).then(() => {resolve();}).catch(() => {commit(TOKEN, '');Vue.ls.remove(TOKEN);reject();});}else{reject();}});}

当读取到本地缓存的token信息后,还需要进行数据库校验;如果数据库中不存在,则会直接跳转到登录页,并清除本地缓存的token信息。

另外,我们之前在“2.2 打开数据库”中定义过DBIsLoadSuccess函数,因为IndexedDB的open()函数不是执行后就返回实例对象的,所有刚进入系统,很有可能会遇到实例对象为空的情况。这时我们需要特别小心,在调用事务前,先判断数据库实例对象是否存在。

接下来让我们完成db/model/user.js中的checkToken函数功能,代码如下:

export const checkToken = token => {return new Promise((resolve, reject) => {//判断数据库是否打开DBIsLoadSuccess().then(() => {//打开事务let {index} = openTransactionIndex(storeName, 'accesstoken');//通过token,查询数据let indexKey = index.getKey(token);indexKey.onerror = function(e){reject(rJson(0, e, '查询出错了~'));}indexKey.onsuccess = function(e){let result = e.target.result;if(result){resolve(rJson(1, e, '校验成功~'))}else{reject(rJson(0, e, '登录失效~'));}}}).catch(() => {reject(rJson(0, e, '数据库打开失败~'));})});
}

这里我们先手动清除数据表中的accesstoken,然后点击任意页面跳转,此时会发现页面直接跳转到登录页了。

正常情况下,在登录时创建的token,是带有时效的,这块这里就不细讲,大家可以通过自己的理解,去完善此部分。

至此,该篇内容已讲解完了,该系统其他功能完善请看后续篇幅。

下一篇地址:本地数据库IndexedDB - 学员管理系统之列表管理(二)_觉醒法师的博客-CSDN博客

本地数据库IndexedDB - 学员管理系统之登录(一)相关推荐

  1. 本地数据库IndexedDB - 初学者

    IndexedDB 是一种可以让你在用户的浏览器内持久化存储数据的方法.IndexedDB 为生成 Web Application 提供了丰富的查询能力,使我们的应用在在线和离线时都可以正常工作. 一 ...

  2. HTML5 本地数据库IndexedDB数据库

    在HTML 5中,新增一种被称为indexedDB的数据库,该数据库是一种存储在客户端本地的NoSQL数据库. window.indexedDB=window.indexedDB||window.we ...

  3. indexDB 本地数据库

    indexDB 本地数据库 IndexedDB是一种使用浏览器存储大量数据的方法,它创造的数据可以被查询,并且可以离线使用. 优点:空间大小,大于250M:支持二进制:IndexedDB不但可以存储对 ...

  4. mysql数据库小系统_Mysql数据库基础小实例 学员管理系统菜单

    package test; import java.sql.*; import java.util.Scanner; public class testSql002_StudentTest { /** ...

  5. SQL Server 本地数据库登录不上 解决方法

    SQL Server 本地数据库登录不上 解决方法 参考文章: (1)SQL Server 本地数据库登录不上 解决方法 (2)https://www.cnblogs.com/cr-cool/p/10 ...

  6. 微信授权登录后,把获取的信息存储到本地数据库中(超详细有用)!

    微信授权登录后,把获取的信息存储到本地数据库中(超详细有用)!下面一起来看看吧! <?php namespace Home\Controller; use Think\Controller; c ...

  7. sqlplus命令无需监听服务就可以登录本地数据库的登录方式的研究

    实验一 1.执行sqlplus命令,然后输入账号密码登录,发现成功登录orcl 2.关掉默认监听服务,确认系统上没有运行的监听服务 3.继续用1的方法登录数据库,依然成功登录 结论一: 1.在本地直接 ...

  8. Oracle11g本地数据库创建用户和密码,附赠使用PL/SQL软件登录用户

    Oracle11g创建用户和密码 步骤: 管理员身份打开cmd命令窗口: 输入sqlplus 先使用管理员账户登录,为了创建普通用户: 用户名:sys as sysdba 密码:随便输 创建新用户,我 ...

  9. 《MySQL入门很简单》学习笔记(22)之第22章驾校学员管理系统(关键词:数据库/MySQL)

    第22章 驾校学员管理系统 略. 参考文献: 1.<MySQL入门很简单>.

最新文章

  1. 对python里的装饰器
  2. Arduino初初教程10——温湿度传感器
  3. jQuery加载一个html页面到指定的div里
  4. python打包exe os模块_python打包成exe格式的方法求教
  5. 在AD中恢复误删除的对象
  6. drupal linux安装,在Debian 10(Buster) Linux服务器中安装drupal 8.8.0的说明
  7. 前端学习(2592):当前用户显示
  8. 文件打开特别慢_“Origin进不去、下载慢”的解决办法合集
  9. mysql连接超时timeout问题
  10. Element-UI Form表单 resetFields() 重置表单无效问题
  11. rx java操作符_RxJava 常用操作符大全(一)
  12. 台式计算机显示器的分辨率,台式电脑分辨率多少合适,测试电脑分辨率
  13. 目标检测应用竞赛 | 天池铝型材表面瑕疵(含数据集、详细解决方案)
  14. 阿里月饼事件,猿方怎么看?
  15. Django批量修改 get_field_display foreignkey
  16. Kryo官方文档-中文翻译
  17. 2018-2019-2 20189206 《密码与安全新技术专题》 第六次作业
  18. (2020年最新版)App Store 审核指南
  19. oracle 发的邮件qq,python基于SMTP发送QQ邮件
  20. android bilibili sd卡,移动bilibili缓存视频到SD卡

热门文章

  1. 10年鼻炎,一路的辛酸泪
  2. 力扣786. 第 K 个最小的素数分数(自定义排序方式)?
  3. WP8.1新功能全解析
  4. 微信(QQ)重名困扰探究系列之一 数据库设计与SQL模糊查询
  5. 笔试题:利用素数定理计算100以内素数的个数
  6. [原]亲历水货笔记本电脑交易
  7. PGM---pgmpy学习
  8. Web前端20~45
  9. onkeyup事件,onpaste事件,完成实时表单验证
  10. linux 系统开启火狐命令_linux下安装firefox