koa2視圖層搭建
接着上一篇《Js全棧開發之koa2路由與mvc搭建初步》來繼續學習koa2,上一篇將koa2項目按mvc架構拆分了一下,只拆分出了路由,控制器和服務層,而視圖層和控制器層混在了一起,沒有進行拆分,本篇將使用模板和靜態插件對視圖層進行拆分,並對Nunjucks模板包的使用做簡單的彙總。
文章目錄
1. 模板與靜態中間件安裝
還是使用npm進行安裝
npm install koa-nunjucks-2 -save
npm install koa-static -save
2. 項目結構
├── controller/
│ ├── home.js
├── service/
│ ├── home.js
├── views/
│ ├── common/
│ ├── header.html
│ ├── footer.html
│ ├── layout.html
│ ├── layout-home.html
│ ├── home/
│ ├── index.html
│ ├── login.html
│ ├── success.html
├── public/
│ ├── home/
│ ├── main.css
├── app.js
├── router.js
├── package.json
3. app.js 入口方法
app.js 入口方法方法中先引入koa-nunjucks-2和koa-static兩個中間件的npm包,之後分別指定它們的存放路徑及相關參數。
const Koa = require('koa')
const path = require('path')
const bodyParser = require('koa-bodyparser')
const nunjucks = require('koa-nunjucks-2')
const staticFiles = require('koa-static')
const app = new Koa()
const router = require('./router')
// 指定 public目錄爲靜態資源目錄,用來存放 js css images 等
app.use(staticFiles(path.resolve(__dirname, "./public"),{
maxage: 30*24*60*60*1000
}))
app.use(nunjucks({
ext: 'html',
path: path.join(__dirname, 'views'), // 指定視圖目錄
nunjucksConfig:{
trimBlocks: true // 開啓轉義 防Xss
}
}))
app.use(bodyParser())
router(app)
app.listen(3000, () => {
console.log('server is running at http://localhost:3000')
})
4. views層使用
先引入公共的父類佈局模板,其他的公共模板再繼承父類的公共模板。之後詳細的各類視圖頁面再繼承對應的公共模板。
- layout.html 佈局模板
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
{% block head %} {% endblock %}
</head>
<body>
{% include "./header.html" %}
{% block body %}
{% endblock %}
{% include "./footer.html" %}
{% block content %}
{% endblock %}
</body>
</html>
- layout-home.html
{% extends "./layout.html" %}
{% block head %}
<link rel="stylesheet" href="/home/main.css">
{% endblock %}
{% block body %}
{% block homeBanner %}
{% endblock %}
<div class="show_time">
<div class="feature-con">
<ul class="feature fn-clear">
<li class="feature-item"><i class="ico"></i>
<h4 class="tit">免費資源</h4>
</li>
<li class="feature-item"><i class="ico"></i>
<h4 class="tit">關於</h4>
</li>
</ul>
</div>
</div>
{% endblock %}
- index.html
{% extends "common/layout-home.html" %}
{% block homeBanner %}
<div class="banner_box">
<div class="banner_inner">
<h2 class="slogan">匯聚天下英才</h2>
<a href="/login" title="gogogo" class="btn" id="gogogo">登錄</a>
</div>
</div>
{% endblock %}
{% block content %}
<div class="hp-dialog">
<div class="hp-box">
<form action="/user/register" method="post">
<h1>登錄頁面</h1>
<p class="error">{{content}}</p>
<input type="text" name="name" placeholder="請輸入用戶名:dahlin">
<input type="password" name="password" placeholder="請輸入密碼:123456">
<button>{{btnName}}</button>
</form>
</div>
</div>
、、
{% endblock %}
基本順序就是 index.html繼承自layout-home.html,layout-home.html繼承自layout.html。
5. controller層使用
在controller裏面調用對應的views層模板
const HomeService = require('../service/home')
module.exports = {
index: async(ctx, next) => {
await ctx.render("home/index", {title: "xxxx歡迎您"})
},
login: async(ctx, next) => {
await ctx.render('home/login',{
btnName: '提交'
})
},
register: async(ctx, next) => {
let params = ctx.request.body
let name = params.name
let password = params.password
let res = await HomeService.register(name,password)
if(res.status == "-1"){
await ctx.render("home/login", res.data)
}else{
ctx.state.title = "個人中心"
await ctx.render("home/success", res.data)
}
}
}
6. 路由router.js
所有的url都是從路由中發起的,特定的路由匹配與之對應的url,url調用controller中的對應方法。
const router = require('koa-router')()
const HomeController = require('./controller/home')
module.exports = (app) => {
router.get( '/', HomeController.index )
router.get('/user', HomeController.login)
router.post('/user/register', HomeController.register)
app.use(router.routes())
.use(router.allowedMethods())
}
本章沒有涉及到model數據模型層,將在下一章和數據庫部分一同總結匯總。
7. Nunjucks語法 基礎
7.1 基礎語法
首先我們需要了解 Nunjucks
的幾個特性
變量
{{ username }}
{{ foo.bar }}
{{ foo["bar"] }}
如果變量的值爲 undefined
或 null
,將不予顯示。
過濾器
{{ foo | title }}
{{ foo | join(",") }}
{{ foo | replace("foo", "bar") | capitalize }}
if
判斷
{% if variable %}
It is true
{% endif %}
{% if hungry %}
I am hungry
{% elif tired %}
I am tired
{% else %}
I am good!
{% endif %}
for
循環
var items = [{ title: "foo", id: 1 }, { title: "bar", id: 2}]
<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>
macro
宏
宏:定義可複用的內容,類似於編程語言中的函數
{% macro field(name, value='', type='text') %}
<div class="field">
<input type="{{ type }}" name="{{ name }}"
value="{{ value | escape }}" />
</div>
{% endmacro %}
接下來就可以把 field
當作函數一樣使用:
{{ field('user') }}
{{ field('pass', type='password') }}
更多語法內容請查閱官方文檔
7.1 繼承功能
網頁常見的結構大多是頭部、中間體加尾部,同一個網站下的多個網頁,頭部和尾部內容通常來說基本一致。於是我們可以採用繼承功能來進行編寫。
先定義一個 layout.html
<html>
<head>
{% block head %}
<link rel="stylesheet">
{% endblock %}
</head>
<body>
{% block header %}
<h1>this is header</h1>
{% endblock %}
{% block body %}
<h1>this is body</h1>
{% endblock %}
{% block footer %}
<h1>this is footer</h1>
{% endblock %}
{% block content %}
<script>
//this is place for javascript
</script>
{% endblock %}
</body>
</html>
layout
定義了五個模塊,分別命名爲:head
、header
、body
、footer
、content
。header
和 footer
是公用的,因此基本不動。業務代碼的修改只需要在 body
內容體中進行、業務樣式表和業務腳本分別在頭部 head
和底部 content
中引入。
接下來我們再定義一個業務級別的視圖頁面:home.html
{% extends 'layout.html' %}
{% block head %}
<link href="home.css">
{% endblock %}
{% block body %}
<h1>home 頁面內容</h1>
{% endblock %}
{% block content %}
<script src="home.js"></script>
{% endblock%}
最終的 home.html
輸出後如下所示:
<html>
<head>
<link href="home.css">
</head>
<body>
<h1>this is header</h1>
<h1>home 頁面內容</h1>
<h1>this is footer</h1>
<script src="home.js"></script>
</body>
</html>