本项目是一个系列项目,最终的目的是开发出一个相似京东商城的网站。本文主要介绍后台管理中的区域管理,以及前端基于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、数据库业务协调处理类、数据库操做类的整个流程。