node.js项目
前端项目构建
npm install -g gulp-cli
gulp -v
gulpfile | package.json文件拷贝
yarn (安装package.json指明的所有的依赖项)
运行了一下gulp,发现报错了! 原因是因为缺少入口文件!
src/libs (放置一些插件,例如jquey.min.js)
/styles (放置一些scss文件,例如app.scss)
/scripts/app.js (js文件)
/static (放置一些静态资源、例如图片、字体文件)
index.html (静态页面)
后续可以借助 : https://adminlte.io/themes/AdminLTE/starter.html 模板
https://adminlte.io/themes/AdminLTE/documentation/
index.html里面的路径css/js引入正确了。
将app.js文件在index.html中引入。
<!--引入scripts/app.js文件-->
<script src="./scripts/app.js"></script>
app.js
const homeTpl = require("./views/home.html")
// console.log(homeTpl) //"<div>adadsaasd</div>"
$(".content").html(homeTpl)
报错:
You may need an appropriate loader to handle this file type, currently no loaders are configured to process
this file. See https://webpack.js.org/concepts#loaders
缺少对于以html文件处理的 laoder装载器。
gulpfile.js
module: {
rules: [
{
test: /\.html$/, //只要发现以html为后缀名的文件,采用stirng-loader装载器处理
use: 'string-loader' //就会将hmtl文件处理成字符串
}
]
}
实现左侧导航点击切换效果:
//点击左侧导航栏实现首页与职位列表页面的切换
$(".sidebar-menu li").on("click",function(){
$(this).addClass("active").siblings().removeClass("active")
let linkAttr = $(this).attr("link") //this.getAttribute("link")
switch(linkAttr){
case "home.html":
$(".content").html(homeTpl)
break;
case "position.html":
$(".content").html(posTpl)
break;
}
})
直接将素材/positions.html文件替换到views/positions.html
点击提交:
//点击提交
$(".content").on("click","#possubmit",function(){
//获取到表单的值 将表单的所有数据序列化成字符串
let data = $("#possave").serialize()
//需要将表单数据进行ajax提交
$.ajax({
url:"http://localhost:3000/api/position/add",
method:"post",
data,
success:data=>{
console.log("data==>",data)
}
})
})
后端项目构建
npm install -g express-generator
express -e
yarn
routes/positions.js
const express = require("express")
const router = express.Router()
//创建接口
router.post("/add",(req,res)=>{
res.send("进入了哦...")
})
module.exports = router;
需要在app.js入口文件里面引入Position.js这个路由对象,然后进行注册。
var positionRouter = require("./routes/positions")
app.use("/api/position",positionRouter)
使用postman进行测试:选择post请求: http://localhost:3000/api/position/add
当你请求的时候,就会app.js ===> positionRouter ===> add
后续的实现了mvc分层,业务层放入到controller里面进行处理
routes/posiitons.js
const express = require("express")
const router = express.Router()
const posController = require("../controller/posController")
//创建接口 http://localhost:3000/api/position/add
router.post("/add",posController.add)
module.exports = router;
controller/posController.js
const add = (req,res)=>{
res.send("进入了哦...")
}
module.exports = {
add
}
连接mongodb数据库
yarn add mongoose
utils/db.js
//引入mongoose模块
const mongoose = require("mongoose")
//创建连接 test数据库的名称
mongoose.connect('mongodb://localhost:27017/lagou',{
useNewUrlParser: true,
useUnifiedTopology: true
})
module.exports = mongoose
model/posModel.js
//引入db
const db = require("../utils/db")
//创建约束
const positionSchema = db.Schema({
companyName:{type:String,required:true},
positionName:{type:String,required:true},
city:{type:String,required:true},
salary:{type:String,required:true},
type:{type:String,required:true},
experience:{type:String,required:true},
degree:{type:String,required:true},
description:{type:String,required:true}
})
//创建集合
const Position = db.model('positions',positionSchema)
//添加职位操作
const save = function(){
Position.insertMany({
companyName:"千锋",
positionName:"讲师",
city:"北京",
salary:"30K",
type:"全职",
experience:"8年",
degree:'本科',
description:'aldjalsd'
},(err)=>{
if(!err){
console.log("插入成功...")
}
})
}
module.exports = {
save
}
posController.js
const posModel = require("../model/posModel")
const add = (req,res)=>{
//需要调用posModel的save方法,往数据库里面添加职位信息
posModel.save()
res.send("进入了哦...")
}
module.exports = {
add
}
当你在浏览器:http://localhost:3000/api/position/add
实现添加职位入库操作
// const save = function(data){
// return Position.insertMany(data,(err)=>{
// if(!err){
// console.log("插入成功...")
// return true
// }else{
// console.log("插入失败")
// return false;
// }
// })
// }
发现这种写法在posController中调用获取不到save函数的返回结果,一直是undefined.
Position.insertMany这个方法是异步的,所以内部函数不能获取到异步函数返回的结果,直接一直都是undefined结果。
const save = function(data){
return Position.insertMany(data)
.then(function(res){return true})
.catch(err=>false)
}
save方法就会返回一个Promise对象。(因为mongodb数据库操作都是异步的,没有办法直接获取到最终结果)
如何获取到一个处于pengding状态的promise对象的resolve或者reject的结果呢?
可以采用es6中的async函数解决,通过await关键字获取到处于pending状态的promise的结果。
const add = async (req,res)=>{
//await 经常发现一个promise对象的场景
const flag = await posModel.save(req.body) //正在往数据库写入数据的一个状态
console.log("flag===>",flag)
res.send("进入了哦...")
}
const add = async (req,res)=>{
//需要调用posModel的save方法,往数据库里面添加职位信息
//save方法现在返回一个 Promise { <pending> }
const flag = await posModel.save(req.body)
if(flag){ //添加职位成功
res.send({flag:true,data:{message:'success'}})
}else{
res.send({flag:false,data:{message:'fail'}})
}
}
后续为了统一接口返回结果,最好搞一个ejs模板进行后端接口的返回,目的就是为了方便控制,传递数据。
const add = async (req,res)=>{
//需要调用posModel的save方法,往数据库里面添加职位信息
//save方法现在返回一个 Promise { <pending> }
const flag = await posModel.save(req.body)
if(flag){ //添加职位成功
res.render("api.succ.ejs",{ //aaaa是给api.succ.ejs模板传递过去的参数
aaaa:JSON.stringify({message:'success'})
})
}else{
res.render("api.fail.ejs",{
aaaa:JSON.stringify({message:'fail'})
})
}
}
{
"flag":true,
"data":<%-aaaa %>
}
前端提交数据给接口,发现跨域报错了。通过代理的方式解决跨域问题
gulpfile.js
middleware: [
proxy('/api', { //http://localhost:3000/api/position/add
target: 'http://localhost:3000',
// changeOrigin: true
})
]
app.js
//点击提交
$(".content").on("click","#possubmit",function(){
//获取到表单的值
let data = $("#possave").serialize()
//需要将表单数据进行ajax提交
$.ajax({
url:"/api/position/add",
method:"post",
data,
dataType:"json", //后端给前端返回类型 (后端的ejs模板双引号!)
success:data=>{ //需要注意,默认data是string类型,所以需要变成object类型。
if(!data.flag){
alert("添加职位失败!")
}else{
$(".content").html(posTpl)
}
}
})
})
查询所有职位列表
//查询所有职位
function getPosTable(){
$(".content").html(posTpl)
//发送ajax 从数据库里面查询职位
$.ajax({
url:"/api/position/find",
dataType:"json",
success:data=>{
console.log("查询到的职位:",data)
}
})
}
需要制造find接口
router.get("/find",posController.find)
posController.js
//查询职位信息
const find = async (req,res)=>{
//调用模型层的查询职位方法
const data = await posModel.find()
res.render("api.succ.ejs",{
data:JSON.stringify(data)
})
}
module.exports = {
add,
find
}
posModel.js
//查询所有的职位信息
const find = function(){
return Position.find()
}
module.exports = {
save,
find
}
实现职位显示:
//查询所有职位
function getPosTable(){
$(".content").html(posTpl)
//发送ajax 从数据库里面查询职位
$.ajax({
url:"/api/position/find",
dataType:"json",
success:data=>{
if(data.flag){
const arr = data.data.map(item=>{
return `<tr>
<td>${item.companyName}</td>
...........
</tr>`
})
$(".table").append(arr)
}
}
})
}
但是这种方式会根据查询到的职位数组,动态生成多个tr元素,再去进行append追加操作,效率比较低。
使用artTemplate模板来去进行渲染。
index.html页面:
<!--引入artTemplate模板引擎解决-->
<script src="./libs/template-web.js"></script>
//查询所有职位
function getPosTable(){
// $(".content").html(posTpl)
//发送ajax 从数据库里面查询职位
$.ajax({
url:"/api/position/find",
dataType:"json",
success:data=>{
if(data.flag){
//注意:template的render函数返回一个拼状好的一个新的页面。
const html = template.render(posTpl,{
data:data.data
})
$(".content").html(html)
}
}
})
}
Position.html
默认遍历数组,每一项
$value 数组中每一项
$index 数组中下标默认从0开始
{{each data item i}}
<tr>
<td>{{i+1}}</td>
<td><img width="50" height="50"
src="https://www.lgstatic.com/i/image3/M00/12/AF/CgpOIFpu7ROAU0UaAAAvwWv_H_w082.jpg" alt="">
</td>
<td>{{item.companyName}}</td>
<td>{{item.positionName}}</td>
<td>{{item.city}}</td>
<td>今天20:22</td>
<td>{{item.salary}}</td>
<td>
<button class="btn btn-sm btn-primary pos-edit" posid="{{item._id}}"><span
class="fa fa-edit"></span> 修改</button>
<button class="btn btn-sm btn-danger pos-remove" posid="{{item._id}}"><span class="fa fa-remove"></span> 删除</button>
</td>
</tr>
{{/each}}
修改职位功能
//点击修改
$(".content").on("click",".pos-edit",function(){
let posId = $(this).attr("posid")
//根据posId然后进行查询对应的职位信息,进行表单回填操作
$.ajax({
url:"/api/position/"+posId,
dataType:"json",
success:data=>{
console.log("data===>",data)
}
})
//跳转到修改页面
$(".content").html(posUpdateTpl)
})
接下来需要创造 /api/position/:id
router.get("/:id",posController.findById)
posController.js
//根据id查询具体职位信息
const findById = async (req,res)=>{
const data = await posModel.findById(req.params.id)
res.render("api.succ.ejs",{
data:JSON.stringify(data)
})
}
module.exports = {
add,
find,
findById
}
posModel.js
//根据传入的id,调用mongoose底层查询具体对象的方法
const findById = function(id){
return Position.findById(id)
}
module.exports = {
save,
find,
findById
}
//点击修改
$(".content").on("click",".pos-edit",function(){
let posId = $(this).attr("posid")
//根据posId然后进行查询对应的职位信息,进行表单回填操作
$.ajax({
url:"/api/position/"+posId,
dataType:"json",
success:data=>{
if(data.flag){
const html = template.render(posUpdateTpl,{
data:data.data //根据id查询的具体职位对象data传递给了posUpdateTpl
})
$(".content").html(html)
}
}
})
//跳转到修改页面
// $(".content").html(posUpdateTpl)
})
在posUpdate.html页面中对于data对象进行取值,表单回填。
<input value={{data.companyName}} type="text" class="form-control" name="companyName" id="companyName" placeholder="请输入公司名称">
点击修改的提交,发现还是添加数据。
position.add.html
<button from="add" type="button" id="possubmit" class="btn btn-info pull-right">提交</button>
posiiton.update.html
<button from="update" type="button" id="possubmit" class="btn btn-info pull-right">提交</button>
//点击提交
$(".content").on("click","#possubmit",function(){
//获取到表单的值
let data = $("#possave").serialize()
let url = $(this).attr("from") === "add" ? "/api/position/add" : "/api/position/update"
//需要将表单数据进行ajax提交
$.ajax({
url,
method:"post",
data,
dataType:"json", //后端给前端返回类型
success:data=>{
if(!data.flag){
alert("职位操作失败!")
}else{
getPosTable()
}
}
})
})
/api/position/update (post请求)
router.post("/update",posController.update)
const update = async (req,res)=>{
// 需要获取外部传入的id,req.body所有更新的内容
let flag = await posModel.update(req.body.id,req.body)
if(flag){
res.render("api.succ.ejs",{
data:JSON.stringify({message:'success'})
})
}else{
res.render("api.fail.ejs",{
data:JSON.stringify({message:'fail'})
})
}
}
注意:需要前端 position.update.html页面中传入唯一的id给后端才可以
<!--在隐藏域里面将id字段传递给后端-->
<input type="hidden" name="id" value={{data._id}}>
const update = function(id,data){
// return Position.findOneAndUpdate({_id:id},data)
return Position.findByIdAndUpdate(id,data)
.then(res=>true)
.catch(err=>false)
}
module.exports = {
save,
find,
findById,
update
}
上传企业Logo
选择图片进行上传,发现后端通过req.body获取不到上传的图片logo信息。原因是因为jquery的默认的ajax方法是不支持表单图片上传。
引入插件jquery.form.min.js插件解决这个问题。
index.html文件里面进行插件引入
position.add.html 记住:只要表单上传图片操作,必须设置enctype=‘multipart/form-data’
<form class="form-horizontal" id="possave" action="/api/position/add" method="post" enctype="multipart/form-data">
app.js文件中,通过jquery.form.min.js里面提供的核心方法ajaxSubmit方法实现上传图片操作。
//点击提交
$(".content").on("click","#possubmit",function(){
let options = {
dataType:"json",
success:data=>{
if(!data.flag){
alert("职位操作失败!")
}else{
getPosTable()
}
}
}
$("#possave").ajaxSubmit(options)
}
前端已经可以将图片与utf-8数据一起传递给了后端了,但是目前后端没有获取到前端传递来的图片与数据。
后端可以通过multer模块,来去处理前端传递来的图片与utf-8数据。
Multer 是一个 node.js 中间件,用于处理
multipart/form-data
类型的表单数据,它主要用于上传文件。它是写在 busboy 之上非常高效。
yarn add multer
positions.js
const express = require("express")
const router = express.Router()
const multer = require("multer")
const path = require("path")
const up = path.resolve(__dirname,"../public/upload")
const upload = multer({dest:up}) //指明上传后的图片放入到了public/upload/xxxx
const posController = require("../controller/posController")
router.post("/add",upload.single('companyLogo'),posController.add)
posController的add方法中,可以通过req.body获取utf-8数据,req.file获取到上传的图片信息。
const add = async (req,res)=>{
console.log("addd=>",req.body,req.file)
//将上传的图片进行改名操作
const up = path.resolve(__dirname,"../public/upload") //找到upload所在的文件路径
const dot = req.file.originalname.lastIndexOf(".")
const fileSuffix = req.file.originalname.substr(dot) //dog.png ==> ".png"
const prevFilePath = path.resolve(up,req.file.filename) //获取之前的upload里面的图片路径
const nextFilePath = path.resolve(up,req.file.filename+fileSuffix) //希望变的图片路径
fs.renameSync(prevFilePath,nextFilePath) //通过fs模块的renameSync方法改名字
//需要调用posModel的save方法,往数据库里面添加职位信息
//save方法现在返回一个 Promise { <pending> }
const flag = await posModel.save(req.body)
if(flag){ //添加职位成功
// res.send({flag:true,data:{message:'success'}})
res.render("api.succ.ejs",{
data:JSON.stringify({message:'success'})
})
}else{
res.render("api.fail.ejs",{
data:JSON.stringify({message:'fail'})
})
}
}
希望Position.html里面显示upload的图片,如何实现?
<td><img width="50" height="50"
src="http://localhost:3000/upload/{{item.companyLogo}}" alt="">
</td>
接下来,需要在数据库posModel中存一个数据字段,叫做companyLogo
const positionSchema = db.Schema({
companyName:{type:String,required:true},
positionName:{type:String,required:true},
city:{type:String,required:true},
salary:{type:String,required:true},
type:{type:String,required:true},
experience:{type:String,required:true},
degree:{type:String,required:true},
description:{type:String,required:true},
companyLogo:{type:String}
})
点击添加的时候,进入posController的add方法中,需要在req.body上面动态的添加companyLogo
//将上传的图片进行改名操作
.......
//往req.body上面动态的添加一个companyLogo字段
req.body.companyLogo = req.file.filename + fileSuffix
点击修改:
position.update.html
<form class="form-horizontal" id="possave" action="/api/position/update" method="post" enctype="multipart/form-data">
position.js
router.post("/update",upload.single('companyLogo'),posController.update)
后续一样的代码,封装成uploadFile
const uploadFile = (req,res)=>{
if(req.file){ //上传图片了,再去进行操作
const dot = req.file.originalname.lastIndexOf(".")
const fileSuffix = req.file.originalname.substr(dot) //".png"
const prevFilePath = path.resolve(__dirname,"../public/upload",req.file.filename)
const nextFilePath = path.resolve(__dirname,"../public/upload",req.file.filename+fileSuffix)
fs.renameSync(prevFilePath,nextFilePath)
req.body.companyLogo = req.file.filename + fileSuffix
}else{ //没有上传图片,图片的companyLogo还是用之前
req.body.companyLogo = req.body.logo
}
}
const update = async (req,res)=>{
uploadFile(req,res)
// 需要获取外部传入的id,req.body所有更新的内容
let flag = await posModel.update(req.body.id,req.body)
if(flag){
res.render("api.succ.ejs",{
data:JSON.stringify({message:'success'})
})
}else{
res.render("api.fail.ejs",{
data:JSON.stringify({message:'fail'})
})
}
}
position.update.html文件中:
<input type="hidden" name='logo' value={{data.companyLogo}}>
目的就是如果不上传图片的话,logo的名字就是数据库中之前的图片的名字,后续进入到posController中update方法的时候,没有上传图片的话就可以将req.body.companyLogo = req.body.logo
前端注册功能实现
//注册登录
let isSignin = false
let greeting = "hello world"
//生成登录注册视图
renderTpl(isSignin,greeting)
function renderTpl(isSignin,greeting){
let html = template.render(userInfoTpl,{
isSignin,
greeting
})
$(".user-menu").html(html)
}
//点击注册
$(".navbar-nav").on("click","#btn-signup",function(e){
$("#user-submit").on("click",async function(){ //点击提交按钮
//获取输入框的值
let username = $("#username").val()
let password = $("#password").val()
let result = await sign({username,password},"signup")
console.log("result===>",result)
})
})
//发起请求给后端
function sign(data,uri){
return $.ajax({
url:"/api/user/"+uri,
data,
type:"post",
dataType:"json",
success:data=>{
return data
}
})
}
需要写后端接口 /api/user/signup 这个注册接口 (post请求)
app.js文件:
app.use('/api/user', usersRouter);
routes/users.js
router.post('/signup', function(req, res, next) {
res.send("/api/user/signup")
});
routes/users.js
router.post('/signup',userController.signup);
controller/usersController.js
//注册
const signup = async (req,res)=>{
let flag = await userModel.save(req.body)
console.log("flag===>",flag) //插入成功为true,插入失败false
res.send("/api/user/signup!")
}
model/usersModel.js
//引入db
const db = require("../utils/db")
//创建约束
const userSchema = db.Schema({
username:{type:String,required:true},
password:{type:String,required:true}
})
//创建集合
const Users = db.model('users',userSchema)
//用户注册的方法
const save = function(data){
return Users.insertMany(data)
.then(res=>true)
.catch(err=>false)
}
module.exports = {
save
}
插入的时候,发现可以插入多个同名用户,可否根据用户名唯一呢?
model/userModel.js
const findOne = function(data){
return Users.findOne(data)
}
controller/usersController.js
//注册
const signup = async (req,res)=>{
let username = req.body.username;
//根据用户名进行查询,如果返回null说明此用户没有存在数据库中,可以进行插入操作,否则返回具体用户信息
let flag = await userModel.findOne({username})
if(flag){ //数据库里面用此用户名
res.render("api.fail.ejs",{
data:JSON.stringify({message:"此用户名已经存在了,请重新注册!"})
})
}else{ //数据库里面没有此用户名
let flag = await userModel.save(req.body)
flag ? res.render("api.succ.ejs",{
data:JSON.stringify({message:"恭喜您:注册成功"})
})
:
res.render("api.fail.ejs",{
data:JSON.stringify({message:"注册失败!"})
})
}
}
//点击注册
$(".navbar-nav").on("click","#btn-signup",function(e){
$("#user-submit").off("click").on("click",async function(){
//获取输入框的值
let username = $("#username").val()
let password = $("#password").val()
let result = await sign({username,password},"signup")
console.log("result===>",result)
if(!result.flag){
alert(result.data.message)
}else{
alert("注册成功了!")
}
})
})
登录功能的实现
//点击登录
$(".navbar-nav").on("click","#btn-signin",function(e){
$("#user-submit").off("click").on("click",async function(){
//获取输入框的值
let username = $("#username").val()
let password = $("#password").val()
let result = await sign({username,password},"signin")
if(result.flag){
renderTpl(true,"您好!"+result.data.username)
}else{
alert(result.data.message)
}
})
})
users.js
router.post("/signin",userController.signin)
usersController.js
const signin = async(req,res)=>{
let flag = await userModel.findOne(req.body)
if(flag){
res.render("api.succ.ejs",{
data:JSON.stringify({message:"恭喜您:登录成功",username:req.body.username})
})
}else{
res.render("api.fail.ejs",{
data:JSON.stringify({message:"用户名或者密码输入错误..."})
})
}
}