Day 12 - 编写日志列表页

MVVM模式不但可用于Form表单,在复杂的管理页面中也能大显身手。例如,分页显示Blog的功能,我们先把后端代码写出来:

apis.py中定义一个Page类用于存储分页信息:

  1. class Page(object):
  2. def __init__(self, item_count, page_index=1, page_size=10):
  3. self.item_count = item_count
  4. self.page_size = page_size
  5. self.page_count = item_count // page_size + (1 if item_count % page_size > 0 else 0)
  6. if (item_count == 0) or (page_index < 1) or (page_index > self.page_count):
  7. self.offset = 0
  8. self.limit = 0
  9. self.page_index = 1
  10. else:
  11. self.page_index = page_index
  12. self.offset = self.page_size * (page_index - 1)
  13. self.limit = self.page_size
  14. self.has_next = self.page_index < self.page_count
  15. self.has_previous = self.page_index > 1

urls.py中实现API:

  1. def _get_blogs_by_page():
  2. total = Blog.count_all()
  3. page = Page(total, _get_page_index())
  4. blogs = Blog.find_by('order by created_at desc limit ?,?', page.offset, page.limit)
  5. return blogs, page
  6. @api
  7. @get('/api/blogs')
  8. def api_get_blogs():
  9. blogs, page = _get_blogs_by_page()
  10. return dict(blogs=blogs, page=page)

返回模板页面:

  1. @view('manage_blog_list.html')
  2. @get('/manage/blogs')
  3. def manage_blogs():
  4. return dict(page_index=_get_page_index(), user=ctx.request.user)

模板页面首先通过API:GET /api/blogs?page=?拿到Model:

  1. {
  2. "page": {
  3. "has_next": true,
  4. "page_index": 1,
  5. "page_count": 2,
  6. "has_previous": false,
  7. "item_count": 12
  8. },
  9. "blogs": [...]
  10. }

然后,通过Vue初始化MVVM:

  1. <script>
  2. function initVM(data) {
  3. $('#div-blogs').show();
  4. var vm = new Vue({
  5. el: '#div-blogs',
  6. data: {
  7. blogs: data.blogs,
  8. page: data.page
  9. },
  10. methods: {
  11. previous: function () {
  12. gotoPage(this.page.page_index - 1);
  13. },
  14. next: function () {
  15. gotoPage(this.page.page_index + 1);
  16. },
  17. edit_blog: function (blog) {
  18. location.assign('/manage/blogs/edit/' + blog.id);
  19. }
  20. }
  21. });
  22. }
  23. $(function() {
  24. getApi('/api/blogs?page={{ page_index }}', function (err, results) {
  25. if (err) {
  26. return showError(err);
  27. }
  28. $('#div-loading').hide();
  29. initVM(results);
  30. });
  31. });
  32. </script>

View的容器是#div-blogs,包含一个table,我们用v-repeat可以把Model的数组blogs直接变成多行的&lt;tr&gt;

  1. <div id="div-blogs" class="uk-width-1-1" style="display:none">
  2. <table class="uk-table uk-table-hover">
  3. <thead>
  4. <tr>
  5. <th class="uk-width-5-10">标题 / 摘要</th>
  6. <th class="uk-width-2-10">作者</th>
  7. <th class="uk-width-2-10">创建时间</th>
  8. <th class="uk-width-1-10">操作</th>
  9. </tr>
  10. </thead>
  11. <tbody>
  12. <tr v-repeat="blog: blogs" >
  13. <td>
  14. <a target="_blank" v-attr="href: '/blog/'+blog.id" v-text="blog.name"></a>
  15. </td>
  16. <td>
  17. <a target="_blank" v-attr="href: '/user/'+blog.user_id" v-text="blog.user_name"></a>
  18. </td>
  19. <td>
  20. <span v-text="blog.created_at.toDateTime()"></span>
  21. </td>
  22. <td>
  23. <a href="#0" v-on="click: edit_blog(blog)"><i class="uk-icon-edit"></i>
  24. </td>
  25. </tr>
  26. </tbody>
  27. </table>
  28. <div class="uk-width-1-1 uk-text-center">
  29. <ul class="uk-pagination">
  30. <li v-if="! page.has_previous" class="uk-disabled"><span><i class="uk-icon-angle-double-left"></i></span></li>
  31. <li v-if="page.has_previous"><a v-on="click: previous()" href="#0"><i class="uk-icon-angle-double-left"></i></a></li>
  32. <li class="uk-active"><span v-text="page.page_index"></span></li>
  33. <li v-if="! page.has_next" class="uk-disabled"><span><i class="uk-icon-angle-double-right"></i></span></li>
  34. <li v-if="page.has_next"><a v-on="click: next()" href="#0"><i class="uk-icon-angle-double-right"></i></a></li>
  35. </ul>
  36. </div>
  37. </div>

往Model的blogs数组中增加一个Blog元素,table就神奇地增加了一行;把blogs数组的某个元素删除,table就神奇地减少了一行。所有复杂的Model-View的映射逻辑全部由MVVM框架完成,我们只需要在HTML中写上v-repeat指令,就什么都不用管了。

可以把v-repeat="blog: blogs"看成循环代码,所以,可以在一个&lt;tr&gt;内部引用循环变量blogv-textv-attr指令分别用于生成文本和DOM节点属性。

完整的Blog列表页如下:

awesomepy-manage-blogs

原文: https://wizardforcel.gitbooks.io/liaoxuefeng/content/py2/106.html