本項目是一個系列項目,最終的目的是開發出一個相似京東商城的網站。本文主要介紹後臺管理中的區域管理,以及前端基於easyui插件的使用。本次增刪改查因數據量少,所以採用模態對話框方式進行,關於數據量大采用跳轉方式修改,詳見博主後續博文。javascript
後臺界面展現:css
地區管理包含省市縣的管理。詳見下文。html
1、數據庫設計前端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
class
Province(Base):
"""
省
"""
__tablename__
=
'province'
nid
=
Column(Integer, primary_key
=
True
)
caption
=
Column(VARCHAR(
16
), index
=
True
)
class
City(Base):
"""
市
"""
__tablename__
=
'city'
nid
=
Column(Integer, primary_key
=
True
)
caption
=
Column(VARCHAR(
16
), index
=
True
)
province_id
=
Column(Integer, ForeignKey(
'province.nid'
))
class
County(Base):
"""
縣(區)
"""
__tablename__
=
'county'
nid
=
Column(Integer, primary_key
=
True
)
caption
=
Column(VARCHAR(
16
), index
=
True
)
city_id
=
Column(Integer, ForeignKey(
'city.nid'
))
|
本次採用的是sqlAlchemy模塊建立數據庫,關於sqlAlchemy的數據庫連接以及數據庫建立本文不作介紹,詳細見Python操做 RabbitMQ、Redis、Memcache、SQLAlchemy(點擊進入詳細介紹)java
表關係分析:上述表關係比較簡單,市中有外鍵,表明這是市是屬於哪一個省;同理縣中也有外鍵,表明這個縣是屬於哪一個市。python
2、目錄結構
該目錄結構在前面博文【tornado】系列項目(一)之基於領域驅動模型架構設計的京東用戶管理後臺 中有詳細介紹,本博文再也不贅述。jquery
3、路由映射
1
2
3
4
5
6
|
(r
"/ProvinceManager.html$"
, Region.ProvinceManagerHandler),
#省份模板展現
(r
"/province.html$"
, Region.ProvinceHandler),
#省份的增刪改查
(r
"/CityManager.html$"
, Region.CityManagerHandler),
#市模板展現
(r
"/City.html$"
, Region.CityHandler),
#市的增刪改查
(r
"/CountyManager.html$"
, Region.CountyManagerHandler),
#縣的模板展現
(r
"/County.html$"
, Region.CountyHandler),
#縣的增刪改查
|
4、後臺模板展現Handler
#以省份爲例進行介紹(市縣相似):ajax
數據獲取Handler:sql
1
2
3
4
5
|
class
ProvinceManagerHandler(AdminRequestHandler):
def
get(
self
,
*
args,
*
*
kwargs):
# 打開頁面,顯示全部的省
self
.render(
'Region/ProvinceManager.html'
)
|
本Handler主要用於從模板展現,默認若是隻有這一個handler,用戶看到的將是空頁面。關於數據的增刪改查,詳見下文。數據庫
5、核心增刪改查操做
再介紹增刪改查以前,咱們先介紹母板文件layout的前端html和繼承該模板的ProvinceManager.html部分JavaScript代碼:
母版layout html:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
<!
DOCTYPE
html>
<
html
>
<
head
lang="en">
<
meta
charset="UTF-8">
<
title
></
title
>
<
link
rel="stylesheet" type="text/css" href="/Statics/Admin/Plugins/jquery-easyui/themes/default/easyui.css"> #導入easyui的css
<
link
rel="stylesheet" type="text/css" href="/Statics/Admin/Plugins/jquery-easyui/themes/icon.css"> #導入easyui的圖標
<
link
rel="stylesheet" type="text/css" href="/Statics/Admin/Css/Common.css"> #自定義css
<
script
type="text/javascript" src="/Statics/Admin/Plugins/jquery-easyui/jquery.min.js"></
script
> #導入jQuery
<
script
type="text/javascript" src="/Statics/Admin/Plugins/jquery-easyui/jquery.easyui.min.js"></
script
> #導入easyui的js
</
head
>
<
body
class="easyui-layout">
<
div
data-options="region:'north'" style="height:50px">
</
div
>
<
div
data-options="region:'south',split:true" style="height:30px;"></
div
>
<
div
data-options="region:'west',split:true" title="後臺管理" style="width:200px;">
<
div
id="aa" class="easyui-accordion" data-options="fit:true,border:false">
<
div
title="地區管理"> #easyui訂製的左側菜單
<
a
id="jd_menu_province" class="jd-menu" href="/ProvinceManager.html">省</
a
>
<
a
id="jd_menu_city" class="jd-menu" href="/CityManager.html">市</
a
>
<
a
id="jd_menu_county" class="jd-menu" href="/CountyManager.html">縣(區)</
a
>
</
div
>
<
div
title="用戶管理">
<
a
id="user" class="jd-menu" href="#">用戶管理</
a
>
<
a
id="jd_menu_merchant" class="jd-menu" href="/MerchantManager.html">商戶管理</
a
>
</
div
>
<
div
title="JD自營">
<
a
id="jd_menu_product" class="jd-menu" href="/ProductManager.html">產品管理</
a
>
</
div
>
</
div
>
</
div
>
<
div
data-options="region:'center'" title="{% block crumbs %} {% end %}"> #內容顯示區
{% block content %} {% end %}
</
div
>
</
body
>
</
html
>
|
省分內容展現區html:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<
div
>
<
table
id="dg"></
table
> #easyui訂製table
<
div
id="dlg" class="easyui-dialog" style="width:400px;height:200px;padding:10px 20px" closed="true" buttons="#dlg-buttons"> #easyui訂製模態對話框,默認關閉狀態
<
form
id="fm1">
<
div
class="input-group clearfix">
<
div
class="group-label" style="width: 80px;">
<
span
>省份:</
span
>
</
div
>
<
div
class="group-input" style="width: 300px;">
<
input
id="dlg_nid" style="width: 200px;display: none" name="nid"/>
<
input
id="dlg_province" style="width: 200px" class="easyui-textbox" type="text" name="caption" data-options="required:true,missingMessage:'省份不能爲空'" /> #easyui訂製form驗證+錯誤信息提示
</
div
>
</
div
>
</
form
>
</
div
>
<
div
id="dlg-buttons"> #easyui訂製按鈕
<
span
id="dlg_summary" style="color: red"></
span
>
<
a
href="#" class="easyui-linkbutton" iconCls="icon-ok" onclick="Save()">保存</
a
>
<
a
href="#" class="easyui-linkbutton" iconCls="icon-cancel" onclick="javascript:$('#dlg').dialog('close')">取消</
a
>
</
div
>
</
div
>
|
JavaScript代碼:
1
2
3
4
5
6
7
|
$(
function
() {
// 加載表格數據
InitTable();
#初始化表格內容(即查詢)
InitPagination();
#初始化分頁
InitMenu();
#初始化左側菜單
});
|
首先介紹兩個簡單的js:
1
2
3
4
5
6
7
|
/*
初始化左側菜單
*/
function
InitMenu(){
$(
'#aa'
).accordion(
'select'
,0); #easyui語法:選擇左側第0個標籤
$(
'#jd_menu_province'
).addClass(
'active'
); #讓省份默認選中
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
/*
初始化分頁
*/
function
InitPagination(){
var
pager = $(
'#dg'
).datagrid(
'getPager'
);
$(pager).pagination({
beforePageText:
'第'
,
afterPageText:
'頁 共{pages}頁'
,
displayMsg:
'當前顯示{from}-{to}條記錄 共{total}條數據'
})
}
|
關鍵的表格數據初始化js(查詢js):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
function
InitTable(){
$(
'#dg'
).datagrid({
title:
'省份列表'
,
iconCls:
'icon-save'
,
#省份圖標
url:
'/province.html'
,
#獲取數據的url
method:
'get'
,
#獲取方式
//fitColumns: true,
idField:
'nid'
,
singleSelect:
true
,
#默認單選
rownumbers:
true
,
#顯示行號
striped:
true
,
#奇數行與偶數行顏色有區別
columns:[[
#每一列標題(easyui默認根據field將後端傳來的數據按表格進行顯示)
{
field:
'ck'
,
checkbox:
true
#顯示checkbox
},
{
field:
'nid'
,
#從數據庫獲取的nid
title:
'ID'
,
#顯示名稱爲ID
width:80,
#寬度80px
align:
'center'
#居中顯示
},
{
field:
'caption'
,
title:
'標題'
,
width:180,
align:
'center'
}
]],
toolbar: [
#顯示的按鈕
{
text:
'添加'
,
#按鈕名稱
iconCls:
'icon-add'
,
#按鈕圖標
handler: AddRow
#點擊按鈕後執行的返回函數
},{
text:
'刪除'
,
iconCls:
'icon-remove'
,
handler: RemoveRow
},{
text:
'修改'
,
iconCls:
'icon-edit'
,
handler: EditRow
}
],
pagePosition:
'both'
,
#上下均顯示分頁
pagination:
true
,
#顯示分頁
pageSize:10,
#默認每頁顯示的數據總數
pageNumber: 1,
#默認第一頁
pageList: [10,20,50],
#分頁可選每頁顯示數量
loadFilter:
function
(data){
#過濾函數
return
data;
}
});
}
|
上述js代碼即查詢時的js代碼,接下來咱們先看查詢的後端業務處理類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
def
get(
self
,
*
args,
*
*
kwargs):
"""
獲取
:param args:
:param kwargs:
:return:
"""
if
self
.get_argument(
'type'
,
None
)
=
=
'all'
:
#若是是獲取全部數據
ret
=
{
'status'
:
True
,
'rows'
: "",
'summary'
:''}
#未來要返回給前端的字典,包含是否獲取成功的狀態、獲取的數據、錯誤信息
try
:
region_service
=
RegionService(RegionRepository())
#將數據庫處理類的對象傳入數據庫業務協調類
all_province_list
=
region_service.get_province()
#獲取全部省份
ret[
'rows'
]
=
all_province_list
#將省份數據添加進返回前端的字典
except
Exception as e:
ret[
'status'
]
=
False
ret[
'summary'
]
=
str
(e)
self
.write(json.dumps(ret))
#返回給前端
else
:
#若是是獲取分頁數據
ret
=
{
'status'
:
True
,
'total'
:
0
,
'rows'
: [],
'summary'
: ''}
try
:
rows
=
int
(
self
.get_argument(
'rows'
,
10
))
#每頁顯示10條
page
=
int
(
self
.get_argument(
'page'
,
1
))
#顯示第一頁
start
=
(page
-
1
)
*
rows 開始條數
region_service
=
RegionService(RegionRepository())
row_list
=
region_service.get_province_by_page(start, rows)
#根據分頁獲取省份數據
row_count
=
region_service.get_province_count()
#獲取省份總數
ret[
'total'
]
=
row_count
ret[
'rows'
]
=
row_list
except
Exception as e:
ret[
'status'
]
=
False
ret[
'summary'
]
=
str
(e)
self
.write(json.dumps(ret))
#返回給前端
|
數據庫業務協調處理類的對應操做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class
RegionService:
def
__init__(
self
, region_repository):
self
.regionRepository
=
region_repository
def
get_province_count(
self
):
count
=
self
.regionRepository.fetch_province_count()
#獲取省份總數
return
count
def
get_province_by_page(
self
, start, offset):
#根據分頁獲取省份
result
=
self
.regionRepository.fetch_province_by_page(start, offset)
return
result
def
get_province(
self
):
#獲取全部省份
return
self
.regionRepository.fetch_province()
|
數據庫操做類相關操做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
class
RegionRepository(IRegionRepository):
def
__init__(
self
):
self
.db_conn
=
DbConnection()
#實例化數據庫連接對象(只需建立一次對象,下面全部方法都不須要再建立)
def
fetch_province(
self
):
#獲取全部省份
cursor
=
self
.db_conn.connect()
sql
=
"""select nid,caption from province order by nid desc """
cursor.execute(sql)
db_result
=
cursor.fetchall()
self
.db_conn.close()
return
db_result
def
fetch_province_by_page(
self
, start, offset):
#根據分頁獲取省份
ret
=
None
cursor
=
self
.db_conn.connect()
sql
=
"""select nid,caption from province order by nid desc limit %s offset %s """
cursor.execute(sql, (offset, start))
db_result
=
cursor.fetchall()
self
.db_conn.close()
return
db_result
def
fetch_province_count(
self
):
#獲取省份總數
cursor
=
self
.db_conn.connect()
sql
=
"""select count(1) as count from province """
cursor.execute(sql)
db_result
=
cursor.fetchone()
self
.db_conn.close()
return
db_result[
'count'
]
|
以上就是查詢操做的全部過程。
增長:
js:
1
2
3
4
5
6
7
8
9
10
|
/*
添加
*/
function
AddRow(){
// 顯示對話框,因爲但願添加則將方法設置爲POST
$(
'#fm1'
).form(
'clear'
); #清空上次form的內容
$(
'#dlg'
).dialog(
'open'
).dialog(
'setTitle'
,
'建立省份'
); #設置模態對話框標籤是建立省份
$(
'#dlg_summary'
).empty(); #清空錯誤信息
METHOD =
'post'
;
#設置提交方式爲post
}
|
增長操做實際上就作了一個操做:打開模態對話框。
前端頁面展現:
當用戶輸入須要添加的省份,接下來點擊保存按鈕,數據將被寫入數據庫並在前端顯示:
保存的js代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/*
保存按鈕
*/
function
Save(){
var
isValid = $(
'#fm1'
).form(
'validate'
);前端form驗證
if
(isValid){
$.ajax({
url:
'/province.html'
,
#提交的url
type: METHOD,
#根據以前定義的方法進行提交
data: {caption: $(
'#dlg_province'
).val(),nid: $(
'#dlg_nid'
).val()}, #提交的數據
dataType:
'json'
,
#數據格式
success:
function
(data){
#若是後端成功返回數據
if
(data.status){
#後端操做成功
$(
'#fm1'
).form(
'clear'
); #清空form內容
$(
'#dlg'
).dialog(
'close'
); #關閉模態對話框
$(
'#dg'
).datagrid(
'reload'
); #從新加載數據
}
else
{
$(
'#dlg_summary'
).text(data.summary); #不然顯示錯誤信息
}
}
})
}
else
{
// 前端驗證經過
}
// $('#fm').form('clear');
}
|
增長對應的後端業務處理方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
def
post(
self
,
*
args,
*
*
kwargs):
"""
添加
:param args:
:param kwargs:
:return:
"""
ret
=
{
'status'
:
False
,
'summary'
: ''}
caption
=
self
.get_argument(
'caption'
,
None
)
if
not
caption:
ret[
'summary'
]
=
'省份不能爲空'
else
:
try
:
region_service
=
RegionService(RegionRepository())
result
=
region_service.create_province(caption)
#建立省份,若是省份已存在,返回None
if
not
result:
ret[
'summary'
]
=
'省份已經存在'
else
:
ret[
'status'
]
=
True
#操做成功
except
Exception as e:
ret[
'summary'
]
=
str
(e)
self
.write(json.dumps(ret))
#返回給前端
|
數據庫協調處理類對應的方法:
1
2
3
4
5
|
def
create_province(
self
, caption):
exist
=
self
.regionRepository.exist_province(caption)
#先判斷省份是否存在,若是存在,該方法返回值爲None
if
not
exist:
self
.regionRepository.add_province(caption)
#建立省份
return
True
|
數據庫對應操做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
def
exist_province(
self
, caption):
#省份是否存在
cursor
=
self
.db_conn.connect()
sql
=
"""select count(1) as count from province where caption=%s """
cursor.execute(sql, (caption,))
db_result
=
cursor.fetchone()
self
.db_conn.close()
return
db_result[
'count'
]
def
add_province(
self
, caption):
#建立省份
cursor
=
self
.db_conn.connect()
sql
=
"""insert into province (caption) values(%s)"""
effect_rows
=
cursor.execute(sql, (caption,))
self
.db_conn.close()
return
effect_rows
|
以上就是省份添加的所有過程。
修改:
實際上修改和添加基本上是同樣的,接下來只介紹與添加不一樣的地方:
js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/*
修改
*/
function
EditRow(){
// 顯示對話框,因爲但願修改則將方法設置爲PUT
// 獲取選中的值,將其賦值到頁面上,而後ajax提交
var
row = $(
'#dg'
).datagrid(
'getSelected'
);
$(
'#dlg_summary'
).empty();
if
(row){
METHOD =
'put'
;
$(
'#fm1'
).form(
'clear'
);
$(
'#fm1'
).form(
'load'
,row);
$(
'#dlg'
).dialog(
'open'
).dialog(
'setTitle'
,
'修改省份'
);
}
else
{
$.messager.alert(
'警告'
,
'請選擇要修改的行'
,
'warning'
);
}
}
|
這裏彈出模態對話框,與添加不一樣的是,修改須要將用戶原有數據存放在input標籤中,方便用戶進行修改。同時,將提交方法修改成put。
修改模態對話框示例截圖:
接下來用戶修改完成後的點擊保存,關於保存的js代碼詳見上文添加部分。
保存的後臺業務處理handler方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
def
put(
self
,
*
args,
*
*
kwargs):
"""
更新
:param args:
:param kwargs:
:return:
"""
ret
=
{
'status'
:
False
,
'summary'
: ''}
nid
=
self
.get_argument(
'nid'
,
None
)
caption
=
self
.get_argument(
'caption'
,
None
)
if
not
caption
or
not
nid:
ret[
'summary'
]
=
'省份不能爲空'
else
:
try
:
region_service
=
RegionService(RegionRepository())
result
=
region_service.modify_province(nid, caption)
if
not
result:
ret[
'summary'
]
=
'省份已經存在'
else
:
ret[
'status'
]
=
True
except
Exception as e:
ret[
'summary'
]
=
str
(e)
self
.write(json.dumps(ret))
|
該方法與添加時的方法基本一致,這裏不作過多介紹。
數據庫協調處理類對應的方法:
1
2
3
4
5
|
def
modify_province(
self
, nid, caption):
exist
=
self
.regionRepository.exist_province(caption)
if
not
exist:
self
.regionRepository.update_province(nid, caption)
#更新省份
return
True
|
數據庫操做對應類的方法:
1
2
3
4
5
6
|
def
update_province(
self
, nid, caption):
#更新省份
cursor
=
self
.db_conn.connect()
sql
=
"""update province set caption=%s where nid=%s """
effect_rows
=
cursor.execute(sql, (caption, nid,))
self
.db_conn.close()
return
effect_rows
|
以上就是省份數據修改的所有過程。
刪除:
js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
/*
刪除
*/
function
RemoveRow(){
// 獲取已經選中的行
var
rows = $(
'#dg'
).datagrid(
'getSelections'
);
console.log(rows);
if
(rows.length<=0){
// 警告框
$.messager.alert(
'警告'
,
'請選擇要刪除的行'
,
'warning'
);
}
else
if
(rows.length>1){
$.messager.alert(
'警告'
,
'不支持批量刪除'
);
}
else
{
// 確認框
$.messager.confirm(
'肯定'
,
'您肯定要刪除嗎?'
,
function
(status) {
#easyui訂製的確認框
if
(status){
// 點擊肯定
// 獲取當前選中行的值,Ajax發送到後臺
var
row = rows[0];
$.ajax({
url:
'province.html'
,
type:
'delete'
,
data: {nid: row.nid},
dataType:
'json'
,
success:
function
(data) {
if
(data.status){
//刪除成功
$.messager.show({
#easyui訂製的messager框
msg:
'刪除成功'
,
showType:
'slide'
,
#淡出
showSpeed: 500,
#速度
timeout: 5,
#顯示5秒
style:{
right:
''
,
top:document.body.scrollTop+document.documentElement.scrollTop,
#在屏幕上方顯示
bottom:
''
}
});
// 從新加載表格
var
rowIndex = $(
'#dg'
).datagrid(
'getRowIndex'
, row);
$(
'#dg'
).datagrid(
'deleteRow'
,rowIndex);
$(
'#dg'
).datagrid(
'reload'
);
// 刪除指定行
//var rowIndex = dt.datagrid('getRowIndex', row);
//dt.datagrid('deleteRow',rowIndex);
}
else
{
//刪除失敗
// $.messager.alert('錯誤信息', data.summary ,'error');
$.messager.show({
#顯示錯誤信息
icon:
'error'
,
title:
'錯誤信息'
,
msg:data.summary,
showType:
'slide'
,
timeout: 0,
style:{
right:
''
,
top:document.body.scrollTop+document.documentElement.scrollTop,
bottom:
''
}
});
}
}
});
}
})
}
}
|
後臺handler類對應方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
def
delete(
self
,
*
args,
*
*
kwargs):
"""
刪除
:param args:
:param kwargs:
:return:
"""
ret
=
{
'status'
:
False
,
'summary'
: ''}
nid
=
self
.get_argument(
'nid'
,
None
)
if
not
nid:
ret[
'summary'
]
=
'請選擇要刪除的省份'
else
:
# 調用service去刪除吧...
# 若是刪除失敗,則顯示錯誤信息
try
:
region_service
=
RegionService(RegionRepository())
region_service.delete_province(nid)
#根據nid刪除省份
ret[
'status'
]
=
True
except
Exception as e:
ret[
'summary'
]
=
str
(e)
self
.write(json.dumps(ret))
|
數據庫業務協調處理類對應的方法:
1
2
3
|
def
delete_province(
self
, nid):
self
.regionRepository.remove_province(nid)
|
數據庫操做類對應方法:
1
2
3
4
5
6
|
def
remove_province(
self
, nid):
cursor
=
self
.db_conn.connect()
sql
=
"""delete from province where nid=%s """
effect_rows
=
cursor.execute(sql, (nid,))
self
.db_conn.close()
return
effect_rows
|
以上就是刪除的所有過程。
總結:本文主要以省份的增刪改查爲例介紹了前端easyui的使用,後端handler、數據庫業務協調處理類、數據庫操做類的整個流程。