Flask 項(xiàng)目實(shí)戰(zhàn) 3: 前端實(shí)現(xiàn)
待做清單程序的總體結(jié)構(gòu)分為前端和后端兩個(gè)部分,上一節(jié)介紹了后端的實(shí)現(xiàn),本節(jié)講解前端的實(shí)現(xiàn)。
1. 首頁(yè)模板 templates/index.html
templates/index.html 是網(wǎng)站首頁(yè)的模板,包括如下幾個(gè)部分:
1.1 引入相關(guān)的庫(kù)
<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 庫(kù)。
在第 6 行,引入網(wǎng)站的樣式文件 static/style.css,函數(shù) url_for(‘static’, filename=‘style.css’) 表示 static 文件夾下的文件 style.css,它的輸出為 static/style.css。
在第 7 行,引入 JS 文件 static/script.js,函數(shù) url_for(‘static’, filename=‘script.js’) 表示 static 文件夾下的文件 static/script.js,它的輸出為 static/script.js。
1.2 顯示 “登錄/注冊(cè)” 按鈕
<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'>注冊(cè)</a>
</span>
{% endif %}
</div>
如果用戶沒(méi)有登錄,網(wǎng)站首頁(yè)的顯示 “登錄/注冊(cè)” 按鈕;如果用戶已經(jīng)登錄,網(wǎng)站首頁(yè)的顯示 “退出” 按鈕。
在第 5 行,變量 hasLogin 標(biāo)記用戶是否登錄,根據(jù) hasLogin 是否為真顯示不同的界面。
1.3 輸入待做事項(xiàng)的文本框
{% if hasLogin %}
<div>
<input type="text" class="row" placeholder="輸入待辦事項(xiàng)">
<i class="fa fa-fw fa-plus-square" onclick='onAddTodo(this);'></i>
</div>
{% endif %}
如果 hasLogin 為真,用戶已經(jīng)登錄,顯示一個(gè)文本框,用于輸入待做事項(xiàng)。
在第 4 行,顯示了一個(gè) font awsome 圖標(biāo) plus-square,點(diǎn)擊該圖標(biāo),調(diào)用函數(shù) onAddTodo(this) 向后端請(qǐng)求新增一個(gè)待做事項(xiàng)。
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>
頁(yè)面模板中有 2 個(gè)變量:todos 和 dones,todos 是 status 等于 ‘todo’ 的待做事項(xiàng)列表,dones 是 status 等于 ‘done’ 的待做事項(xiàng)列表。
在第 1 行,遍歷 todos,展示所有的待做事項(xiàng);在第 4 行,顯示了一個(gè) font awsome 圖標(biāo) check,點(diǎn)擊該圖標(biāo)表示已經(jīng)完成該項(xiàng),將待做事項(xiàng)移入到完成清單中,調(diào)用函數(shù) onUpdateTodo(todo.todoId) 向后端請(qǐng)求更新待做事項(xiàng)的 status 為 ‘done’。
在第 12 行,遍歷 dones,展示所有的完成事項(xiàng);在第 15 行,顯示了一個(gè) font awsome 圖標(biāo) delete,點(diǎn)擊該圖標(biāo),調(diào)用函數(shù) onDeleteTodo(todo.todoId) 向后端請(qǐng)求刪除待做事項(xiàng)。
2 注冊(cè)頁(yè)面模板 templates/register.html
注冊(cè)頁(yè)面 templates/register.html 顯示一個(gè)注冊(cè)表單,由如下部分構(gòu)成:
2.1 引入相關(guān)文件
<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>注冊(cè)</title>
</head>
在第 4 行和第 5 行,引入font-awesome 庫(kù)。
在第 5 行,引入網(wǎng)站的樣式文件 static/style.css,函數(shù) url_for(‘static’, filename=‘style.css’) 表示 static 文件夾下的文件 style.css,它的輸出為 static/style.css。
2.2 注冊(cè)表單
<body>
<h3><i class='fa fa-user-plus'></i> 注冊(cè)</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 是注冊(cè)表單,包括 3 個(gè)字段:name、password、隱藏字段,根據(jù) 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>
這里注意兩點(diǎn):
- form.email.errors 和 form.password.errors 是一個(gè)錯(cuò)誤信息列表,errors[0] 表示第一條錯(cuò)誤信息;
- form.hidden_tag() 用于防范 CSRF 攻擊,生成 <input id=“csrf_token”/> 標(biāo)簽,請(qǐng)參考相關(guān)詞條。
2.3 登錄頁(yè)面模板 templates/login.html
登錄頁(yè)面 templates/login.html 顯示一個(gè)登錄表單,代碼如下:
<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>
登錄頁(yè)面 templates/login.html 與注冊(cè)頁(yè)面 templates/register.html 幾乎完全相同,除了 title 標(biāo)簽不一樣。請(qǐng)參考對(duì) templates/register.html 的解釋。
3. 調(diào)用后端服務(wù) static/script.js
文件 script.js 定義了調(diào)用后端服務(wù)的 Ajax 函數(shù),由如下部分構(gòu)成:
3.1 配置 Ajax
function ajaxError()
{
alert('ajax error');
}
function ajaxSuccess(result)
{
if (result.error) {
alert('操作失敗');
return;
}
location.reload();
}
客戶端使用 ajax 技術(shù)請(qǐng)求服務(wù)端的服務(wù)。當(dāng) ajax 請(qǐng)求失敗時(shí),調(diào)用 ajaxError,提示用戶 ajax 請(qǐng)求服務(wù)失??;當(dāng) ajax 請(qǐng)求成功時(shí),調(diào)用 ajaxSuccess,提示用戶 ajax 請(qǐng)求服務(wù)成功。
3.2 新增待做事項(xiàng)
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
});
}
點(diǎn)擊 “新增” 按鈕后,執(zhí)行函數(shù) onAddTodo(button),button 指向的是 “新增” 按鈕。在 templates/index.html 中,按鈕、待做事項(xiàng)位于相同的 DIV 中,如下所示:
<div>
<input type="text" class="row" placeholder="輸入待辦事項(xiàng)">
<i class="fa fa-fw fa-plus-square" onclick='onAddTodo(this);'></i>
</div>
在第 3 行到第 4 行,表達(dá)式的含義如下所示:
表達(dá)式 | 含義 |
---|---|
$(button).parent() | 指向按鈕的父節(jié)點(diǎn) |
$(button).parent().children() | 表示 div 的 2 個(gè)子節(jié)點(diǎn) |
children.eq(0) | 指向待做事項(xiàng)的文本框 |
children.eq(0).val() | 待做事項(xiàng)的文本框的值 |
在第 7 行,通過(guò) JQuery 的 ajax 函數(shù)調(diào)用后端服務(wù),設(shè)置 url 為 ‘/todos/add’、type 為 ‘POST’ ,表示新增一條待做事項(xiàng)。
3.3 更新待做事項(xiàng)
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
});
}
當(dāng)用戶完成一個(gè)待做事項(xiàng)后,將待做事項(xiàng)的 status 從 ‘todo’ 更改為 ‘done’。
在第 5 行,通過(guò) JQuery 的 ajax 函數(shù)調(diào)用后端服務(wù),設(shè)置 url 為 ‘/todos/update’、type 為 ‘POST’ ,更新待做事項(xiàng)的 status。
3.4 刪除待做事項(xiàng)
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 行,通過(guò) JQuery 的 ajax 函數(shù)調(diào)用后端服務(wù),設(shè)置 url 為 ‘/todos/delete’、type 為 ‘POST’ ,刪除待做事項(xiàng)。
4. 樣式文件 static/style.css
style.css 是網(wǎng)站的樣式文件,設(shè)置尺寸、字體大小等信息。
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;
}
對(duì)標(biāo)簽 body 設(shè)置,margin: auto 表示將內(nèi)容居中顯示,兩側(cè)的 margin 相等,根據(jù)內(nèi)容的寬度和屏幕的寬度自動(dòng)計(jì)算 margin 的大小。
對(duì)標(biāo)簽 a 設(shè)置,不顯示下劃線。
對(duì) class 等于 fa 的元素設(shè)置,顯示光標(biāo)的形狀為手形。
登錄、注冊(cè)、退出的按鈕的 class 等于 login,顯示的 font-size 為 80%,略小于正常的尺寸。
待做事項(xiàng)和完成事項(xiàng)的 class 等于 row,設(shè)置上下的 padding 和 margin,讓它們互相之間存在一個(gè)間隔。
5. 小結(jié)
本節(jié)講解了前端的實(shí)現(xiàn),使用思維導(dǎo)圖概括如下: