Node.js
Express
MongoDB(Mongoose)
webpack作用:对文件进行打包
webpack官网:https://webpack.js.org/
初始化:yarn init -y
添加包:yarn add webpack -D
, yarn add webpack-cli -D
(本项目使用的webpack版本为4.44.2,webpack5.0版本和4.x版本有很大区别,所以建议使用 yarn add webpack@4.44.2 -D
yarn add webpack-cli@3.3.12
)
查看帮助:webpack --help
webpack 是使用文件进行配置的,所以创建一个配置文件:webpack.config.js
开始测试:
webpack.config.js
const path = require('path')
module.exports = {
//配置入口
entry: {
app: './src/app.js'
},
//配置出口
output: {
//注意必须写物理路径
path: path.join(__dirname, './dist'),
filename: 'app.js'
}
}
src/app.js
console.log(0)
运行:npx webpack
yarn add html-webpack-plugin@4.4.0 -D
webpack.config.js添加插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
//...
//配置插件
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, './public/index.html'),
filename: 'index.html',
inject: true
})
]
}
"scripts": {
"build":"npx webpack"
}
yarn build
yarn add webpack-dev-server@3.11.2 -D
webpack.config.js
module.exports = {
//...
//配置dev-server
devServer: {
contentBase: path.join(__dirname, 'dist'),
//是否压缩
// compress: true,
port: 8080
}
}
运行:npx webpack-dev-server -y
为了简化,我们继续写一个脚本: "dev": "npx webpack-dev-server"
npm i copy-webpack-plugin@6.4.1 -S -D
或者yarn add copy-webpack-plugin -S -D
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
//{ from: "source", to: "dest" },
//{ from: "other", to: "public" },
from: path.resolve(__dirname, './public/favicon.ico'),
to: 'dist'
],
}),
],
};
package.json
{
"devDependencies": {
"@webpack-cli/serve": "^1.7.0",
"copy-webpack-plugin": "6.4.1",
"html-webpack-plugin": "4.4.1",
"webpack": "4.41.5",
"webpack-cli": "3.3.10",
"webpack-plugin": "^1.0.5"
},
"scripts": {
"build": "npx webpack",
"dev": "npx webpack-dev-server"
},
"dependencies": {
"webpack-dev-server": "3.10.1"
},
}
module.exports = {
//...
devtool: 'source-map',
}
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
//配置环境
mode: 'development',
devtool: 'source-map',
//配置入口
entry: {
app: './src/app.js'
},
//配置出口
output: {
//注意必须写物理路径
path: path.join(__dirname, './dist'),
filename: 'app.js'
},
//配置插件
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, './public/index.html'),
filename: 'index.html',
inject: true
}),
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, './public/favicon.ico'),
to: 'dist'
},
],
}),
],
//配置dev-server
devServer: {
contentBase: path.join(__dirname, 'dist'),
//是否压缩
// compress: true,
port: 8080
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lagou-admin</title>
<link rel="stylesheet" href="./libs/css/bootstrap.min.css">
<!-- Font Awesome -->
<link rel="stylesheet" href="./libs/css/font-awesome.min.css">
<!-- Ionicons -->
<link rel="stylesheet" href="./libs/css/ionicons.min.css">
<link rel="stylesheet" href="./libs/css/AdminLTE.min.css">
<link rel="stylesheet" href="./libs/css/skin-blue.min.css">
<link rel="stylesheet" href="./libs/css/blue.css">
</head>
<body class="hold-transition skin-blue sidebar-mini login-page">
<div id="root"></div>
<script src="./libs/js/jquery-2.2.3.min.js"></script>
<script src="./libs/js/jquery.form.min.js"></script>
<script src="./libs/js/socket.io.js"></script>
<script src="./libs/js/bootstrap.min.js"></script>
<script src="./libs/js/app.min.js"></script>
<script src="./libs/js/icheck.min.js"></script>
</body>
</html>
npm i art-template art-template-loader -S -D
(也可以使用yarn安装)
webpack.config.js
//这个插件是用来每次打包前先清除原来的内容
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
//...
//配置loaders
module: {
rules: [
{
test: /\.art$/,
//排除部分
exclude: /(node_modules)/,
use: {
loader: 'art-template-loader'
}
}
]
},
//配置插件
plugins: [
//...
new CleanWebpackPlugin()
],
}
src/app.js
//es6导入模块的方法
import indexTpl from './views/index.art'
const html = indexTpl({})
$('#root').html(html)
安装sme-router:yarn add sme-router -S
创建路由:routers/index.js
import SMERouter from 'sme-router'
//es6导入模块的方法
import indexTpl from '../views/index.art'
import signinTpl from '../views/signin.art'
const router = new SMERouter('root')
const htmlIndex = indexTpl({})
const htmlSignin = signinTpl({})
// $('#root').html(signin)
router.route('/', (req, res, next) => {
res.render(htmlSignin)
})
router.route('/signin', (req, res, next) => {
res.render(htmlSignin)
})
export default router
src/app.js
//载入路由
import router from './routers'
router.go('/')
src/controller/index.js
//es6导入模块的方法
import router from '../routers'
import indexTpl from '../views/index.art'
import signinTpl from '../views/signin.art'
const htmlIndex = indexTpl({})
const htmlSignin = signinTpl({})
const _handleSubmit = (router) => {
return (e) => {
e.preventDefault()
router.go('/index')
}
}
const signin = (router) => {
return (req, res, next) => {
res.render(htmlSignin)
$('#signin').on('submit', _handleSubmit(router))
}
}
const index = (router) => {
return (req, res, next) => {
res.render(htmlIndex)
}
}
export {
signin,
index
}
signin.art
<div class="login-box">
<div class="login-logo">
<a href="index2.html"><b>拉勾网</b>后台管理系统</a>
</div>
<!-- /.login-logo -->
<div class="login-box-body">
<p class="login-box-msg">请登录</p>
<form id="signin" action="">
<div class="form-group has-feedback">
<input type="text" name="username" class="form-control" placeholder="用户名">
<span class="glyphicon glyphicon-envelope form-control-feedback"></span>
</div>
<div class="form-group has-feedback">
<input type="password" name="password" class="form-control" placeholder="密码">
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
</div>
<div class="row">
<!-- /.col -->
<div class="col-xs-4">
<button type="submit" class="btn btn-primary btn-block btn-flat" >登录</button>
</div>
<!-- /.col -->
</div>
</form>
</div>
<!-- /.login-box-body -->
</div>
<!-- /.login-box -->
routers/index.js
import SMERouter from 'sme-router'
const router = new SMERouter('root')
import { signin, index } from '../controller'
// $('#root').html(signin)
router.route('/', signin(router))
router.route('/index', index(router))
router.route('/signin', signin(router))
export default router
yarn add css-loader@5.0.1 style-loader@2.0.0 -D
webpack.config.js
module.exports = {
//...
//配置loaders
module: {
rules: [
{
test: /\.css$/,
loaders: ['style-loader', 'css-loader']
}
]
},
}
html,
body {
height: 100%;
}
#root {
height: 100%;
}
//...
const index = (router) => {
return (req, res, next) => {
res.render(htmlIndex)
//window.resize(),让页面撑满整个屏幕
$(window, '.wrapper').resize()
}
}
从这里开始步入后端
初始化yarn init -y
添加依赖yarn global add express-generator
初始化express -e
下载依赖:yarn install
or npm i
为了方便修改package.json里面的脚本scripts:start:nodemon ./bin/www
启动项目:yarn start
修改app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
// var indexRouter = require('./routes/index');
// var usersRouter = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// app.use('/', indexRouter);
// app.use('/users', usersRouter);
const usersRouter = require('./routes/users')
app.use('/api/users', usersRouter)
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
我们这里不需要用到默认的routes/index.js,所以把它删除掉
修改routes/users.js
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.post('/signup', function (req, res, next) {
res.send('respond with a resource');
});
module.exports = router;
http://localhost:3000/api/users/signup
controllers/users.js
const signup = (req, res, next) => {
res.send('hello')
}
exports.signup = signup
routes/users.js
var express = require('express');
var router = express.Router();
const { signup } = require('../controllers/users')
/* GET users listing. */
router.post('/signup', signup);
module.exports = router;
view/succ.ejs
{
"ret": true,
"errorCode": 0,
"data":<%- data %>
}
controllers/users.js
//注册用户
const signup = (req, res, next) => {
const { username, password } = req.body
//这里直接使用模板,第一个参数是模板模板名称succ.ejs
res.render('succ', {
data: JSON.stringify({ username, password })
})
}
exports.signup = signup
mongoose中文网地址:http://www.mongoosejs.net/
安装:yarn add mongoose
创建连接数据库的工具包:utils/db.js
// getting-started.js
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/lagou-admin');
创建数据库:use lagou-admin
kittens demo:
// getting-started.js
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/lagou-admin', {
useNewUrlParser: true,
useUnifiedTopology: true
});
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
//mongonDB里面的schema就相当于一个模型
var kittySchema = mongoose.Schema({
name: String
});
var Kitten = mongoose.model('Kitten', kittySchema);
var felyne = new Kitten({ name: 'Felyne' });
felyne.save()
console.log(felyne.name); // 'Felyne'
require('../utils/db')
//注册用户
const signup = (req, res, next) => {
const { username, password } = req.body
//这里直接使用模板,第一个参数是模板模板名称succ.ejs
res.render('succ', {
data: JSON.stringify({ username, password })
})
}
exports.signup = signup
yarn start
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzxDqL6Z-1668266731499)(D:\Typora\imgs\image-20220829225000443.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pUdXA9zz-1668266731499)(D:\Typora\imgs\image-20220829225020592.png)]
db.js
//...
//mongonDB里面的schema就相当于一个模型
var usersSchema = mongoose.Schema({
username: String,
password: String
});
//集合的名字
var Users = mongoose.model('users', usersSchema);
exports.Users = Users
我们把controller中对db.js的引用删除,提取出M层:model/users.js
const { Users } = require('../utils/db')
const signup = ({ username, password }) => {
const users = new Users({
username,
password
})
users.save()
}
//下面两种写法是等价的
// exports.signup = signup
module.exports = {
signup
}
controllers/users.js
const usersModel = require('../models/users')
//注册用户
const signup = (req, res, next) => {
const { username, password } = req.body
usersModel.signup({
username,
password
})
//这里直接使用模板,第一个参数是模板模板名称succ.ejs
res.render('succ', {
data: JSON.stringify({ username, password })
})
}
exports.signup = signup
controllers/users.js
const usersModel = require('../models/users')
//注册用户
const signup = async (req, res, next) => {
const { username, password } = req.body
//判断用户是否存在
let findResult = await usersModel.findUser(username)
console.log(findResult)
if (findResult) {
res.render('fail', {
data: JSON.stringify({
message: '用户名已存在'
})
})
} else {
//数据库里没有这个用户,开始添加用户
let result = await usersModel.signup({
username,
password
})
console.log(result)
}
//这里直接使用模板,第一个参数是模板模板名称succ.ejs
res.render('succ', {
data: JSON.stringify({ username, password })
})
}
exports.signup = signup
module/users.js
const { Users } = require('../utils/db')
const findUser = (username) => {
return Users.findOne({ username })
}
const signup = ({ username, password }) => {
const users = new Users({
username,
password
})
return users.save()
}
//下面两种写法是等价的
exports.signup = signup
exports.findUser = findUser
// module.exports = {
// signup
// }
views/fail.ejs
{
"ret": true,
"errorCode": -1,
"data":<%- data %>
}
安装:yarn add bcrypt
创建工具js:
utils/tools.js
const bcrypt = require('bcrypt')
exports.hash = (myPlaintextPassword) => {
return new Promise((resolve, reject) => {
bcrypt.hash(myPlaintextPassword, 10, function (err, hash) {
if (err) {
reject(err)
}
resolve(hash)
})
});
}
controllers/users.js
const { hash } = require('../utils/tools')
//注册用户
const signup = async (req, res, next) => {
//设置返回头
res.set('content-type', 'application/json; charset=utf-8')
//...
//加密后的密码
const bcryptPassword = await hash(password)
//...
} else {
//数据库里没有这个用户,开始添加用户
let result = await usersModel.signup({
username,
password: bcryptPassword
})
}
//...
hash(data,salt,cb)
salt:salt
- [REQUIRED] - the salt to be used to hash the password. if specified as a number then a salt will be generated with the specified number of rounds and used
rounds=8 : ~40 hashes/sec
rounds=9 : ~20 hashes/sec
rounds=10: ~10 hashes/sec
rounds=11: ~5 hashes/sec
rounds=12: 2-3 hashes/sec
rounds=13: ~1 sec/hash
rounds=14: ~1.5 sec/hash
rounds=15: ~3 sec/hash
rounds=25: ~1 hour/hash
rounds=31: 2-3 days/hash
安装插件:yarn add cors
app.js中使用中间件
var cors = require('cors')
app.use(cors())
webpack.config.js
module.exports = {
//...
//配置dev-server
devServer: {
//...
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:3000',
//pathRewrite: { "^/api": "" }
}
}
}
}
//...
router.get('/list', list)
//...
//用户列表
const list = async (req, res) => {
res.set('content-type', 'application/json; charset=utf-8')
const listResult = await usersModel.findList()
res.render('succ', {
data: JSON.stringify( listResult )
})
}
exports.list = list
//...
const findList = () => {
//排序
return Users.find().sort({ _id: -1 })
}
exports.findList = findList
{{each data}}
<tr>
<td>{{$index+1}}.</td>
<td>{{$value.username}}</td>
<td><button data-id="{{$value._id}}" class="btn btn-danger remove">删除</button></td>
</tr>
{{/each}}
import usersListTpl from '../views/users-list.art'
//...
const _list = () => {
$.ajax({
url: '/api/users/list',
success(result) {
$('#users-list').html(usersListTpl({
data: result.data
}))
//console.log(result)
}
})
}
const index = (router) => {
return (req, res, next) => {
//渲染首页
res.render(htmlIndex)
//window.resize(),让页面撑满整个屏幕
$(window, '.wrapper').resize()
//填充用户列表
$('#content').html(usersTpl())
//渲染list
_list()
//点击保存,提交表单
$('#users-save').html(usersTpl())
}
}
const _signup = () => {
const $btnClose = $('#users-close')
//提交表单
const data = $('#users-form').serialize()
console.log(data)
$.ajax({
url: '/api/users/signup',
type: 'post',
data,
success(res) {
console.log(res)
//刷新
_list()
}
})
}
<ul class="pagination pagination-sm no-margin pull-right" id="users-page-list">
<li><a href="#">«</a></li>
{{each pageArray}}
<li><a href="#">{{$index+1}}</a></li>
{{/each}}
<li><a href="#">»</a></li>
</ul>
import userListPageTpl from '../views/users-pages.art'
//...
const pageSize = 5
let dataList = []
const _loadData = () => {
return $.ajax({
url: '/api/users/list',
async: false,
success(result) {
dataList = result.data
//分页
_pagination(result.data)
}
})
}
const _pagination = (data) => {
const total = data.length
//往上取整
const pagesCount = Math.ceil(total / pageSize)
const pageArray = new Array(pagesCount)
const htmlPage = userListPageTpl({
pageArray
})
$('#users-page').html(htmlPage)
//默认添加第一个
$('#users-page-list li:nth-child(2').addClass('active')
$('#users-page-list li:not(:first-child , :last-child)').on('click', function () {
$(this).addClass('active').siblings().removeClass('active')
// console.log($(this).index())
_list($(this).index())
})
}
const _list = (pageNo) => {
let start = (pageNo - 1) * pageSize
$('#users-list').html(usersListTpl({
//截取数据
data: dataList.slice(start, start + pageSize)
}))
}
const index = (router) => {
return async (req, res, next) => {
//渲染首页
res.render(htmlIndex)
//window.resize(),让页面撑满整个屏幕
$(window, '.wrapper').resize()
//填充用户列表
$('#content').html(usersTpl())
//初次渲染list
_loadData()
_list(1)
//点击保存,提交表单
$('#users-save').html(usersTpl())
}
}
const _signup = () => {
const $btnClose = $('#users-close')
//提交表单
const data = $('#users-form').serialize()
console.log(data)
$.ajax({
url: '/api/users/signup',
type: 'post',
data,
sucdess: async (res) => {
console.log(res)
//提交数据后渲染
_loadData()
_list(1)
}
})
//单击关闭模拟框
$btnClose.click()
}
//...
backend/routes/users.js
var express = require('express');
var router = express.Router();
const { signup, list } = require('../controllers/users')
/* GET users listing. */
router.post('/', signup);
router.get('/', list)
// router.delete('/', remove)
module.exports = router;
frontend/controllers/index.js
//es6导入模块的方法
// import router from '../routers'
import indexTpl from '../views/index.art'
import signinTpl from '../views/signin.art'
import usersTpl from '../views/users.art'
import usersListTpl from '../views/users-list.art'
import userListPageTpl from '../views/users-pages.art'
const htmlIndex = indexTpl({})
const htmlSignin = signinTpl({})
const pageSize = 5
let dataList = []
const _handleSubmit = (router) => {
return (e) => {
e.preventDefault()
router.go('/index')
}
}
const _loadData = () => {
return $.ajax({
url: '/api/users',
async: false,
success(result) {
dataList = result.data
//分页
_pagination(result.data)
}
})
}
const _pagination = (data) => {
const total = data.length
//往上取整
const pagesCount = Math.ceil(total / pageSize)
const pageArray = new Array(pagesCount)
const htmlPage = userListPageTpl({
pageArray
})
$('#users-page').html(htmlPage)
//默认添加第一个
$('#users-page-list li:nth-child(2').addClass('active')
$('#users-page-list li:not(:first-child , :last-child)').on('click', function () {
$(this).addClass('active').siblings().removeClass('active')
// console.log($(this).index())
_list($(this).index())
})
}
const _list = (pageNo) => {
let start = (pageNo - 1) * pageSize
$('#users-list').html(usersListTpl({
//截取数据
data: dataList.slice(start, start + pageSize)
}))
}
const signin = (router) => {
return (req, res, next) => {
res.render(htmlSignin)
$('#signin').on('submit', _handleSubmit(router))
}
}
const index = (router) => {
return async (req, res, next) => {
//渲染首页
res.render(htmlIndex)
//window.resize(),让页面撑满整个屏幕
$(window, '.wrapper').resize()
//填充用户列表
$('#content').html(usersTpl())
//初次渲染list
_loadData()
_list(1)
//点击保存,提交表单
$('#users-save').html(usersTpl())
}
}
const _signup = () => {
const $btnClose = $('#users-close')
//提交表单
const data = $('#users-form').serialize()
console.log(data)
$.ajax({
url: '/api/users',
type: 'post',
data,
sucdess: async (res) => {
console.log(res)
//提交数据后渲染
_loadData()
_list(1)
}
})
//单击关闭模拟框
$btnClose.click()
}
const signup = () => {
}
export {
signin,
index
}