04:后台信息管理项目

一、数据管理后台

1. AdminLTE模版的介绍

AdminLTE模版基于bootstrap,拥有最佳开源管理仪表板和控制面板主题。AdminLTE建立在引导之上,提供了一系列响应性强、可重用和常用的组件。

其中 starter.html 是 AdminLTE 建议用来作为起点的参考示例。build 目录包含未编译的 CSS、JS 文件;dist 目录包含经过编译的 CSS、JS 文件,同时还提供经过压缩的版本(.min)。plugins 目录存放依赖的插件;pages 目录下是一些示例页面。

2. 首页结构和项目架构

  1. 使用express项目生成器:express-generator快速生成ejs模版项目:
    • express myapp --view=ejs
  2. 进入项目文件目录,下载项目依赖:
    • npm i
  3. 将AdminLTE模版的starter.html源码复制myapp的view/index.ejs
  4. 启动myap项目:npm start,在浏览器查看首页布局
  5. 将starter.html的依赖(css,js,img)复制到myapp的public目录
    • 所有的相对路径前加:/
  6. 将index.ejs的公共部分,抽离出来形成公共模版,并在index.ejs中使用
    • views—->(header,aside,footer,sidebar)
  7. 处理左侧导航列表(结构、样式、图标)
    • (首页,轮播图管理,商品管理,用户管理,购物车管理,订单管理,消息管理等)
  8. 添加子页面,对应左侧导航链接
    • views—->(banner.ejs,pro.ejs,users.ejs,cart.ejs,order.ejs,message.ejs)
  9. 设置路由
    • 准备路由模块:
      • routes—->(banner.js,pro.js,users.js,cart.js,order.js,message.js)
    • 设置路由功能:
      • 在对应文件中修改:res.render("pro");
    • 注册路由模块:
      • app.js:
        • 引入路由文件:const pro = require("./routes/pro");
        • 注册到express:app.use("/pro",pro);
    • 使用路由:在a标签中正常使用即可
      • 点击时的当前项,也可在路由中设置变量,在渲染模板时使用对应的active即可

3. 增加添加信息页面及其功能

  1. 在AdminLTE模版中查找表格的UI模块,修改布局,实现商品数据展示表格结构
    • 设定商品字段:品牌,LOGO,分类,图片,名称,价格,销量,库存,折扣,评分,操作(删除,修改)
  2. 设置添加表单页面
    • 在views创建goodsAdd.ejs,准备添加表单,并修改表单为对应的商品数据字段,设置id,name等属性
    • 因为goodsAdd只能在goods页面被跳转,可以认为goodsAdd是goods的子页面(子路由)
    • 所以,只需要在rotues的goods.js中,添加路由处理,修改路径和对应渲染的页面即可:
1
2
3
router.get('/add',(req, res, next)=>{
res.render('goodsAdd',{activeIndex:1});
});
- 在goods.ejs添加链接:"/goods/add",实现跳转子路由。 
  1. 设置路由处理表单提交数据(goods.js—->addAction)
    • 在goods路由中添加子路由addAction,用来接收并处理表单提交数据:
1
2
3
router.post('/addAction', (req, res, next)=>{
res.send(req.body);
});

二、准备数据库

  1. 下载mongoose模块:npm i mongoose -S
  2. 在myapp项目中创建数据库操作模块:sql(文件夹)/db.js(连接数据库) + pro.js(创建集合) + sql.js(数据增删改查的封装)
  3. 创建集合之前,先设计集合结构(pro.js):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
goodsId:{type:String},
brand:{type:String},
logo:{type:String},
goodsName:{type:String},
goodsImg:{type:String},
price:{type:Number},
sales:{type:Number},
stock:{type:Number},
column:{type:String},
discount:{type:Number},
score:{type:Number},
describe:{type:String}
}
- 将集合模块暴露,准备实现数据的操作

三、处理数据并插入数据库信息(pro.js—->addAction)

  1. 接收到的表单数据,默认为字符数据,需要将对应字段修改成数值数据,如:price,sales,stock,discount,score
    • 注意:此处修改数据,是为了符合数据库集合创建时设定的字段类型
  2. 且此时缺少商品id字段,为了保证id字段绝对不会重复,此时使用第三方模块:node-uuid
    • 下载:npm i node-uuid
    • 引入:const uuid = require("node-uuid");
    • 使用:uuid.v1()    // 会自动生成唯一的id值
  3. 引入集合
    • const pro = require("./../pro.js");
  4. 引入数据操作的封装
    • const sql = require("./../sql.js");
  5. 开始数据的插入操作:
    • sql.insert(...);
      • 注意数据格式和创建集合字段格式保持一致
  6. 在插入数据成功后,注意设置路由重定向:
    • res.redirect("./pro");

四、查询数据并渲染页面

  • 初始打开商品数据页面时,应立即读取数据库信息,然后渲染页面
    1. 在rotues的pro.js的路由处理内,先读取数据库数据,读取成功后
1
2
3
4
5
6
7
8
9
router.get('/', (req, res, next)=>{
sql.find(pro,{},{_id:0}).then((data)=>{
// 再渲染页面,并将数据设置为变量
res.render('pro',{
activeIndex:1,
data:data
})
});
});
2. 在pro.ejs页面解析变量渲染布局
1
2
3
4
5
6
7
<% for(var i=0;i<data.length;i++){ %>
<tr>
<td><%= i+1 %></td>
<td><%= data[i].brand %></td>
……
<tr>
<% } %>

五、从Excel文件,导入数据

  1. 在商品信息管理页面(pro.ejs),添加导入信息按钮
    • <a href="./pro/upload" class="btn btn-primary">导入信息</a>
  2. 设置点击导入按钮的跳转路由
    • 在routes/pro.js中添加新的路由处理
    • router.get('/upload',(req, res, next)=>{ ... });
  3. 需要借助node解析excel的第三方模块:node-xlsx
    • 下载:npm i node-xlsx
    • 引入:const xlsx = require("node-xlsx");
    • 使用:const excelData = xlsx.parse("excel文件的绝对路径");
  4. 在upload路由处理中,解析文件数据,插入数据库,重定向路由

六、删除数据并删除数据库信息

  1. 点击删除按钮时,需将goodsId提交到指定删除路由
    • href="./pro/rm?id=<%= data[i].goodsId %>"
  2. 设置路由,接收商品goodsId后,删除数据库中数据
1
2
3
4
5
6
router.get('/rm', (req, res, next)=>{
var data = req.query.id;
sql.delete(pro,{goodsId:data}).then(()=>{
res.redirect("/pro");
});
});
  1. 删除完成后,重定向路由为当前页面
    • res.redirect("/pro");

七、商品管理其他功能

1. 增加更新页面及其功能

  • 过程可参考添加页面及其功能
    • 额外增加goodsId字段的显示,但是不允许修改
      • 使用readonly属性设置输入框只读
  • 处理数据并更新数据库信息
    • 过程可参考添加数据
    • 注意goodsId的获取

2. 数据排序

  1. 添加排序按钮,跳转到排序路由
  2. 设置排序路由
  3. 根据排序规则,查找数据,渲染页面

3. 数据搜索

  1. 设置搜索布局,跳转到搜索路由
  2. 设置搜索路由
  3. 根据搜索关键字,查询数据,渲染页面

4. 分页

  1. 设置分页布局,跳转到分页路由
  2. 设置分页路由
  3. 根据分页信息,查询数据,渲染页面

八、banner管理

1. 前端上传base64数据

  1. 前端图片上传
    • 客户端js使用FileReader将图片文件转成base64
1
<input type="file" name="imgFile" id="file">
1
2
3
4
5
6
7
8
9
var f = document.getElementById("file");
// FileReader使用方式
obtn.onclick = function(){
var reader = new FileReader();
reader.readAsDataURL(f.files[0]); // 解析成base64格式
reader.onload = function () {
console.log(this.result);
}
}
  1. 图片管理
  2. banner接口实现

2. 前端直接上传文件

multer向服务器上传文件

九、管理员登录

1. 设计管理员信息数据库

  1. 准备数据库,在myapp项目中创建管理员信息数据库集合:admin.js(创建集合)
  2. 并设计集合结构(admin.js):
1
2
3
4
5
6
7
8
{
userId:{type:String}, // 用户id
userName:{type:String}, // 用户名
passWord:{type:String}, // 密码
power:{type:Number}, // 权限:1普通管理员,2超级管理员
avatar:{type:String}, // 头像
lastTime:{type:String} // 最后一次登录时间
}
- 将集合模块暴露,准备实现数据的操作
  1. 使用uuid模块生成数据的唯一id
  2. 使用md5模块,对用户密码进行加密操作
    • npm install md5
  3. 设计权限状态:1为普通管理员,0位超级管理员
  4. 插入测试数据(insert.js)
1
2
3
4
5
6
7
8
9
10
11
12
13
const admin = require("./admin");
const uuid = require("node-uuid");
const md5 = require("md5");

admin.insertMany({
userId:uuid.v1(), // 用户id
userName:"liyang", // 用户名
passWord:md5("123456"), // 密码
power:1 // 权限:1普通管理员,2超级管理员
},(err)=>{
if(err) throw err;
console.log("管理员添加成功");
})

2. 增加登陆页面及路由

  1. 在views中创建login.ejs,处理布局。
  2. 注册login页面的路由信息
  3. 在login路由中添加/handle,用来处理登录验证,修改login.ejs中登录的提交地址为/login/handle
  4. /login/handle的路由处理中,根据接收到的用户信息,请求数据库,请求到数据,表示登录成功,否则表示登录失败
  5. 登录成功,存储登录成功之后的状态,同时路由重定向到首页
  6. 登录失败,路由重定向回登录页面

3. 状态存储

  1. 通过cookie
    • 存:res.cookie("isLogin","ok");
    • 读:req.cookies.isLogin
  2. 通过session
    • 下载session模块:npm install express-session
    • 配置session信息:
1
2
3
4
5
6
app.use(session({
secret: '加密字段', // 加密信息,可以随便写
resave: false, // 强制保存session,默认为true,建议设置为false
saveUninitialized: true, // 强制将未初始化的session存储,默认为true,建议为true
cookie: { maxAge: 1000 * 30 * 60 } // 过期时间,毫秒数
}))
- 存:`req.session.isLogin = "ok";`
- 读:`req.session.isLogin`
  1. 通过token — 前后端分离
    • 第一次请求时,用户发送账号与密码
    • 后台校验通过,生成一个有时效性的token,再将此token发送给用户
    • 用户获得token后,将此token存储在本地,一般存储在localstorage或cookie
    • 之后每次请求都会将此token添加在请求头,所有需要校验身份的接口都会被校验token,若token解析后的数据包含用户身份信息,则身份验证通过。
    • 如:
    • ajax登陆成功后,接收后端返回一个token字段str,
    • 使用localStorage.setItem(‘token’, str)或document.cookie = “token=” + str,
    • 以后再调用接口时先从本地取出token字段的值,然后随着请求的数据一起发送到服务器,服务器验证token的有效性,如果ok则返回数据,如果不ok,返回某一个字段,表示未登录,前端负责页面跳转到登录页面

4. 状态验证

  • 所有页面路由注册之前(除登录路由之外),开启路由拦截
1
2
3
4
5
6
7
app.all("*",(req,res,next)=>{
if(req.session.isLogin === "ok"){
next();
}else{
res.redirect("/login");
}
})

5. 权限验证

  1. 将用户权限值一同存在session
  2. 需要权限验证时,获取session中的权限信息
  3. 将权限信息传给aside.ejs,决定是否渲染某些导航

6. 防止未登录或无权限用户强行通过路由访问

  • 在路由渲染时,立即获取session信息,进行登录和权重验证
1
2
3
4
5
6
7
8
9
10
11
12
13
router.get('/', function(req, res, next) {
if(req.session.isLogin === "ok" && req.session.power < 1){
res.render("cart", {
activeIndex:4,
power:req.session.power
})
}else{
res.render("noPower",{
activeIndex:0,
power:req.session.power
})
}
});

更新: 2024-06-11 17:05:50
原文: https://www.yuque.com/liyangyf/node/oo4g2b