至此已经有一个简陋的Web App demo了,还有一些待开发的功能(*标注)以后一点一点完善。
后端API包括:
获取日志列表:GET /api/blogs
获取日志内容:GET /api/blogs/:id
创建日志:POST /api/blogs
*修改日志:POST /api/blogs/:id
*删除日志:POST /api/blogs/:id/delete
*获取评论:GET /api/comments
*创建评论:POST /api/blogs/:id/comments
*删除评论:POST /api/comments/:id/delete
*获取用户:GET /api/users
创建新用户:POST /api/users
用户浏览页面包括:
首页:GET /
注册页:GET /register
登录页:GET /signin
注销页:GET /signout
日志详情页:GET /blog/:id
管理页面包括:
*评论列表页:GET /manage/comments
日志列表页:GET /manage/blogs
创建日志页:GET /manage/blogs/create
*修改日志页:GET /manage/blogs/edit
*用户列表页:GET /manage/users
handlers.py中增加:
@post('/api/blogs/{id}')
def api_edit_blog(id, request, *, name, summary, content):
# 需要传入request来检查是否为管理员
check_admin(request)
blog = await Blog.find(id)
# 对于用户输入要记得检查
if not name or not name.strip():
raise APIValueError('name', 'name cannot be empty.')
if not summary or not summary.strip():
raise APIValueError('summary', 'summary cannot be empty.')
if not content or not content.strip():
raise APIValueError('content', 'content cannot be empty.')
blog.name = name.strip()
blog.summary = summary.strip()
blog.content = content.strip()
await blog.update()
return blog
@post('/api/blogs/{blog_id}/delete')
def api_delete_blog(id, request):
check_admin(request)
blog = await Blog.find(id)
await blog.remove()
return dict(id=id) # 谁来处理?
@get('/api/comments')
async def api_comments(*, page='1'):
page_index = get_page_index(page)
num = await Comment.findNumber('count(id)')
p = Page(num, page_index)
if num == 0:
return dict(page=p, comments=())
comments = await Comment.findAll(orderBy='created_at desc', limit=(p.offset, p.limit))
return dict(page=p, comments=comments)
@post('/api/blogs/{id}/comments')
async def api_create_comment(id, request, *, content):
user = request.__user__
# 永远记得要对用户进行检查
if user is None:
raise APIPermissionError('Please signin first.')
if not content or not content.strip():
raise APIValueError('content', 'content cannot be empty.')
# 评论id和created_at不需要指定, 可以自动生成
blog = await Blog.find(id)
if blog is None:
raise APIResourceNotFoundError('Blog')
comment = Comment(
blog_id = blog.id,
user_id = user.id,
user_name = user.name,
user_image = user.image,
content = content.strip()
)
await comment.save()
return comment
@post('/api/comments/:id/delete')
async def api_delete_comment(id, request):
check_admin(request)
comment = Comment.find(id)
if comment is None:
raise APIResourceNotFoundError('Comment')
await comment.remove()
return dict(id=id)
@get('/api/users')
async def api_users(*, page='1'):
page_index = get_page_index(page)
num = await User.findNumber('count(id)')
p = Page(num, page_index)
if num == 0:
return dict(page=p, users=())
users = await User.findAll(orderBy='created_at desc', limit=(p.offset, p.limit))
return dict(page=p, users=users)
@get('/manage/') # 首页下面的Manage点击跳转到博客列表
def manage():
return 'redirect:/manage/blogs'
@get('/manage/comments')
def manage_comments(*, page='1'):
return {
'__template__': 'manage_comments.html',
'page_index': get_page_index(page)
}
@get('/manage/blogs/edit')
def manage_edit_blog(*, id):
return {
# 可以直接使用edit的模板
'__template__': 'manage_blog_edit.html',
'id': id,
'action': '/api/blogs/%s' % id
}
@get('/manage/users')
def manage_users(*, page='1'):
return {
'__template__': 'manage_users.html',
'page_index': get_page_index(page)
}
日志内容页面blog.html:
{% extends '__base__.html' %}
{% block title %}{{ blog.name }}{% endblock %}
{% block beforehead %}
<script>
var comment_url = '/api/blogs/{{ blog.id }}/comments'; // 为id号博客创建一条评论
$(function () {
var $form = $('#form-comment');
$form.submit(function (e) { // 绑定button的submit事件
// 在未输入内容时阻止对表单的提交, e是event
e.preventDefault();
$form.showFormError('');
var content = $form.find('textarea').val().trim();
if (content==='') {
return $form.showFormError('请输入评论内容!');
}
$form.postJSON(comment_url, { content: content }, function (err, result) {
if (err) {
return $form.showFormError(err);
}
refresh();
});
});
});
</script>
{% endblock %}
{% block content %}
<div class="uk-width-medium-3-4">
<article class="uk-article">
<h2>{{ blog.name }}</h2>
<p class="uk-article-meta">发表于{{ blog.created_at|datetime }}</p>
<p>{{ blog.html_content|safe }}</p>
</article>
<hr class="uk-article-divider">
{% if __user__ %}
<h3>发表评论</h3>
<article class="uk-comment">
<header class="uk-comment-header">
<img class="uk-comment-avatar uk-border-circle" width="50" height="50" src="{{ __user__.image }}">
<h4 class="uk-comment-title">{{ __user__.name }}</h4>
</header>
<div class="uk-comment-body">
<form id="form-comment" class="uk-form">
<div class="uk-alert uk-alert-danger uk-hidden"></div>
<div class="uk-form-row">
<textarea rows="6" placeholder="说点什么吧" style="width:100%;resize:none;"></textarea>
</div>
<div class="uk-form-row">
<button type="submit" class="uk-button uk-button-primary"><i class="uk-icon-comment"></i> 发表评论</button>
</div>
</form>
</div>
</article>
<hr class="uk-article-divider">
{% endif %}
<h3>最新评论</h3>
<ul class="uk-comment-list">
{% for comment in comments %}
<li>
<article class="uk-comment">
<header class="uk-comment-header">
<img class="uk-comment-avatar uk-border-circle" width="50" height="50" src="{{ __user__.image }}">
<h4 class="uk-comment-title">{{ comment.user_name }} {% if comment.user_id==blog.user_id %}(作者){% endif %}</h4>
<p class="uk-comment-meta">{{ comment.created_at|datetime }}</p>
</header>
<div class="uk-comment-body">
{{ comment.html_content|safe }}
</div>
</article>
</li>
{% else %}
<p>还没有人评论...</p>
{% endfor %}
</ul>
</div>
<div class="uk-width-medium-1-4">
<div class="uk-panel uk-panel-box">
<div class="uk-text-center">
<img class="uk-border-circle" width="120" height="120" src="{{ blog.user_image }}">
<h3>{{ blog.user_name }}</h3>
</div>
</div>
<div class="uk-panel uk-panel-header">
<h3 class="uk-panel-title">友情链接</h3>
<ul class="uk-list uk-list-line">
<li><i class="uk-icon-link"></i> <a href="#">编程</a></li>
<li><i class="uk-icon-link"></i> <a href="#">思考</a></li>
<li><i class="uk-icon-link"></i> <a href="#">读书</a></li>
</ul>
</div>
</div>
{% endblock %}
效果:
评论列表页面manage_comments.html:
{% extends '__base__.html' %}
{% block title %}评论{% endblock %}
{% block beforehead %}
<script>
function initVM(data) {
$('#vm').show(); // 显示隐藏的元素
var vm = new Vue({
el: '#vm',
data: {
comments:
page: data.page
},
methods: {
delete_comment: function (comment) {
var content = comment.content.length > 20? commment.content.substring(0, 20) + '...' : comment.content;
if (confirm('确认要删除平论“' + comment.content + '”?删除后不可恢复!')) {
postJSON('/api/comments/' + comment.id + '/delete', function (err, r) {
if (err) {
return error(err); // awesome.js中定义
}
refresh();
});
}
}
}
});
}
$(function() {
// awesome.js中的getJSON(url,data,callback)把data中的参数转换为形如"a=1&b=2"的键值对
// 然后传递给_httpJSON函数提交
getJSON('/api/comments', {
// page_index从handlers.py中传来
page: {{ page_index }}
}, function (err, results) {
if (err) {
return fatal(err);
}
$('#loading').hide();
// results是handlers.py中api_comments函数返回的dict
initVM(results);
});
});
</script>
{% endblock %}
{% block content %}
<div class="uk-width-1-1 uk-margin-bottom">
<div class="uk-panel uk-panel-box">
<ul class="uk-breadcrumb">
<li class="uk-active"><span>评论</span></li>
<li><a href="/manage/blogs">日志</a></li>
<li><a href="/manage/users">用户</a></li>
</ul>
</div>
</div>
<div id="error" class="uk-width-1-1"></div>
<div id="loading" class="uk-width-1-1 uk-text-center">
<span><i class="uk-icon-spinner uk-icon-medium uk-icon-spin"></i> 正在加载...</span>
</div>
<div id="vm" class="uk-width-1-1" style="display:none">
<table class="uk-table uk-table-hover">
<thead>
<tr>
<th class="uk-width-2-10">作者</th>
<th class="uk-width-5-10">内容</th>
<th class="uk-width-2-10">创建时间</th>
<th class="uk-width-1-10">操作</th>
</tr>
</thead>
<tbody>
<!-- 可以把v-repeat="blog: blogs"看成循环代码, 所以可以在一个<tr>内部引用循环变量blog -->
<tr v-repeat="comment: comments">
<td>
<span v-text="comment.user_name"></span>
</td>
<td>
<span v-text="comment.content"></span>
</td>
<td>
<span v-text="comment.created_at.toDateTime()"></span>
</td>
<td>
<a href="#0" v-on="click: delete_comment(comment)"><i class="uk-icon-trash-o"></i></a>
</td>
</tr>
</tbody>
</table>
<div v-component="pagination" v-with="page"></div> <!-- awesome.js、__base__.html都定义了, 但是怎么用的? -->
</div>
{% endblock %}
效果:
用户列表页面manage_users.html:
{% extends '__base__.html' %}
{% block title %}用户{% endblock %}
{% block beforehead %}
<script>
function initVM(data) {
$('#vm').show(); // 显示隐藏的元素
var vm = new Vue({
el: '#vm',
data: {
users: data.users,
page: data.page
}
});
}
$(function() {
getJSON('/api/users', {
page: {{ page_index }}
}, function (err, results) {
if (err) {
return fatal(err);
}
$('#loading').hide();
initVM(results);
});
});
</script>
{% endblock %}
{% block content %}
<div class="uk-width-1-1 uk-margin-bottom">
<div class="uk-panel uk-panel-box">
<ul class="uk-breadcrumb">
<li><a href="/manage/comments">评论</a></li>
<li><a href="/manage/blogs">日志</a></li>
<li class="uk-active"><span>用户</span></li>
</ul>
</div>
</div>
<div id="error" class="uk-width-1-1"></div>
<div id="loading" class="uk-width-1-1 uk-text-center">
<span><i class="uk-icon-spinner uk-icon-medium uk-icon-spin"></i> 正在加载...</span>
</div>
<div id="vm" class="uk-width-1-1">
<table class="uk-table uk-table-hover">
<thead>
<tr>
<th class="uk-width-4-10">名字</th>
<th class="uk-width-4-10">电子邮件</th>
<th class="uk-width-2-10">注册时间</th>
</tr>
</thead>
<tbody>
<tr v-repeat="user: users">
<td>
<span v-text="user.name"></span>
<span v-if="user.admin" style="color:#d05"><i class="uk-icon-key"></i> 管理员</span>
</td>
<td>
<a v-attr="href: 'mailto:'+user.email" v-text="user.email"></a>
</td>
<td>
<span v-text="comment.created_at.toDateTime()"></span>
</td>
</tr>
</tbody>
</table>
<div v-component="pagination" v-with="page"></div> <!-- awesome.js、__base__.html都定义了, 但是怎么用的? -->
</div>
{% endblock %}
效果:
还有其他功能也都测试成功:发表评论、删除评论、