Flask 项目实战 3: 前端实现
待做清单程序的总体结构分为前端和后端两个部分,上一节介绍了后端的实现,本节讲解前端的实现。
1. 首页模板 templates/index.html
templates/index.html 是网站首页的模板,包括如下几个部分:
1.1 引入相关的库
<html>
<head>
<meta charset='utf-8'>
<script src="https://lib.baomitu.com/jquery/2.2.4/jquery.min.js"></script>
<link href="https://lib.baomitu.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="{{url_for('static', filename='style.css')}}" rel="stylesheet">
<script src="{{url_for('static', filename='script.js')}}"></script>
<title>每日清单</title>
</head>
在第 4 行和第 5 行,引入 JQuery 和 font-awesome 库。
在第 6 行,引入网站的样式文件 static/style.css,函数 url_for(‘static’, filename=‘style.css’) 表示 static 文件夹下的文件 style.css,它的输出为 static/style.css。
在第 7 行,引入 JS 文件 static/script.js,函数 url_for(‘static’, filename=‘script.js’) 表示 static 文件夹下的文件 static/script.js,它的输出为 static/script.js。
1.2 显示 “登录/注册” 按钮
<body>
<div class='header'>
<i class="fa fa-calendar-plus-o"></i> 待做清单
{% if hasLogin %}
<span class='login'>
<i class="fa fa-sign-out"></i>
<a href='/users/logout'>退出</a>
</span>
{% else %}
<span class='login'>
<i class="fa fa-sign-in"></i>
<a href='/users/login'>登录</a>
<i class="fa fa-user-plus"></i>
<a href='/users/register'>注册</a>
</span>
{% endif %}
</div>
如果用户没有登录,网站首页的显示 “登录/注册” 按钮;如果用户已经登录,网站首页的显示 “退出” 按钮。
在第 5 行,变量 hasLogin 标记用户是否登录,根据 hasLogin 是否为真显示不同的界面。
1.3 输入待做事项的文本框
{% if hasLogin %}
<div>
<input type="text" class="row" placeholder="输入待办事项">
<i class="fa fa-fw fa-plus-square" onclick='onAddTodo(this);'></i>
</div>
{% endif %}
如果 hasLogin 为真,用户已经登录,显示一个文本框,用于输入待做事项。
在第 4 行,显示了一个 font awsome 图标 plus-square,点击该图标,调用函数 onAddTodo(this) 向后端请求新增一个待做事项。
1.4 待做清单和完成清单
{% for todo in todos %}
<div>
<input type="text" class="row" value="{{todo.title}}">
<i class="fa fa-fw fa-check" onclick='onUpdateTodo({{todo.todoId}});'></i>
</div>
{% endfor %}
<div class='header'>
<i class="fa fa-calendar-check-o"></i> 完成清单
</div>
{% for done in dones %}
<div>
<input type="text" class="row" value="{{done.title}}">
<i class="fa fa-fw fa-trash" onclick='onDeleteTodo({{done.todoId}});'></i>
</div>
{% endfor %}
</body>
</html>
页面模板中有 2 个变量:todos 和 dones,todos 是 status 等于 ‘todo’ 的待做事项列表,dones 是 status 等于 ‘done’ 的待做事项列表。
在第 1 行,遍历 todos,展示所有的待做事项;在第 4 行,显示了一个 font awsome 图标 check,点击该图标表示已经完成该项,将待做事项移入到完成清单中,调用函数 onUpdateTodo(todo.todoId) 向后端请求更新待做事项的 status 为 ‘done’。
在第 12 行,遍历 dones,展示所有的完成事项;在第 15 行,显示了一个 font awsome 图标 delete,点击该图标,调用函数 onDeleteTodo(todo.todoId) 向后端请求删除待做事项。
2 注册页面模板 templates/register.html
注册页面 templates/register.html 显示一个注册表单,由如下部分构成:
2.1 引入相关文件
<html>
<head>
<meta charset='UTF-8'>
<link href="https://lib.baomitu.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="{{url_for('static', filename='style.css')}}" rel="stylesheet">
<title>注册</title>
</head>
在第 4 行和第 5 行,引入font-awesome 库。
在第 5 行,引入网站的样式文件 static/style.css,函数 url_for(‘static’, filename=‘style.css’) 表示 static 文件夹下的文件 style.css,它的输出为 static/style.css。
2.2 注册表单
<body>
<h3><i class='fa fa-user-plus'></i> 注册</h3>
<form action="/users/register" method="POST">
<div class="row">
{{ form.name.label }}
{{ form.name() }}
<b>{{ form.name.errors[0] }}</b>
</div>
<div class="row">
{{ form.password.label }}
{{ form.password() }}
<b>{{ form.password.errors[0] }}</b>
</div>
<div class="row">
{{ form.submit() }}
</div>
{{ form.hidden_tag() }}
</form>
</body>
</html>
form 是注册表单,包括 3 个字段:name、password、隐藏字段,根据 form 中字段 email 和 password 的属性,它被渲染为如下的 HTML 文件:
<html>
<head>
<meta charset='UTF-8'>
<link href="https://lib.baomitu.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="/static/style.css" rel="stylesheet">
<title>登录</title>
</head>
<body>
<div class="header"><i class='fa fa-sign-in'></i> 登录</div>
<form action="/users/login" method="POST">
<div class="row">
<label for="name">姓名</label>
<input id="name" name="name" required type="text" value="">
<b></b>
</div>
<div class="row">
<label for="password">密码</label>
<input id="password" name="password" required type="password" value="">
<b></b>
</div>
<div class="row">
<input id="submit" name="submit" type="submit" value="登录">
</div>
<input id="csrf_token" name="csrf_token" type="hidden" value="ImRlYTZjZDEwZjU3YjNjNGY0MDVkMDc4ZDhiZTMwNWM1OTk2MjhiMzAi.X2LvVA.0x7iz2PGVHH-r8dWf7KQNMkuSAE">
</form>
</body>
</html>
这里注意两点:
- form.email.errors 和 form.password.errors 是一个错误信息列表,errors[0] 表示第一条错误信息;
- form.hidden_tag() 用于防范 CSRF 攻击,生成 <input id=“csrf_token”/> 标签,请参考相关词条。
2.3 登录页面模板 templates/login.html
登录页面 templates/login.html 显示一个登录表单,代码如下:
<html>
<head>
<meta charset='UTF-8'>
<link href="https://lib.baomitu.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="{{url_for('static', filename='style.css')}}" rel="stylesheet">
<title>登录</title>
</head>
<body>
<div class="header"><i class='fa fa-sign-in'></i> 登录</div>
<form action="/users/login" method="POST">
<div class="row">
{{ form.name.label }}
{{ form.name() }}
<b>{{ form.name.errors[0] }}</b>
</div>
<div class="row">
{{ form.password.label }}
{{ form.password() }}
<b>{{ form.password.errors[0] }}</b>
</div>
<div class="row">
{{ form.submit() }}
</div>
{{ form.hidden_tag() }}
</form>
</body>
</html>
登录页面 templates/login.html 与注册页面 templates/register.html 几乎完全相同,除了 title 标签不一样。请参考对 templates/register.html 的解释。
3. 调用后端服务 static/script.js
文件 script.js 定义了调用后端服务的 Ajax 函数,由如下部分构成:
3.1 配置 Ajax
function ajaxError()
{
alert('ajax error');
}
function ajaxSuccess(result)
{
if (result.error) {
alert('操作失败');
return;
}
location.reload();
}
客户端使用 ajax 技术请求服务端的服务。当 ajax 请求失败时,调用 ajaxError,提示用户 ajax 请求服务失败;当 ajax 请求成功时,调用 ajaxSuccess,提示用户 ajax 请求服务成功。
3.2 新增待做事项
function onAddTodo(button)
{
var children = $(button).parent().children();
var title = children.eq(0).val();
var data = JSON.stringify({'title': title});
$.ajax({
'url': '/todos/add',
'type': 'POST',
'contentType': 'application/json',
'data': data,
'dataType': 'json',
'error': ajaxError,
'success': ajaxSuccess
});
}
点击 “新增” 按钮后,执行函数 onAddTodo(button),button 指向的是 “新增” 按钮。在 templates/index.html 中,按钮、待做事项位于相同的 DIV 中,如下所示:
<div>
<input type="text" class="row" placeholder="输入待办事项">
<i class="fa fa-fw fa-plus-square" onclick='onAddTodo(this);'></i>
</div>
在第 3 行到第 4 行,表达式的含义如下所示:
表达式 | 含义 |
---|---|
$(button).parent() | 指向按钮的父节点 |
$(button).parent().children() | 表示 div 的 2 个子节点 |
children.eq(0) | 指向待做事项的文本框 |
children.eq(0).val() | 待做事项的文本框的值 |
在第 7 行,通过 JQuery 的 ajax 函数调用后端服务,设置 url 为 ‘/todos/add’、type 为 ‘POST’ ,表示新增一条待做事项。
3.3 更新待做事项
function onUpdateTodo(todoId)
{
var data = JSON.stringify({'todoId': todoId});
$.ajax({
'url': '/todos/update',
'type': 'POST',
'contentType': 'application/json',
'data': data,
'dataType': 'json',
'error': ajaxError,
'success': ajaxSuccess
});
}
当用户完成一个待做事项后,将待做事项的 status 从 ‘todo’ 更改为 ‘done’。
在第 5 行,通过 JQuery 的 ajax 函数调用后端服务,设置 url 为 ‘/todos/update’、type 为 ‘POST’ ,更新待做事项的 status。
3.4 删除待做事项
function onDeleteTodo(todoId)
{
var data = JSON.stringify({'todoId': todoId});
$.ajax({
'url': '/todos/delete',
'type': 'POST',
'contentType': 'application/json',
'data': data,
'dataType': 'json',
'error': ajaxError,
'success': ajaxSuccess
});
}
在第 5 行,通过 JQuery 的 ajax 函数调用后端服务,设置 url 为 ‘/todos/delete’、type 为 ‘POST’ ,删除待做事项。
4. 样式文件 static/style.css
style.css 是网站的样式文件,设置尺寸、字体大小等信息。
body {
width: 400px;
margin: auto;
}
a {
text-decoration: none;
}
.fa {
cursor: pointer;
}
.login {
font-size: 80%;
font-weight: normal;
}
.header {
padding-top: 8px;
padding-bottom: 8px;
font-size: 120%;
font-weight: bold;
}
.row {
width: 360px;
padding-top: 3px;
padding-bottom: 3px;
margin-top: 2px;
margin-bottom: 2px;
}
对标签 body 设置,margin: auto 表示将内容居中显示,两侧的 margin 相等,根据内容的宽度和屏幕的宽度自动计算 margin 的大小。
对标签 a 设置,不显示下划线。
对 class 等于 fa 的元素设置,显示光标的形状为手形。
登录、注册、退出的按钮的 class 等于 login,显示的 font-size 为 80%,略小于正常的尺寸。
待做事项和完成事项的 class 等于 row,设置上下的 padding 和 margin,让它们互相之间存在一个间隔。
5. 小结
本节讲解了前端的实现,使用思维导图概括如下: