一、开发模式
MVC+MPA(koa art-template)+ssr
SPA(vue react) MPA(koa art-template)
SPA(vue react) — > 接口
1.1MVC
1.2入门案例
(一)入口文件
const Koa = require('koa');
const app = new Koa;
const path = require('path');
const render = require('koa-art-template');
//模板引擎
render(app,{
root:path.join(__dirname,'views'),
extname:'.html',
});
//开启路由
require('./controller')(app);
//监听
app.listen(8080,() => {
console.log('=====>>>','index running...');
})
(二)controller控制层
//处理数据的路由
const router = require('koa-simple-router');
const model = require('../models');
let controller = (app) => {
app.use(router(_ => {
_.get('/', (ctx, next) => {
ctx.render('view')
})
//get art-template
_.get('/index1', async (ctx, next) => {
let data = await model.db()
ctx.render('index1',{data})
})
_.get('/index2', async (ctx, next) => {
let data = await model.db1()
ctx.render('index2',{data})
})
}))
}
module.exports = controller
(三)models模型层
let data = {
db: () => {
return Promise.resolve('假装这是从数据库获取的数据库');
},
db1: () => {
return Promise.resolve('哈哈哈');
}
}
module.exports = data;
(四)view视图层
index1.html和index2.html一样
<body>
<h1>{{data}}</h1>
</body>
二、项目架构搭建
1、项目搭建
(一)首先初始化–npm init -y
(二)创建文件夹assets、components、config、controllers、models、views
(三)使用到Koa框架,先安装–npm i koa -S
2、入口文件
(一)创建app.js入口文件
const Koa = require('koa');
const app = new Koa();
//监听
app.listen(8080,() => {
console.log(' =====>','server running 8080 ...')
})
(二)在package.json中的"scripts"中加入
3、注册路由
(一)使用koa-simple-router路由
①npm install koa-simple-router
②在controller文件夹下创建index.js和indexControllers.js
③index.js并在其中导入官网用法
④indexControllers.js中创建类
//创建一个类
class IndexController {
constructor(){}
//方法
index(){
return (ctx, next) => {
ctx.body = 'hello word'
}
}
}
//导出
module.exports = IndexController;
⑤是index.js中接收并优化代码
const router = require('koa-simple-router')
//导入
const IndexController = require('./indexControllers.js');
//对类进行实例
const indexController = new IndexController();
//导出
module.exports = (app) => {
app.use(router(_ => {
_.get('/', indexController.index())
}))
}
⑥在入门文件app.js中开启路由
/开启路由
require('./controllers')(app)
注: 在npm官网搜索需要的路由,并使用给的用法
4、模板配置
(一)安装npm install --save art-template koa-art-template
(二)在app.js中
//模板引擎
const render = require('koa-art-template');
render(app, {
root: path.join(__dirname, 'views'),
extname: '.html',
});
(三)在views中创建index.html和layout.html文件【内容从官网copy】
官网地址
5、静态资源
(一)在assets下css中创建index.css和scripts中创建index.js
(二)安装npm install koa-static
const serve = require('koa-static');
//静态文件
app.use(serve(path.join(__dirname, 'assets')));
(三)在控制类IndexController中index()方法
return async function (ctx) {
await ctx.render('index');
}
(四)在index.html文件中引入对应的css和js
(五)在layout.html模板文件中引入vue,并在index.html中写
<div id="app">
<h2>${message}</h2>
<input type="text" v-model="message">
</div>
(六)在index.js中处理vue
注意:如果在Vue中没有设置delimiters:[’${’,’}’]的话,页面{{message}}会冲突
new Vue({
el:'#app',
data:{
message:'hello koa'
},
delimiters:['${','}']
})
6、错误处理
(一)创建middlewares文件夹并创建index.js
const middlewares = {
error(app){
app.use(async(ctx,next) => {
try{
await next();
}catch(err){
console.log(err);
ctx.status = 500;
ctx.body = "程序员好像出错了,请与程序员小哥联系!!!"
}
})
app.use(async(ctx,next) => {
await next();
if(ctx.status != 404) return;
ctx.status = 404;
ctx.body = `<script type="text/javascript" src="//qzonestyle.gtimg.cn/qzone/hybrid/app/404/search_children.js" charset="utf-8"></script>`
})
}
}
module.exports = middlewares;
(二)在app.js中
//错误处理
require('./middlewares').error(app)
7、错误日志
(一)在npm官网搜log4js并npm install log4js安装
(二)在入门文件app.js中
/*
记录错误的日志
*/
const log4js = require("log4js");
log4js.configure({
//appenders输出目的地
appenders: { cheese: { type: "file", filename: "logs/cheese.log" } },
//categories类别
categories: { default: { appenders: ["cheese"], level: "error" } }
});
const logger = log4js.getLogger("cheese");
// logger.error("Cheese is too ripe!");
(三)将logger传入错误处理中
(四)在错误处理文件index.js中
logger.error(err);
8、配置外依
(一)使用lodash工具库–先安装npm i --save lodash
//lodash工具库
const _ = require('lodash');
const path = require('path');
let config = {
"staticDir":path.join(__dirname,'..','assets'),
"templatesDir":path.join(__dirname,'..','views')
}
if(process.env.NODE_ENV == 'development'){
let localPort = {
port:8080
};
_.assignIn(config,localPort);
}
if(process.env.NODE_ENV == 'production'){
let prodPort = {
port:80
};
_.assignIn(config,prodPort);
}
module.exports = config;
(二)在app.js中导入const config = require(’./config’)并修改相关
(三)但是仍然无法访问,原因是如法进入if中判断是否为开发或者生产模式,解决方法使用新的cross-env
cross-env这是一款运行跨平台设置和使用环境变量的脚本。
①npm install --save-dev cross-env
②修改package.json文件中
三、图书馆案例修改功能展示[基于上面的配置]
使用模态框进行前端渲染,使用ajax处理逻辑,后台controller处理数据;
3.1主页面
解析: ①此处首先引入bootstrap、toastr、template、vue、jQuery
②引入模板和模态框
③引入自己的index.js文件
此处使用了模态框和模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>主页面</title>
<link rel="stylesheet" href="./css/admin.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<!-- 提示框样式 -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" rel="stylesheet" />
</head>
<body>
<div class="wrapper">
<h1>six 图书馆管理系统</h1>
<a class="btn btn-default" href="#" id="add">添加图书</a>
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>书名</th>
<th>作者</th>
<th>分类</th>
<th>描述</th>
<th>操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<!-- 模态框 -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<!-- == 模态框内容 ==================== -->
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">新增</h4>
</div>
<div class="modal-body">
<form id="form">
<div class="form-group">
<label for="txt_departmentname">书名</label>
<input type="text" name="name" class="form-control" id="txt_departmentname"
placeholder="书名">
</div>
<div class="form-group">
<label for="txt_parentdepartment">作者</label>
<input type="text" name="author" class="form-control" id="txt_parentdepartment"
placeholder="作者">
</div>
<div class="form-group">
<label for="txt_departmentlevel">分类</label>
<input type="text" name="category" class="form-control" id="txt_departmentlevel"
placeholder="分类">
</div>
<div class="form-group">
<label for="txt_statu">描述</label>
<input type="text" name="description" class="form-control" id="txt_statu" placeholder="状态">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal"><span
class="glyphicon glyphicon-remove" aria-hidden="true"></span>关闭</button>
<!-- <button type="button" id="btn_submit" class="btn btn-primary" data-dismiss="modal"><span
class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>保存</button> -->
<button type="button" id="btn_submit" class="btn btn-primary"><span
class="glyphicon glyphicon-floppy-disk"></span>提交</button>
</div>
<!-- == 模态框内容 ==================== -->
</div>
</div>
</div>
<!-- 模板 -->
<script type="text/template" id="template">
{{each data}}
<tr>
<td>{{$value.id}}</td>
<td>{{$value.name}}</td>
<td>{{$value.author}}</td>
<td>{{$value.category}}</td>
<td>{{$value.description}}</td>
<td class="edit">
<a href="javascript:;">修改</a> |
<a href="javascript:;">删除</a>
</td>
</tr>
{{/each}}
</script>
<!-- vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<!-- jQuery.js -->
<script src="/js/jquery-3.5.1.js"></script>
<!-- art-template -->
<script src="https://cdn.staticfile.org/art-template/4.10.0/lib/template-web.min.js"></script>
<!-- bootstrap.js -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
<!-- 提示框 js -->
<!-- https://codeseven.github.io/toastr/ -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script>
<!-- -->
<script src="/js/index.js"></script>
</body>
</html>
3.2在controller中index.js下
const router = require('koa-simple-router')
const controllers = require('./indexController');
const contro = new controllers();
let controller = (app) => {
app.use(router(_ => {
_.get('/',contro.index())
//伪静态
_.get('/index',contro.index())
//获取图书
_.get('/admin',contro.adminindex())
//添加图书页
_.get('/addpage',contro.actionAddPage())
_.post('/add',contro.actionAdd())
//修改编辑图书
_.get('/editpage',contro.actionEditpage())
_.post('/edit',contro.actionEdit())
//删除图书
_.get('/delete',contro.deleteEdit())
}))
}
module.exports = controller;
3.3在controller中indexController.js下
对对应访问的路径进行处理
let SafeRequest = require('../models');
let safeRequest = new SafeRequest();
class contributors{
constructor(){}
index(){
// return(ctx, next) => {
// ctx.body = 'hello world'
// }
let data = '图书馆案例!!!'
return async function (ctx) {
await ctx.render('index',{
data
});
}
}
//获取图书
adminindex(){
return async function (ctx) {
let msg = await safeRequest.getData();
//要前端渲染 将msg传给页面
ctx.body = msg;
// ctx.render('admin',{
// data:msg
// });
}
}
//完成修改
actionEdit(){
return async function (ctx) {
try{
let msg = await safeRequest.editData(ctx.request.body)
//返回数据
ctx.body = msg;
// console.log('msg',msg);
// ctx.redirect('/admin');
}catch(err){
console.log('err',err);
}
}
}
}
module.exports = contributors;
3.4在models中index.js数据库处理下
处理完后,将数据返回给controller,controller将数据返回给前端index.js进行渲染;
// let data = require("../data.json");
const path = require("path");
const fs = require("fs");
const db = require("./db_promise")
class SafeRequest{
constructor(){}
//获取数据
getData(){
return db.query("SELECT * FROM t_book");
// return new Promise((resolve,reject) => {
// resolve(data);
// })
}
async editData(opt) {
// console.log(opt);
let result = {
code:0,
message:'',
data:[]
}
try{
let data = await db.query(`update t_book set name=?,author=?,category=?,description=? where id=? `,
[opt.name,opt.author,opt.category,opt.description,opt.id]);
if(data.affectedRows > 0){
result.message = '修改成功'
return Promise.resolve(result);
}else{
result.message = '修改失败';
result.code = 1;
return Promise.reject(result);
}
}catch(err){
result.message = '修改失败';
result.code = 1;
result.status = 500
return Promise.reject(result);
}
}
}
module.exports = SafeRequest;
3.5前端渲染
//编辑图书
$('.edit').on('click','a:eq(0)', function(e){
// console.log('0 ===>',0);
$('#myModal').modal('show');
//获取id
let id = $(this).parent().siblings().eq(0).text();
// console.log('id',id);
$.ajax({
url:'/editpage?editid='+id,
type: 'get',
dataType: 'json',
success(msg){
console.log('msg =====>',msg);
$('#form').find('input[name=name]').val(msg.data.name);
$('#form').find('input[name=author]').val(msg.data.author);
$('#form').find('input[name=category]').val(msg.data.category);
$('#form').find('input[name=description]').val(msg.data.description);
//发起请求 修改图书信息
$('#btn_submit').off('click').click(function(){
// console.log('2 ===>',2);
submitData('/edit',id);
})
},
error(err){
console.log('err =====>',err);
}
})
})
//封装 提交数据
function submitData(url,id){
//使用serialize()方法获取到数据
let data = $('#form').serialize();
//使用三元表达式
data += id ? `&id=${id}` : '';
// console.log(data);
$.ajax({
url:url,
type: 'post',
dataType: 'json',
data: data,
success(msg){
console.log('msg =====>',msg);
if(msg.code === 0){
toastr.success('提交成功!');
//添加成功后在加载页面
getListBook();
}else{
toastr.error('提交失败!');
}
},
error(err){
console.log('err =====>',err);
toastr.error('提交失败!');
}
})
//关闭模态框
$('#myModal').modal('hide');
}