MVVM模式不但可用于Form表单,在复杂的管理页面中也能大显身手。例如,分页显示Blog的功能,我们先把后端代码写出来:
在apis.py中定义一个Page类用于存储分页信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Page( object ): def __init__( self , item_count, page_index = 1 , page_size = 10 ): self .item_count = item_count self .page_size = page_size self .page_count = item_count / / page_size + ( 1 if item_count % page_size > 0 else 0 ) if (item_count = = 0 ) or (page_index < 1 ) or (page_index > self .page_count): self .offset = 0 self .limit = 0 self .page_index = 1 else : self .page_index = page_index self .offset = self .page_size * (page_index - 1 ) self .limit = self .page_size self .has_next = self .page_index < self .page_count self .has_previous = self .page_index > 1 |
在urls.py中实现API:
1
2
3
4
5
6
7
8
9
10
11
|
def _get_blogs_by_page(): total = Blog.count_all() page = Page(total, _get_page_index()) blogs = Blog.find_by( 'order by created_at desc limit ?,?' , page.offset, page.limit) return blogs, page @api @get ( '/api/blogs' ) def api_get_blogs(): blogs, page = _get_blogs_by_page() return dict (blogs = blogs, page = page) |
返回模板页面:
1
2
3
4
|
@view ( 'manage_blog_list.html' ) @get ( '/manage/blogs' ) def manage_blogs(): return dict (page_index = _get_page_index(), user = ctx.request.user) |
模板页面首先通过API:GET /api/blogs?page=?拿到Model:
1
2
3
4
5
6
7
8
9
10
|
{ "page" : { "has_next" : true, "page_index" : 1 , "page_count" : 2 , "has_previous" : false, "item_count" : 12 }, "blogs" : [...] } |
然后,通过Vue初始化MVVM:
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
|
<script> function initVM(data) { $( '#div-blogs' ).show(); var vm = new Vue({ el: '#div-blogs' , data: { blogs: data.blogs, page: data.page }, methods: { previous: function () { gotoPage( this .page.page_index - 1); }, next: function () { gotoPage( this .page.page_index + 1); }, edit_blog: function (blog) { location.assign( '/manage/blogs/edit/' + blog.id); } } }); } $( function () { getApi( '/api/blogs?page={{ page_index }}' , function (err, results) { if (err) { return showError(err); } $( '#div-loading' ).hide(); initVM(results); }); }); </script> |
View的容器是#div-blogs,包含一个table,我们用v-repeat可以把Model的数组blogs直接变成多行的<tr>:
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
|
< div id = "div-blogs" class = "uk-width-1-1" style = "display:none" > < table class = "uk-table uk-table-hover" > < thead > < tr > < th class = "uk-width-5-10" >标题 / 摘要</ th > < th class = "uk-width-2-10" >作者</ th > < th class = "uk-width-2-10" >创建时间</ th > < th class = "uk-width-1-10" >操作</ th > </ tr > </ thead > < tbody > < tr v-repeat = "blog: blogs" > < td > < a target = "_blank" v-attr = "href: '/blog/'+blog.id" v-text = "blog.name" ></ a > </ td > < td > < a target = "_blank" v-attr = "href: '/user/'+blog.user_id" v-text = "blog.user_name" ></ a > </ td > < td > < span v-text = "blog.created_at.toDateTime()" ></ span > </ td > < td > < a href = "#0" v-on = "click: edit_blog(blog)" >< i class = "uk-icon-edit" ></ i > </ td > </ tr > </ tbody > </ table > < div class = "uk-width-1-1 uk-text-center" > < ul class = "uk-pagination" > < li v-if = "! page.has_previous" class = "uk-disabled" >< span >< i class = "uk-icon-angle-double-left" ></ i ></ span ></ li > < li v-if = "page.has_previous" >< a v-on = "click: previous()" href = "#0" >< i class = "uk-icon-angle-double-left" ></ i ></ a ></ li > < li class = "uk-active" >< span v-text = "page.page_index" ></ span ></ li > < li v-if = "! page.has_next" class = "uk-disabled" >< span >< i class = "uk-icon-angle-double-right" ></ i ></ span ></ li > < li v-if = "page.has_next" >< a v-on = "click: next()" href = "#0" >< i class = "uk-icon-angle-double-right" ></ i ></ a ></ li > </ ul > </ div > </ div > |
往Model的blogs数组中增加一个Blog元素,table就神奇地增加了一行;把blogs数组的某个元素删除,table就神奇地减少了一行。所有复杂的Model-View的映射逻辑全部由MVVM框架完成,我们只需要在HTML中写上v-repeat指令,就什么都不用管了。
可以把v-repeat="blog: blogs"看成循环代码,所以,可以在一个<tr>内部引用循环变量blog。v-text和v-attr指令分别用于生成文本和DOM节点属性。
完整的Blog列表页如下: