我们使用 jQuery + Semantic-UI 实现前端页面的设计,最终效果图如下:
注册页
登录页
未登录时的主页(或用户页)
登录后的主页(或用户页)
发表文章页
编辑文章页
未登录时的文章页
登录后的文章页
通知
4.5.1 组件
前面提到过,我们可以将模板拆分成一些组件,然后使用 ejs 的 include 方法将组件组合起来进行渲染。我们将页面切分成以下组件:
主页
文章页
根据上面的组件切分图,我们创建以下样式及模板文件:
public/css/style.css
/* ---------- 全局样式 ---------- */
body {
width: 1100px;
height: 100%;
margin: 0 auto;
padding-top: 40px;
}
a:hover {
border-bottom: 3px solid #4fc08d;
}
.button {
background-color: #4fc08d !important;
color: #fff !important;
}
.avatar {
border-radius: 3px;
width: 48px;
height: 48px;
float: right;
}
/* ---------- nav ---------- */
.nav {
margin-bottom: 20px;
color: #999;
text-align: center;
}
.nav h1 {
color: #4fc08d;
display: inline-block;
margin: 10px 0;
}
/* ---------- nav-setting ---------- */
.nav-setting {
position: fixed;
right: 30px;
top: 35px;
z-index: 999;
}
.nav-setting .ui.dropdown.button {
padding: 10px 10px 0 10px;
background-color: #fff !important;
}
.nav-setting .icon.bars {
color: #000;
font-size: 18px;
}
/* ---------- post-content ---------- */
.post-content h3 a {
color: #4fc08d !important;
}
.post-content .tag {
font-size: 13px;
margin-right: 5px;
color: #999;
}
.post-content .tag.right {
float: right;
margin-right: 0;
}
.post-content .tag.right a {
color: #999;
}
views/header.ejs
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= blog.title %></title>
<link rel="stylesheet" href="//cdn.bootcss.com/semantic-ui/2.1.8/semantic.min.css">
<link rel="stylesheet" href="/css/style.css">
<script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
<script src="//cdn.bootcss.com/semantic-ui/2.1.8/semantic.min.js"></script>
</head>
<body>
<%- include('components/nav') %>
<%- include('components/nav-setting') %>
<%- include('components/notification') %>
views/footer.ejs
<script type="text/javascript">
$(document).ready(function () {
// 点击按钮弹出下拉框
$('.ui.dropdown').dropdown();
// 鼠标悬浮在头像上,弹出气泡提示框
$('.post-content .avatar-link').popup({
inline: true,
position: 'bottom right',
lastResort: 'bottom right'
});
})
</script>
</body>
</html>
注意:上面
<script></script>
是 semantic-ui 操控页面控件的代码,一定要放到 footer.ejs 的</body>
的前面,因为只有页面加载完后才能通过 JQuery 获取 DOM 元素。
在 views 目录下新建 components 目录用来存放组件(即可以复用的模板片段),在该目录下创建以下文件:
views/components/nav.ejs
<div class="nav">
<div class="ui grid">
<div class="four wide column"></div>
<div class="eight wide column">
<a href="/posts"><h1><%= blog.title %></h1></a>
<p><%= blog.description %></p>
</div>
</div>
</div>
views/components/nav-setting.ejs
<div class="nav-setting">
<div class="ui buttons">
<div class="ui floating dropdown button">
<i class="icon bars"></i>
<div class="menu">
<% if (user) { %>
<a class="item" href="/posts?author=<%= user._id %>">个人主页</a>
<div class="divider"></div>
<a class="item" href="/posts/create">发表文章</a>
<a class="item" href="/signout">登出</a>
<% } else { %>
<a class="item" href="/signin">登录</a>
<a class="item" href="/signup">注册</a>
<% } %>
</div>
</div>
</div>
</div>
views/components/notification.ejs
<div class="ui grid">
<div class="four wide column"></div>
<div class="eight wide column">
<% if (success) { %>
<div class="ui success message">
<p><%= success %></p>
</div>
<% } %>
<% if (error) { %>
<div class="ui error message">
<p><%= error %></p>
</div>
<% } %>
</div>
</div>
4.5.2 app.locals 和 res.locals
上面的 ejs 模板中我们用到了 blog、user、success、error 变量,我们将 blog 变量挂载到 app.locals
下,将 user、success、error 挂载到 res.locals
下。为什么要这么做呢?app.locals
和 res.locals
是什么?它们有什么区别?
express 中有两个对象可用于模板的渲染:app.locals
和 res.locals
。我们从 express 源码一探究竟:
express/lib/application.js
app.render = function render(name, options, callback) {
...
var opts = options;
var renderOptions = {};
...
// merge app.locals
merge(renderOptions, this.locals);
// merge options._locals
if (opts._locals) {
merge(renderOptions, opts._locals);
}
// merge options
merge(renderOptions, opts);
...
tryRender(view, renderOptions, done);
};
express/lib/response.js
res.render = function render(view, options, callback) {
var app = this.req.app;
var opts = options || {};
...
// merge res.locals
opts._locals = self.locals;
...
// render
app.render(view, opts, done);
};
可以看出:在调用 res.render
的时候,express 合并(merge)了 3 处的结果后传入要渲染的模板,优先级:res.render
传入的对象> res.locals
对象 > app.locals
对象,所以 app.locals
和 res.locals
几乎没有区别,都用来渲染模板,使用上的区别在于:app.locals
上通常挂载常量信息(如博客名、描述、作者这种不会变的信息),res.locals
上通常挂载变量信息,即每次请求可能的值都不一样(如请求者信息,res.locals.user = req.session.user
)。
修改 index.js,在 routes(app)
上一行添加如下代码:
// 设置模板全局常量
app.locals.blog = {
title: pkg.name,
description: pkg.description
}
// 添加模板必需的三个变量
app.use(function (req, res, next) {
res.locals.user = req.session.user
res.locals.success = req.flash('success').toString()
res.locals.error = req.flash('error').toString()
next()
})
这样在调用 res.render
的时候就不用传入这四个变量了,express 为我们自动 merge 并传入了模板,所以我们可以在模板中直接使用这四个变量。