小电商项目实战
参考:http://www.hubwiz.com/course/549a704f88dba0136c371703/
设计
需求设计
1、功能:
- 注册、登录
- 登陆后,浏览商品,选购商品并加入购物车
- 购物车:增加、减少、删除;购物车结算
2、技术选型
项目涉及:Nodejs、Express框架、MongoDB数据库、Mongoose对象模型库
Nodejs
:采用Google Chrome浏览器的V8引擎,一个后端的Javascript运行环境,提供很多系统级的API,如文件操作、网络编程等。Express
:基于Node.js的Web应用开发框架, 支持Ejs、jade等多种模板,并且提供一系列强大的功能,比如:模板解析、静态文件服务、中间件、路由控制等等。MongoDB
:MongoDB是一个基于分布式文件存储的一个高性能,开源,无模式的文档型数据库,数据以BSON文档的格式存储在磁盘上。Mongoose
:是在node.js异步环境下对mongodb进行便捷操作的对象模型库,封装了MongoDB对文档的的一些增删改查等常用方法。
3、结构划分
- 用户注册模块:填写用户名、密码、确认密码;成功注册,自动登录;
- 用户登录模块:填写用户名、密码,正确后,登录成功,进行商品展示;
- 商品模块:用户选择相关产品加入购物车;
- 购物车模块:对购物车产品进行修改、添加、删除操作;
- 结算模块:对购物车内已选择商品进行结算。
4、流程设计
数据库设计
创建三个集合:user(用户)集合、commodity(商品)集合、cart(购物车)集合:
- user中字段:name、passord
- commodity中字段:name、price、imgSrc
- cart:uId(用户ID)、cId(商品ID)、cName(商品名称)、cPrice(商品价格)、cImgSrc(商品展示图片路径)、cQuantity(商品数量)、cStatus(商品结算状态,未结算为false,已结算为true)
功能实现
快速创建express项目的方法:创建第一个Node.js Express App
项目目录
- public目录:存放静态文件(js、css、img)。
- routes目录:存放路由文件。
- views目录: 存放页面文件(html)。
- common目录:存放公共文件。
公共方法
1)common/models.js
–> 用来保存各个集合
的Schema(以文件形式存储的数据库模型骨架–>表结构
),便于利用Schema创建–>Model,在数据库中创建真正的集合。
module.exports = {
user: {
name: { type: String, required: true },
password: { type: String, required: true }
},
commodity: {
name: String,
price: Number,
imgSrc: String
},
cart:{
uId: { type: String },
cId: { type: String },
cName: { type: String },
cPrice: { type: String },
cImgSrc: { type:String } ,
cQuantity: { type: Number },
cStatus : { type: Boolean, default: false }
}
};
2)common/dbHelper.js
–> 通过加载models.js
(集合结构),由Schema构造生成集合的Model模型(可以对数据库有实质性的操作)
var mongoose=require('mongoose'),//加载mongodb对象操作模型库
Schema=mongoose.Schema,
models=require('./models');
for(var m in models){
mongoose.model(m,new Schema(models[m]));//通过骨架,构造model
}
module.exports = {
getModel: function (type) {
return mongoose.model(type);
}
};
注册
1)app.js
:
//1、模块加载
var express=require("express"),
session=require("express-session"),
mongoose=require("mongoose"),
bodyParser = require('body-parser'),
multer = require('multer'),
ejs=require('ejs'),
path=require('path');
//定义全局变量dbHelper;在dbHelper中:mongoose操作Schema获取Model的方法
global.dbHelper = require( './common/dbHelper' );
//2、创建web实例-->app
var app=express();
//3、设定view engine变量,意为网页模板引擎;使用ejs模板引擎,处理后缀名为html文件
app.set('view engine','html');
app.engine('.html',ejs.__express);
//4、设定views变量,意为视图存放的目录
app.set('views',path.join(__dirname,'views'));
//5、指定静态资源访问路径
app.set(express.static(path.join(__dirname,'public')));
//6、挂载中间件
app.use(session({
secret:'secret',
cookie:{
maxAge:1000*60*30
},
resave:true,//重新保存:强制保存session即使它并没有变化。默认为true但是得写上
saveUninitialized:true//强制将未初始化的session存储。
}));
app.use(bodyParser.json());//解析application/json
app.use(bodyParser.urlencoded({ extended: false}));//解析application/x-www-form-urlencoded
app.use(multer());
//7、mongoose-->接mongodb数据库
global.db = mongoose.connect("mongodb://127.0.0.1:27017/test");
mongoose.connection.on('disconnected', function (error) {
console.log("数据库连接失败:" + error);
});
mongoose.connection.on('connected', function () {
console.log("succeed:数据库连接成功!");
});
//8、挂载路由中间件,在服务器给出响应之前,用于对HTTP请求不断加工
app.use(function(req,res,next){
res.locals.user = req.session.user;//保存用户信息
var err = req.session.error;//保存响应结果信息
res.locals.message = '';//保存html标签
if (err) res.locals.message = '<div class="alert alert-danger" style="margin-bottom: 20px;color:red;">' + err + '</div>';
next();
})
//9、挂载路由到指定路径
require('./routes/register.js')(app);
//10、指定监听端口:要么通过环境变量指定,环境变量没有指定则默认80端口
var port=process.env.PORT||80;
app.listen(port);
console.log('Magic happens on port ' + port);
2)路由:routes/register.js
:
module.exports=function(route){
route.get('/register',function(req,res){
res.render('register');
});
route.post('/register',function(req,res){
//通过全局变量dbHelper,使用自定义的getModel:得到集合-->user
var User=global.dbHelper.getModel('user');
//通过req.body,取得http-->post请求发送来的参数(依赖模块:body-parser)
var name=req.body.name;
//查找数据库中是否已经存在用户名,否:则利用user.create()向集合user中插入document
User.findOne({name:name},function(err,doc){
if (err) {
res.sendStatus(500);
req.session.error = '网络异常错误!';
console.log("error:"+err);
}
else if(doc){
req.session.error="用户名已经存在";
res.sendStatus(500);
}
else{
User.create({name:name,password:req.body.password},function(err,doc){
if(err){
console.log("error:"+err);
res.sendStatus(500);
}
else{
req.session.error = '用户名创建成功!';
res.sendStatus(200);
}
})
}
})
});
return route;
}
3)视图:views/register.html
:
body部分:(简单实现功能,没加样式)
<body>
<div>
<%- message %>
<div><h1>用户注册</h1></div>
<div>
<form action="#">
<label for="username">姓名:</label>
<input type="text" id="username" name="username" placeholder="用户名"/>
<br>
<label for="password">密码:</label>
<input type="password" id="password" name="password" placeholder="密码"/>
<br>
<label for="confirmpwd">确认密码:</label>
<input type="password" id="confirmpwd" name="confirmpwd" placeholder="确认密码"/>
<br>
<input type="button" value="注册" onclick="register()"/>
</form>
</div>
</div>
</body>
JavaScript部分:
<script>
function register(){
//1、得到表单中的值
var username=document.getElementById("username").value;
var password=document.getElementById("password").value;
var confirmpwd=document.getElementById("confirmpwd").value;
//2、正则表达式验证注册信息
var regex=/^[\s]+$]/;//^:匹配字符开始;$:匹配字符结束;\s:空格;+:一个或多个
//如果填写信息匹配空白字符串成功或者为空,则提示输入注册信息有问题
if(regex.test(username)||username.length==0){
return alert("用户名格式不正确");
}
if(regex.test(password)||password.length==0){
return alert("密码格式不正确");
}
if(password!=confirmpwd){
return alert("两次密码不一致");
}
//3、原生ajax发送post请求
//var data=JSON.stringify({name:username,password:password});
var data="name="+username+"&password="+password;
var request=new XMLHttpRequest();
request.open("POST","/register",true);
request.setRequestHeader('Content-Type','application/x-www-form-urlencoded');//设置编码
//request.setRequestHeader('Content-Type', "application/json");//设置编码
request.send(data);
request.onload=function(){
if(request.status==200){
alert("注册成功!");
var resData=request.responseText;
console.log(resData);
location.href='register';
}
else{
var resData=request.responseText;
console.log(resData);
location.href='register';
}
}
}
</script>
登录
1)路由:routes/login.js
:
module.exports=function(route){
route.get('/login',function(req,res){
if(req.session.user){
res.redirect('/home');//如果用户已经登录成功,转跳“/home”路由
}
else{
res.render('login');
}
});
route.post('/login',function(req,res){
var User = global.dbHelper.getModel('user');
var name=req.body.name;
//查找数据库中是否存在用户请求登录的用户名
User.findOne({name:name},function(err,doc){
if(err){
req.session.error = '网络异常错误!';
console.log(error);
res.sendStatus(500);
}
else if(doc){
if(doc.password==req.body.password){
req.session.error='登录成功!';
req.session.user=doc;
res.sendStatus(200);
}
else{
req.session.error='用户存在,密码不正确!';
res.sendStatus(404);
}
}
else{
req.session.error='用户名不存在!';
res.sendStatus(404);
}
})
});
return route;
}
2)视图:viewa/login.js
:
body部分:
<body>
<div>
<%- message %>
<div><h1>用户登录</h1></div>
<div>
<form action="#">
<label for="username">姓名:</label>
<input type="text" id="username" name="username" placeholder="用户名"/>
<br>
<label for="password">密码:</label>
<input type="password" id="password" name="password" placeholder="密码"/>
<br>
<input type="button" value="登录" onclick="login()"/>
<a href="register">注册</a>
</form>
</div>
</div>
</body>
javascript部分:
<script>
function login(){
var username=document.getElementById("username").value;
var password=document.getElementById("password").value;
var regex=/^[\s]+$]/;
if(regex.test(username)||username.length==0){
return alert("用户名格式不正确");
}
if(regex.test(password)||password.length==0){
return alert("密码格式不正确");
}
var data="name="+username+"&password="+password;
var request=new XMLHttpRequest();
request.open("POST","/login",true);
request.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
request.send(data);
request.onload=function(){
if(request.status==200){
alert("登录成功!");
var resData=request.responseText;
location.href='home';
}
else{
location.href='login';
}
}
}
</script>
商品展示
1)路由:home.js
2)视图:home.html
商品管理
包括商品的增删改查,四大部分。
1)路由:addcommodity.js
2)视图:addcommodity.html
购物车结算
遇到的问题及解决方法:
1、文件上传(Multer)
2、静态资源访问受限
浏览器出现错误提示:
Refused to apply style from 'http://localhost/stylesheets/bootstrap.min.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
GET http://localhost/images/xmsz-1.jpg 404 (Not Found)
解决方法:将指定静态资源访问路径的一条语句
app.set(express.static(path.join(__dirname,'public')));
改为:
app.use(express.static(path.join(__dirname,'public')));
3、文本节点(空白节点)
<ul id="ul">
<li></li>
<li></li>
</ul>
<script>
console.log(document.getElementById("ul").childNodes.length);
</script>
如上代码,有的浏览器输出2
,而有的浏览器输出为5
,这是因为存在文本节点
。
文本节点:换行,制表符这些特殊的文本字符组成,有的浏览器会忽略这些节点,有的浏览器则认可。
通常情况下我们都不需要空白节点影响获取节点,因此去除空白节点的方法:
function removeWhiteNode(node) {
for (var i = 0; i < node.childNodes.length; i++) {
if (node.childNodes[i].nodeType === 3 && /^\s+$/.test(node.childNodes[i].nodeValue)) {
node.childNodes[i].parentNode.removeChild(node.childNodes[i]);
}
}
return node;
}
4、点击表单提交按钮页面自动刷新(或者转跳)问题
场景:通过Ajax,用post方法提交表单的时候,点击确认提交
,提交成功后,页面总是自动刷新了
方法一:iframe
<iframe name="formsubmit" style="display:none;"></iframe>
<form action="#" target="formsubmit">
…………
</form>
方法二:阻止表单自动提交
event.preventDefault() //阻止form表单默认提交
方法三:<button>
标签的type属性不为submit
,改为:
<button type="button">提交</button>
5、<img>
自动缩放,并且不改变原本比例
<div style="width:100px;height:100px">
<img src="……" style="max-width:100%;max-height:100%"/>
</div>
通过给<img>
标签包裹设置了宽高的div,然后限制img的最大宽高,将img图片限制。
6、用js控制:bootstrap-modal模态框
一般情况下:
<!-- 按钮触发模态框 -->
<button type="button" data-toggle="modal" data-target="#modal">打开模态框</button>
<!-- 模态框(Modal) -->
<div id="modal" class="modal fade">
//code
</div >
用按钮触发模态框显示,关键是:
data-toggle="modal"
:用于打开模态框窗口data-target="#modal"
:打开的目标
如何用js控制模态框的打开,而不是点击按钮呢?
- 取消按钮的
data-toggle="modal"
和data-target="#modal"
两个属性的设置; - 在js中通过以下代码控制模态框的显示和隐藏:
$('#modal').modal('hide')
$('#modal').modal('show')
7、js控制:bootstrap-dropdown下拉菜单
一般情况:
<div class="dropdown">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">Dropdown button</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Link 1</a>
<a class="dropdown-item" href="#">Link 2</a>
<a class="dropdown-item" href="#">Link 3</a>
</div>
</div>
class="dropdown"
:包裹触发节点和下拉菜单,使它们位置上关联;class="dropdown-toggle"
:用于指定下拉菜单的触发节点;data-toggle="dropdown"
:用于点击触发下拉菜单显示,绑定这个动作;class="dropdown-menu"
:指定下拉菜单。
js控制下拉菜单的显示:
-
取消按钮的
data-toggle="dropdown"
属性设置;$(‘.dropdown-menu’).dropdown(“toggle”);
其中$('.dropdown-menu')
是触发节点。
问题:js控制下拉菜单后,怎么使其隐藏?网上有很多人说再次调用$('.dropdown-menu').dropdown("toggle");
,我试了试,不行。
解决:用jQuery中的slideDown()
和slideUp()
解决这个问题:
$('.dropdown-menu').slideDown();//显示下拉菜单
$(".dropdown-menu").slideUp();//隐藏下拉菜单