04:后台信息管理项目
一、数据管理后台
1. AdminLTE模版的介绍
AdminLTE模版基于bootstrap,拥有最佳开源管理仪表板和控制面板主题。AdminLTE建立在引导之上,提供了一系列响应性强、可重用和常用的组件。
其中 starter.html 是 AdminLTE 建议用来作为起点的参考示例。build 目录包含未编译的 CSS、JS 文件;dist 目录包含经过编译的 CSS、JS 文件,同时还提供经过压缩的版本(.min)。plugins 目录存放依赖的插件;pages 目录下是一些示例页面。
2. 首页结构和项目架构
- 使用express项目生成器:express-generator快速生成ejs模版项目:
- 进入项目文件目录,下载项目依赖:
- 将AdminLTE模版的starter.html源码复制myapp的view/index.ejs
- 启动myap项目:
npm start
,在浏览器查看首页布局
- 将starter.html的依赖(css,js,img)复制到myapp的public目录
- 将index.ejs的公共部分,抽离出来形成公共模版,并在index.ejs中使用
- views—->(header,aside,footer,sidebar)
- 处理左侧导航列表(结构、样式、图标)
- (首页,轮播图管理,商品管理,用户管理,购物车管理,订单管理,消息管理等)
- 添加子页面,对应左侧导航链接
- views—->(banner.ejs,pro.ejs,users.ejs,cart.ejs,order.ejs,message.ejs)
- 设置路由
- 准备路由模块:
- 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. 增加添加信息页面及其功能
- 在AdminLTE模版中查找表格的UI模块,修改布局,实现商品数据展示表格结构
- 设定商品字段:品牌,LOGO,分类,图片,名称,价格,销量,库存,折扣,评分,操作(删除,修改)
- 设置添加表单页面
- 在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",实现跳转子路由。
- 设置路由处理表单提交数据(goods.js—->addAction)
- 在goods路由中添加子路由addAction,用来接收并处理表单提交数据:
1 2 3
| router.post('/addAction', (req, res, next)=>{ res.send(req.body); });
|
二、准备数据库
- 下载mongoose模块:
npm i mongoose -S
- 在myapp项目中创建数据库操作模块:sql(文件夹)/db.js(连接数据库) + pro.js(创建集合) + sql.js(数据增删改查的封装)
- 创建集合之前,先设计集合结构(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)
- 接收到的表单数据,默认为字符数据,需要将对应字段修改成数值数据,如:price,sales,stock,discount,score
- 注意:此处修改数据,是为了符合数据库集合创建时设定的字段类型
- 且此时缺少商品id字段,为了保证id字段绝对不会重复,此时使用第三方模块:
node-uuid
- 下载:
npm i node-uuid
- 引入:
const uuid = require("node-uuid");
- 使用:
uuid.v1()
// 会自动生成唯一的id值
- 引入集合
const pro = require("./../pro.js");
- 引入数据操作的封装
const sql = require("./../sql.js");
- 开始数据的插入操作:
- 在插入数据成功后,注意设置路由重定向:
四、查询数据并渲染页面
- 初始打开商品数据页面时,应立即读取数据库信息,然后渲染页面
- 在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文件,导入数据
- 在商品信息管理页面(pro.ejs),添加导入信息按钮
<a href="./pro/upload" class="btn btn-primary">导入信息</a>
- 设置点击导入按钮的跳转路由
- 在routes/pro.js中添加新的路由处理
router.get('/upload',(req, res, next)=>{ ... });
- 需要借助node解析excel的第三方模块:node-xlsx
- 下载:
npm i node-xlsx
- 引入:
const xlsx = require("node-xlsx");
- 使用:
const excelData = xlsx.parse("excel文件的绝对路径");
- 在upload路由处理中,解析文件数据,插入数据库,重定向路由
六、删除数据并删除数据库信息
- 点击删除按钮时,需将goodsId提交到指定删除路由
href="./pro/rm?id=<%= data[i].goodsId %>"
- 设置路由,接收商品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. 增加更新页面及其功能
- 过程可参考添加页面及其功能
- 处理数据并更新数据库信息
2. 数据排序
- 添加排序按钮,跳转到排序路由
- 设置排序路由
- 根据排序规则,查找数据,渲染页面
3. 数据搜索
- 设置搜索布局,跳转到搜索路由
- 设置搜索路由
- 根据搜索关键字,查询数据,渲染页面
4. 分页
- 设置分页布局,跳转到分页路由
- 设置分页路由
- 根据分页信息,查询数据,渲染页面
八、banner管理
1. 前端上传base64数据
- 前端图片上传
- 客户端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");
obtn.onclick = function(){ var reader = new FileReader(); reader.readAsDataURL(f.files[0]); reader.onload = function () { console.log(this.result); } }
|
- 图片管理
- banner接口实现
2. 前端直接上传文件
multer向服务器上传文件
九、管理员登录
1. 设计管理员信息数据库
- 准备数据库,在myapp项目中创建管理员信息数据库集合:admin.js(创建集合)
- 并设计集合结构(admin.js):
1 2 3 4 5 6 7 8
| { userId:{type:String}, userName:{type:String}, passWord:{type:String}, power:{type:Number}, avatar:{type:String}, lastTime:{type:String} }
|
- 将集合模块暴露,准备实现数据的操作
- 使用uuid模块生成数据的唯一id
- 使用md5模块,对用户密码进行加密操作
- 设计权限状态:1为普通管理员,0位超级管理员
- 插入测试数据(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(), userName:"liyang", passWord:md5("123456"), power:1 },(err)=>{ if(err) throw err; console.log("管理员添加成功"); })
|
2. 增加登陆页面及路由
- 在views中创建
login.ejs
,处理布局。
- 注册login页面的路由信息
- 在login路由中添加
/handle
,用来处理登录验证,修改login.ejs中登录的提交地址为/login/handle
- 在
/login/handle
的路由处理中,根据接收到的用户信息,请求数据库,请求到数据,表示登录成功,否则表示登录失败
- 登录成功,存储登录成功之后的状态,同时路由重定向到首页
- 登录失败,路由重定向回登录页面
3. 状态存储
- 通过cookie
- 存:
res.cookie("isLogin","ok");
- 读:
req.cookies.isLogin
- 通过session
- 下载session模块:
npm install express-session
- 配置session信息:
1 2 3 4 5 6
| app.use(session({ secret: '加密字段', resave: false, saveUninitialized: true, cookie: { maxAge: 1000 * 30 * 60 } }))
|
- 存:`req.session.isLogin = "ok";`
- 读:`req.session.isLogin`
- 通过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. 权限验证
- 将用户权限值一同存在session
- 需要权限验证时,获取session中的权限信息
- 将权限信息传给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