Node.js必知必会学习笔记(第四章)

531 阅读11分钟

初识---后端渲染1

const http = require('http');
const fs = require('fs');
//node会把json格式的数据自动转成对象
//'./mime.json'该文件中存放的是MIME类型
const mime = require('./mime.json');   

const quotes = [

    '哈哈',

    '嘻嘻',

    '呵呵'
];

const server = http.createServer((req, res) => {

    let url = req.url;
    if (url.startsWith('/public')) {
        let content = fs.readFileSync('.' + url);

        // url中最后一个  . 出现的位置
        let lastPoint = url.lastIndexOf('.');
        // 获取当前url中表示的文件后缀
        let suffix = url.substring(lastPoint);
        // console.log('suffix', suffix);
        // 根据后最从 mime 中获取对应 MIME 类型

        res.setHeader('content-type', mime[suffix] + ';charset="utf-8"');
        res.end(content);
    }

    if (url.startsWith('/quote')) {

        res.setHeader('content-type', 'text/html;charset="utf-8"');

        let quote = quotes.sort(() => {
            return Math.random() - .5;
        })[0];
        
        res.end(quote);
    }


    if (url.startsWith('/all')) {

        res.setHeader('content-type', 'text/html;charset="utf-8"');
        
        // 把这个数组的数据输出成一段html
        // 后端根据这个数组生成一段html返回给前端 : 后端渲染
        // res.end(JSON.stringify(quotes));
        let lis = quotes.map( q => {
            return `<li>${q}</li>`;
        } ).join('');
        // console.log('lis', lis);
        res.end(`
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Document</title>
            <link rel="stylesheet" href="/public/css.css">
        </head>
            <body>
                <h1>后端渲染</h1>
                <ul>${lis}</ul>
            </body>
        </html>
        `);
    }
});

server.listen(8888);

初识---后端渲染2

if (url.startsWith('/all')) {

    res.setHeader('content-type', 'text/html;charset="utf-8"');

     /**
      * 问题
      *     1、后端的逻辑与前端需要的html混合了,不利于程序的开发、维护
      */ 
    let lis = quotes.map( q => {
        return `<li>${q}</li>`;
    } ).join('');

    // 有点类似静态文件代理,
    //但是不一样的是,读取的内容并不会直接返回给前端,
    //而是需要在后端做一些处理,把处理后的结果再返回
    let tplContent = fs.readFileSync('./template/index.tpl').toString();
    //  模板解析过程
    tplContent = tplContent.replace(/\$\{lis\}/ig, lis);
    res.end(tplContent);
}
});

初识---后端渲染3(nunjucks)

const http = require('http');
const fs = require('fs');
const mime = require('./mime.json');    // node会把json格式的数据自动转成 对象
const nunjucks = require('./nunjucks');

// console.log('nunjucks', nunjucks);
// let c = nunjucks.renderString('Hello {{ username }}', { username: 'James' });
// console.log('c', c);

const quotes = [

    '哈哈',

    '嘻嘻',

    '呵呵'
];

const server = http.createServer((req, res) => {

    let url = req.url;
    if (url.startsWith('/public')) {
        let content = fs.readFileSync('.' + url);

        // url中最后一个  . 出现的位置
        let lastPoint = url.lastIndexOf('.');
        // 获取当前url中表示的文件后缀
        let suffix = url.substring(lastPoint);
        // console.log('suffix', suffix);
        // 根据后最从 mime 中获取对应 MIME 类型

        res.setHeader('content-type', mime[suffix] + ';charset="utf-8"');
        res.end(content);
    }

    if (url.startsWith('/quote')) {

        res.setHeader('content-type', 'text/html;charset="utf-8"');

        let quote = quotes.sort(() => {
            return Math.random() - .5;
        })[0];
        
        res.end(quote);
    }


    if (url.startsWith('/all')) {

        res.setHeader('content-type', 'text/html;charset="utf-8"');
        
         /**
          * 问题
          *     1、后端的逻辑与前端需要的html混合了,不利于程序的开发、维护
          */ 
        // let lis = quotes.map( q => {
        //     return `<li>${q}</li>`;
        // } ).join('');

        // 有点类似静态文件代理,但是不一样的是,读取的内容并不会直接返回给前端,而是需要在后端做一些处理,把处理后的结果再返回
        let tplContent = fs.readFileSync('./template/index-nunjucks.tpl').toString();
        //  模板解析过程
        //  对当前的模板字符串(tplContent)进行处理
        tplContent = nunjucks.renderString(tplContent, {
            title: '加油',
            quotes
        });
        res.end(tplContent);
    }
});

server.listen(8888);

初识EJS模块引擎---后端渲染

  • 更多详情请参考官网https://www.npmjs.com/package/ejs
EJS是后台模板
可以把我们数据库和文件读取的数据显示到HTML页面上
是一个第三方模块

基本使用:
cnpm install ejs --save

nodejs使用:
ejs.renderFile(filename,data,options,function(err,str){
	//str=>Rendered HTML string 
})

get/post 

超文本传输协议(http)的设计目的是
保证客户端计器与服务器之间的通信

在客户端和服务器端之间进行请求响应时
两种最常被用到的方法是get和post

get  从指定的资源请求数据(一般用于获取数据)
post 向指定的资源提交要被处理的数据(一般用于提交数据)
  • 该构建的目录结构为:普通版实现方法

  • 下面是views/form.ejs的页面代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="./doLogin" method="POST">
        用户名<input type="text" name="username"><br/>
        密码<input type="password" name="password"><br/>
        <input type="submit" value="提交"/>
    </form>
</body>
</html>
  • 下面是views/login.ejs的页面代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h2>zzzzzejs</h2>
    <h3><%=msg%></h3>
    <!-- <h3><%=list%></h3> -->
    <ul>
        <%for(var i=0;i<list.length;i++){%>
            <li><%=list[i].title%></li>
        <%}%>
        
    </ul>
</body>
</html>
  • 下面是main.js的页面代码
var http = require('http');
var url = require('url');
var ejs=require('ejs')

http.createServer(function (request, response) {
	
    //获取路由
    //输出http://127.0.0.1/login获取 /login
    let pathname=url.parse(request.url).pathname;
    
    
    //获取请求类型 GET/POST
    console.log(request.method)
    


/*判断了七种情况,分别是
'/login'、'/register'、
'/admin'、'/news'、
'/poster'、'/doLogin'、其他
*/  
if(pathname=='/login'){
    // response.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
    // response.end('login')


    let msg='数据库里面获取的数据';
    let list=[
        {
            title:'新闻',
            author:'lth'
        },
        {
            title:'新闻1',
            author:'lth1'
        },
        {
            title:'新闻2',
            author:'lth2'
        }
    ]
    ejs.renderFile('./views/login.ejs',{
        msg,
        list
    },(err,data)=>{
        response.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
        response.end(data)
    })
}else if(pathname=='/register'){
    response.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
    response.end('register')
}else if(pathname=='/admin'){
    response.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
    response.end('admin')
}else if(pathname=='/news'){
    //http://127.0.0.1:8081/news?page=28&id=1
    //?page=28&id=1 --->url.parse(path,true).search
    var query=url.parse(request.url,true).query;
    console.log('query', query)
    response.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
    response.end('get传值获取成功')
}else if(pathname=='/poster'){
    //post演示
    ejs.renderFile('./views/form.ejs',{},(err,data)=>{
        response.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
        response.end(data)
    })
}else if(pathname=='/doLogin'){
    //获取post传值
    // response.end('post ok')

    var postData='';
    request.on('data',(chunk)=>{
        postData+=chunk
    });
    request.on('end',()=>{
        console.log('postData',postData)
        response.end(postData)
    })
}else{
    response.writeHead(404, {'Content-Type': 'text/html;charset="utf-8"'});
    response.end('页面不存在')
}

}).listen(8071)

  • 下面是/poster渲染views/form.ejs的页面

  • 下面是/poster点击提交后跳转doLogin的页面

  • 下面是/login渲染views/login.ejs的页面

  • 该构建的目录结构为:进阶版实现方法

  • 重点关注module/routes.js

  • 下面是module/routers.js的页面代码

这里是module/routers.js的页面代码
const fs = require("fs");
var path = require('path');
var url = require('url');
var ejs = require('ejs');


//封装为一个私有方法
//--不需要暴露
//---返回的是例如"text/html"用于拼接content-type


let getFileMine=function(extname){
    ////同步方法拿到数据
    var data=fs.readFileSync('../data/mime.json') 

    let mimeObj=JSON.parse(data.toString());

    return mimeObj[extname]
}

这里是module/routers.js的页面代码

let app={
    static:(request,response,staticPath)=>{
//实现静态web服务

    
//获取地址 例如/login.html
let pathname=url.parse(request.url).pathname;

    pathname=pathname=='/'?'/index.html':pathname
    
//可以获取后缀名 
let extname=path.extname(pathname)
    
//2.通过fs模块读取文件,排除'/favicon.ico'的情况
    if(pathname!='/favicon.ico'){
        
        
        try {
//读取静态目录下的文件例如 ./static/index.html
//如果此文件存在则通过后缀名获取content-type所需要的信息并加上去
var data=fs.readFileSync('./'+staticPath+pathname)

if(data){
    let mime=getFileMine(extname)
    response.writeHead(200, {'Content-Type': ''+mime+';charset="utf-8"'});
    response.end(data);  //不加载css样式
}} catch (error) {
	console.log('error', 'error')
}
        
    
    }
    },
    login:(request,response)=>{
        //处理登录的业务逻辑
        response.end(' --- login ---')
    },
    news:(request,response)=>{
        //处理news的业务逻辑
        response.end(' --- news --- ')
    },
    register:(request,response)=>{
        //处理register的业务逻辑
    },
    admin:(request,response)=>{
        //处理admin的业务逻辑
    },
    poster:(request,response)=>{
        //处理poster的业务逻辑
        ejs.renderFile('../demo01/views/form.ejs',{},(err,data)=>{
            response.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
            response.end(data)
        })
    },
    doLogin:(request,response)=>{
        //处理doLogin的业务逻辑
        response.end('--- doLogin ----')
    },
    error:(request,response)=>{
        response.writeHead(404, {'Content-Type': 'text/html;charset="utf-8"'});
        response.end('页面不存在hhhh')
        // response.end('admin')
    }
}

module.exports=app
  • 下面是main.js的页面代码
//引入的自定义模块
const routes =require('../module/routes')

var url = require('url');
var http = require('http');


http.createServer(function (request, response) {
    
    

    console.log(request.method)


// http://127.0.0.1/news  pathname=/news   --->replace('/','')  //去掉/变成news
// http://127.0.0.1/login  pathname=/login
// http://127.0.0.1/xxx  pathname=/xxx ---->不存在会报错
    let pathname=url.parse(request.url).pathname.replace('/','');

    
//获取请求类型
    console.log(request.method)

    try{
        routes[pathname](request,response)
    }catch(error){
        routes['error'](request,response)
    }
}).listen(8080)

初识nunjucks模板引擎---后端渲染

- cnpm i koa koa-router koa-nunjucks-2 -S

  • nunjucks的语法使用
变量:`{{username}}`

注释:{# Loop through all the users #}

if:
{% if hungry %}
  I am hungry
{% elif tired %}
  I am tired
{% else %}
  I am good!
{% endif %}

for:
<h1>Posts</h1>
<ul>
{% for item in items %}
  <li>{{ item.title }}</li>
{% else %}
  <li>This would display if the 'item' collection were empty</li>
{% endfor %}
</ul>


过滤器:
 {{ foo | replace("foo", "bar") | capitalize }}
 
 
模板继承block/extends:
1.定义父类模板:
h1>我是公共模板</h1>
<div class="leftContent">
    {% block left %}
        这边是左侧的内容
    {% endblock %}
    {% block right %}
        这边是右侧的内容
    {% endblock %}
    {% block somevalue %}
        我是一些数据
    {% endblock %}
</div>
2.继承父类模板:
{% extends "common.html" %}
{% block left %}
    我是左侧的内容1111
{% endblock %}
{% block right %}
    我是右侧的内容11111
{% endblock %}

{% block somevalue %}
    {{ super() }}
{% endblock %}

Macro(宏标签)可以定义可复用的内容,类似与编程语言中的函数:
{% macro pet(animalName,name="小白") %}
  <div>
      这里是一只{{animalName}};他的名字是{{name}}
  </div>
{% endmacro %}
调用:{{pet("狗狗")}}


include/import:
include 引入文件:{% include "footer.html" %}

import 导入文件:
{% macro pet(animalName) %}
<p>这是一只{{animalName}}</p>
{% endmacro %}
{% macro book(bookName) %}
<p>这是一本书,名字叫{{bookName}}</p>
{% endmacro %}

调用:
{% import 'somemodule.html' as fn %}
{{fn.pet("狗狗")}}
{{fn.book("nodejs从入门到实践")}}

初识koa的基本操作

  • 中间件函数的理解
const Koa = require('koa');

// 只是初始化了一个 Koa Application 对象,并没有创建 http 服务,也没有监听端口
const server = new Koa();

// use 接收的是一个函数,这个函数我们通常称为:中间件函数
// 中间件并不是循环依次执行的,里面不是for
// 控制中间件的执行,
// 假设其中一个中间件执行以后,不需要执行后续的中间件了
let content = '';
server.use(async function(ctx, next) {    // next => 下一个中间件函数
    console.log('有人访问了1');
    content = 'a';
    // ctx.response.body = content;    // res.end(content);
    ctx.body = content;
    await next();
    // ...
    console.log('123');
});

server.use(function(ctx, next) {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('有人访问了2');
            content += 'b';
            ctx.body = content;
            resolve();
        }, 1000);
    })
});

// 启动了一个基于 http 的 webserver
server.listen(8888);
  • 静态代理:
const Koa = require('koa');
const mime = require('./mime.json');
const fs = require('fs');

// 只是初始化了一个 Koa Application 对象,并没有创建 http 服务,也没有监听端口
const server = new Koa();

// 静态代理
server.use(async (ctx, next) => {
    let url = ctx.url;
    if (url.startsWith('/public')) {
        let content = fs.readFileSync('.' + url);
        let lastPoint = url.lastIndexOf('.');
        
        let suffix = url.substring(lastPoint);
        // res.setHeader('content-type', mime[suffix] + ';charset="utf-8"');
        ctx.set('content-type', mime[suffix] + ';charset="utf-8"');
        ctx.body = content;
    } else {
        await next();
    }
});

// 动态处理
server.use((ctx, next) => {
    ctx.body = 'kkb';
});


// 启动了一个基于 http 的 webserver
server.listen(8888);
  • 静态代理:使用koa-static-cache
  • 动态代理:使用koa-router
const Koa = require('koa');
const KoaStaticCache = require('koa-static-cache');
const KoaRouter = require('koa-router');

let users=[{id:1},{id:2}]


const quotes = [

    '哈哈',

    '嘻嘻',

    '呵呵'
];


// 只是初始化了一个 Koa Application 对象,
//并没有创建 http 服务,也没有监听端口
const server = new Koa();

server.use(KoaStaticCache('./public', {
    //代理的目录
    prefix: '/public',
    // gzip 压缩
    gzip: true,
    // 有利于开发中的文件变更
    dynamic: true
}) );


let router = new KoaRouter();
router.get('/', async ctx => {
    ctx.body = '首页';
});


router.get('/:id(\\d+)', async ctx => {
    //  koa-router 会解析真实url中 :id 所表示的部分
    //并把解析后的结果转成一个对象,存储在 ctx.params
    
    ctx.body = users.find(user => user.id == ctx.params.id);
});


router.get('/quote', async ctx => {
    let quote = quotes.sort(() => {
        return Math.random() - .5;
    })[0];
    ctx.body = quote;
})


server.use( router.routes() );

//启动了一个基于 http 的 webserver
server.listen(8888);
  • Koa 的流程
  • 因为函数调用的栈(LIFO - Last In First Out - 后进先出)特性
/**
 * File: /app.js
***/

const Koa = require('koa');

const app = new Koa();

app.use(async (ctx, next) => {
   console.log('a - start');
   next();
   console.log('a - end');
});

app.use(async (ctx, next) => {
    console.log('b - start');
    next();
    console.log('b - end');
 });

app.use(async (ctx, next) => {
    console.log('c - start');
    next();
    console.log('c - end');
 });

app.use(async (ctx, next) => {
        console.log('d - start');
    next();
    console.log('d - end');
});

app.listen(8888);
  • 输出为:
a - start
b - start
c - start
d - start
d - end
c - end
b - end
a - end
  • 洋葱模型

项目案例

  • 案例的结构目录
  • public目录下为静态资源分别是css样式和图片资源
  • tempates目录下模板引擎指定的目录
  • app.js为主写入文件
  • 利用到的技术如下:
const Koa = require('koa');
//用来处理静态资源
const KoaStaticCache = require('koa-static-cache');
//用来处理动态路由
const KoaRouter = require('koa-router');
//模板引擎用来执行模板文件渲染页面
const nunjucks = require('nunjucks');
//nodejs操纵数据库
const mysql2 = require('mysql2');
//对post请求过来的数据进行封装
const koaBody = require('koa-body');
// 创建数据库连接
const connection = mysql2.createConnection({
    host: '127.0.0.1',
    user: 'xxx',
    password: 'xxx',
    database: 'xxx'  
});

const app = new Koa();

// 设置静态文件资源代理
app.use(KoaStaticCache('./public', {
    prefix: '/public',
    gzip: true,
    dynamic: true
}));


// 动态资源处理 koa-router
const router = new KoaRouter();

// 配置模板引擎
nunjucks.configure('./templates', {
    watch: true,
    noCache: true
});

app.use(router.routes());


app.listen(8888);
  • 模板引擎templates目录下的base.html页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="/public/css/css.css" />
</head>
<body>
    
    <div id="app">
        <header id="header">
            <a href="/" id="logo"></a>
    
            <nav id="nav">
                {% for category in categories %}
                <a href="/{{category.id}}">{{category.name}}</a>
                {% endfor %}
            </nav>
    
            <div id="user">
                <a href="/addItem">添加新商品</a>
                <a href="">登录</a>
                <a href="/register">注册</a>
            </div>
        </header>
    
        <div id="main">
            
            {% block main %}{% endblock %}
    
        </div>
    </div>

</body>
</html>
  • 模板引擎templates目录下的register.html页面
{% extends "base.html" %}

{% block main %}
<div id="register" class="panel">
    <h2>注册</h2>
    <form action=""  method="POST" enctype="application/x-www-form-urlencoded">
        <div class="form-item">
            <label>
                <span class="txt">姓名:</span>
                <input type="text" class="form-input" name="username">
            </label>
        </div>
        <div class="form-item">
            <label>
                <span class="txt">密码:</span>
                <input type="password" class="form-input" name="password">
            </label>
        </div>
        <div class="form-item">
            <label>
                <span class="txt">重复密码:</span>
                <input type="password" class="form-input" name="repeatPassword">
            </label>
        </div>
        <div class="form-item">
            <label>
                <span class="txt"></span>
                <!-- <button class="form-button primary">登录</button> -->
                <button class="form-button">注册</button>
            </label>
        </div>
    </form>
</div>
{% endblock %}

  • 模板引擎templates目录下的index.html页面
{% extends "base.html" %}

{% block main %}
<ul class="items-list">
    
    {% for item in items %}
    <li class="panel">
        <img src="/public/items/{{item.cover}}" alt="" class="cover">
        <div class="name">{{item.name}}</div>
        <div class="price">¥ {{(item.price/100).toFixed(2)}}</div>
    </li>
    {% endfor %}
</ul>

<div class="pagination-container">
    
    <div class="pagination">
        <a href="" class="prev">上一页</a>
        <!-- pages是总共有几页(左闭右包) -->
        {% for p in range(1, pages+1) %}
            {% if p == page %}
            <a href="" class="current">{{p}}</a>
            {% else %}
            <a href="?page={{p}}">{{p}}</a>
            {% endif %}
        {% endfor %}


        {% if current<pages  %}
        <a href="{{current+1}}" class="next">下一页</a>
        {% elif current>=pages %}
        <a href="{{pages}}" class="next">下一页</a>
        {% else %}
        <a href="" class="next">下一页</a>
        {% endif %}
        

    </div>

</div>
{% endblock %}
  • 模板引擎templates目录下的addItem.html页面
{% extends "base.html" %}

{% block main %}
<div id="login" class="panel">
    <h2>添加商品</h2>
    <!-- 
        action:提交地址,发送请求的url
        配合post把表单中的数据通过请求过程中的正文携带给后端
            提交的数据最好通过一些方式进行格式化,比如json格式,或其它
            浏览器(其它各种客户端框架)提供一些内置的数据格式

            如果表单中支持:
                application/x-www-form-urlencoded:表单默认,url编码
                multipart/form-data:formdata格式,二进制
                text/plain:纯文本

            表单中还有一个特别注意的地方:
                //通过name收集value
                表单控制中的name属性,这个属性是用来设置提交数据的key
    -->
    <!-- 如果action=''留空则会把当前的页面做为提交的页面
        即这里的post请求发送给了/addItem.html页面
    -->
    <form action="" method="POST" enctype="application/x-www-form-urlencoded">
        <div class="form-item">
            <label>
                <span class="txt">商品类别:</span>
                <select name="categoryId">
                    <option value="">请选择一个类别</option>
                    {% for category in categories %}
                    <option value="{{category.id}}">{{category.name}}</option>
                    {% endfor %}
                </select>
            </label>
        </div>
        <div class="form-item">
            <label>
                <span class="txt">商品名称</span>
                <input type="text" name="name" class="form-input">
            </label>
        </div>
        <div class="form-item">
            <label>
                <span class="txt">商品价格</span>
                <input type="text" name="price" class="form-input">
            </label>
        </div>
        <div class="form-item">
            <label>
                <span class="txt">商品图片</span>
                <input type="text" name="cover" class="form-input">
            </label>
        </div>
        <div class="form-item">
            <label>
                <span class="txt"></span>
                <button class="form-button primary">提交</button>
            </label>
        </div>
    </form>
</div>
{% endblock %}
  • app.js对首页的处理逻辑
// 首页
router.get('/:id(\\d*)', async ctx => {
    // 1、获取当前页面所需要的动态数据:商品分类数据、商品列表数据
    // categories, items
    let categories = [];
    // 动态路由中的数据会被koa-router自动解析
    // 并存储到ctx.params 下,它是一个对象,
    // 对象中的key就是动态路由:后面的名称
    let categoryId = ctx.params.id;
    // ctx.request.query 存储了url中?后面的内容
    let page = ctx.request.query.page;
    let current=Number(page)
    // console.log(page,'?page=2(xx)');
    if (!page) {
        page = 1;
    }


    // function nextPage(current,pages){
    //     console.log(1);
    // }

    // nextPage()

    
    // console.log('categoryId', categoryId);
    // CURD(Create, Update, Read, Delete) (增删改查)
    // C:insert into
    // U: update
    // R: select
    // D: delete
    // connection.query(
    //     'SELECT * FROM `categories`',
    //     function (err, results, fields) {
    //         // 数据库的查询是异步的(需要时间),数据还没有查询出来的时候,
    //         // 后面的代码就执行完了,后端已经返回了
    //         console.log(results);
    //         console.log(fields);
    //     }
    // );



    // 每页显示的数据条数
    //一页一条数据
    let prepage = 2;
    // 当前页
    // let page = 1;   // page:1 => offset:0, page:2 => offset:5, page:3 => offset:10
    /**
     *     // ctx.request.query 存储了url中?后面的内容
            let page = ctx.request.query.page;
            page是当前第几页
     */
    // 偏移量
    let offset = (page - 1) * prepage;


    // 查询所有数据的总条数
    let sqlCount = 'SELECT count(id) as count FROM `items`';
    let [
        [{
            count
        }]
    ] = await query(sqlCount);
    // console.log('sql', count);
    //pages是总共有几页
    let pages = Math.ceil((count / prepage));
    // console.log(pages,'pagessss');

    let sql = 'SELECT * FROM `items` limit ' + prepage + ' offset ' + offset;
    if (categoryId) {
        //如果存在类目id即/1或/2或/3则增加where语句
        sql = 'SELECT * FROM `items` where `category_id`=? limit '+ prepage + ' offset ' + offset;
    }

    [categories] = await query('SELECT * FROM `categories`');
    // console.log([categories], '1111');
    /**[categories]
     * [ [ TextRow { name: '手机', id: 1 },
        TextRow { name: '笔记本', id: 2 },
        TextRow { name: '电视机', id: 3 } ] ]
     */

    
    /**
     *    let sql = 'SELECT * FROM `items` limit ' + prepage + ' offset ' + offset;
     * 
     * if (categoryId) {
        sql = 'SELECT * FROM `items` where `category_id`=? limit 1 offset 1';
    }
     * 
     * 用来渲染单个单个的商品数据图
     */
    [items] = await query(sql, [categoryId]);

    // 2、通过后端模板引擎对数据和模板文件进行渲染,得到最终返回给前端的页面
    ctx.body = nunjucks.render('index.html', {
        categories,
        items,
        pages,
        page,
        current
    });
});
  • app.js对/addItem的处理逻辑
/ 通过get方式访问和返回一个添加新商品的页面
// 通过点击添加一个新商品跳转过来的添加商品页面
//<option value="{{category.id}}">{{category.name}}</option>
//<option value="">请选择一个类别</option>

//在option中的value有两种可能一种是''(空字符),一种是1,2...(商品id)
router.get('/addItem', async ctx => {
    [categories] = await query('SELECT * FROM `categories`');

    ctx.body = nunjucks.render('addItem.html', {
        categories
    });
});
  • app.js对/register的处理逻辑
router.get('/register',async ctx=>{
    [categories] = await query('SELECT * FROM `categories`');

    ctx.body = nunjucks.render('register.html', {
        categories
    });
})
  • app.js对/addItem中post请求的处理逻辑
// post提交过来的数据进行处理
// 相当于拦截这个页面的post请求
router.post('/addItem', koaBody(), async ctx => {
    // ctx.request.body => koaBody 中间件解析请求正文后数据存储的位置
    // console.log('数据', ctx.request.body);
    /**
     * 数据 { categoryId: '1',
        name: '荣耀20 PRO',  
        price: '229900',
        cover: '62cf191f88e41447.jpg' }
     */
    let {
        categoryId,
        name,
        price,
        cover
    } = ctx.request.body;

    // 对数据进行合法性验证

    // 验证通过,存储到数据库
    // 解构获取结果中的第一个数组数据

    //预查询
    let [rs] = await query(
        "INSERT INTO `items` (`category_id`, `name`, `price`, `cover`) VALUES (?, ?, ?, ?)",
        [categoryId, name, price, cover]
    );
    // console.log('rs', rs);

    ctx.body = '<p>添加成功</p><p><a href="/addItem">继续添加</a> | <a href="/">回到首页</a></p>';
});
  • app.js对/register中post请求的处理逻辑
router.post('/register',koaBody(),async ctx => {
    let {
        username,
        password,
        repeatPassword
    } = ctx.request.body;
    if(password===repeatPassword){
            //预查询
        let [result] = await query(
            "INSERT INTO `users` (`username`, `password`) VALUES (?, ?)",
            [username,password]
        );
        console.log(result);
        ctx.body = '<p>注册成功</p><p><a href="">点击登录</a> | <a href="/">回到首页</a></p>';
    }else{
        ctx.body = '<p>注册失败</p><p><a href="/register">重新注册</a> | <a href="/">回到首页</a></p>';
    }
})