67. 新建话题
简介
本章我们将完成话题的 CURD (创建、编辑和详情页)开发,在本节里我们首先完成话题发布功能。
需求分解
创建话题的要求如下:
- 只允许登录用户发布话题;
- 新建表单里用户只能填写或选择话题的标题、分类和正文这三项内容;
reply_count
、view_count
和last_reply_user_id
这三个属性属于程序维护,不需要给这三个属性赋值;- 在保存时自动把话题的创建用户ID( user_id ),设置成当前登录用户;
- 在本节里,我们暂时让话题的摘要和标题相同,下一节再介绍摘要信息如何生成;
- 话题发布成功后跳转到话题详情页。
对于「在保存时自动把话题的创建用户ID( user_id ),设置成当前登录用户」这一需求,我们使用数据模型的 数据完成 这一功能实现。数据完成 提供的 auto
、 instert
和 update
三个属性,可以分别在写入、新增和更新的时候进行字段值的自动完成。因为在这里只要求创建保存时自动给 user_id
赋值,所以我们使用 insert
属性实现。
验证器
首先,我们创建话题验证器:
$ php think make:validate common/Topic
验证器代码如下:
application/common/validate/Topic.php
<?php
namespace app\common\validate;
use think\Validate;
class Topic extends Validate
{
protected $rule = [
'title' => 'require|length:3,50',
'category_id' => 'require|egt:1',
'body' => 'require|min:3',
];
protected $message = [
'title.require' => '标题不能为空',
'title.length' => '标题长度必须在3-50个字符之间',
'category_id.require' => '分类不能为空',
'category_id.egt' => '分类不能为空',
'body.require' => '正文不能为空',
'body.min' => '正文至少包含3个字符',
];
}
数据模型
接下来,我们在 Topic 数据模型里定义创建保存方法:
'application/common/model/Topic.php'
<?php
namespace app\common\model;
use think\Model;
use app\common\validate\Topic as Validate;
class Topic extends Model
{
// 新增实例记录时自动完成user_id字段赋值
protected $insert = ['user_id'];
.
.
.
/**
* user_id属性修改器
* @Author zhanghong(Laifuzi)
* @DateTime 2019-06-21
*/
protected function setUserIdAttr(){
// 当前登录用户ID
$current_user = User::currentUser();
if(empty($current_user)){
return 0;
}
return $current_user->id;
}
/**
* 创建记录
* @Author zhanghong(Laifuzi)
* @DateTime 2019-06-21
* @param array $data 表单提交数据
* @return Topic [description]
*/
public static function createItem($data)
{
$validate = new Validate;
if(!$validate->batch(true)->check($data)){
$e = new ValidateException('数据验证失败');
$e->setData($validate->getError());
throw $e;
}
try{
$topic = new self;
$topic->allowField(true)->save($data);
}catch (\Exception $e){
throw new \Exception('创建话题失败');
}
return $topic;
}
}
控制器
在上一章我们使用命令行已经生成了话题控制器,所以我们现在打开已生成的控制器在里面完成话题创建方法代码。
另外,因为需求「只允许登录用户发布话题」,所以我们需要在控制器里使用 auth
中间件限制创建权限,和之前控制器一样在这里我们使用 except
属性注册中间件。
<?php
namespace app\index\controller;
use think\Request;
use app\common\model\Topic as TopicModel;
use app\common\model\Category as CategoryModel;
use app\common\exception\ValidateException;
class Topic extends Base
{
protected $middleware = [
'auth' => ['except' => ['index']],
];
.
.
.
public function create()
{
$categories = CategoryModel::all();
$this->assign('categories', $categories);
$this->assign('topic', []);
return $this->fetch('form');
}
public function save(Request $request)
{
if(!$request->isAjax()){
$this->redirect('[topic.create]');
}
try{
$data = $request->post();
$topic = TopicModel::createItem($data);
}catch (ValidateException $e){
$this->error($e->getMessage(), '', ['errors' => $e->getData()]);
}catch (\Exception $e){
$this->error($e->getMessage());
}
$this->success('创建成功', url('[topic.read]', ['id' => $topic->id]));
}
.
.
.
}
因为用户发帖时我们让用户选择话题分类,所以我们在 create()
方法里将所有的分类读取赋值给变量 $categories
,并传入视图页面中。
路由
在路由文件里添加创建话题路由配置:
route/route.php
<?php
.
.
.
// 话题管理
Route::get('topic/create', 'topic/create')->name('topic.create');
Route::post('topic', 'topic/save')->name('topic.save');
Route::get('topic', 'topic/index')->name('topic.index');
Route::get('category/<id>', 'category/read')->name('category.read');
注意,路由 topic.save
和 topic.save
一定要定义在 topic.index
前面。
视图模板
- 我们在顶部导航栏新增发帖入口:
application/index/view/layout/_header.html
.
.
.
<!-- Authentication Links -->
{empty name="current_user"}
<li class="nav-item"><a class="nav-link" href="{:url('[page.login]')}">登录</a></li>
<li class="nav-item"><a class="nav-link" href="{:url('[page.signup]')}">注册</a></li>
{else/}
<li class="nav-item">
<a class="nav-link mt-1 mr-3 font-weight-bold" href="{:url('[topic.create]')}">
<i class="fa fa-plus"></i>
</a>
</li>
<li class="nav-item dropdown">
.
.
.
- 我们在右边导航栏新增发帖入口:
application/index/view/topic/_sidebar.html
<div class="card ">
<div class="card-body">
<a href="{:url('[topic.create]')}" class="btn btn-success btn-block" aria-label="Left Align">
<i class="fas fa-pencil-alt mr-2"></i> 新建话题
</a>
</div>
</div>
刷新页面:
【截图】
现在我们可以很方便的通过顶部导航栏进入话题发布页面。
- 我们完成话题创建表单页面:
application/index/view/topic/form.html
{extend name="layout/main" /}
{block name="content"}
<div class="container">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">
<h1>
<i class="glyphicon glyphicon-edit"></i>
新建话题
</h1>
</div>
<div class="panel-body">
<form id='model-form' action="{:url('[topic.save]')}" method="POST" accept-charset="UTF-8">
<div class="form-group">
<input class="form-control" type="text" name="title" id="title-field" value="" placeholder="请填写标题" required/>
</div>
<div class="form-group">
<select class="form-control" name="category_id" required>
<option value="" hidden disabled selected>请选择分类</option>
{volist name='categories' id='category'}
<option value="{$category->id}">{$category->name}</option>
{/volist}
</select>
</div>
<div class="form-group">
<textarea name="body" id="body-field" class="form-control" rows="3"></textarea>
</div>
<div class="well well-sm">
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
</div>
</div>
</div>
</div>
{/block}
{block name="scripts"}
<script src="/static/assets/plugins/jquery-validate/jquery.validate.min.js"></script>
<script src="/static/assets/plugins/jquery-validate/bootstrap.validate.js"></script>
<script type="text/javascript">
jQuery(function($){
validAndSubmitForm(
"form#model-form",
{
"title":{
required: true,
rangelength: [3, 50]
}, "category_id":{
required: true,
min: 1
}, "body":{
required: true,
minlength: 3
}
}, {
"title":{
required: "标题不能为空",
rangelength: "标题长度必须在3-50个字符之间"
}, "category_id":{
required: "分类不能为空",
min: "分类不能为空"
}, "body":{
required: "正文不能为空",
minlength: "正文至少包含3个字符"
}
}
);
});
</script>
{/block}
效果展示
Git 版本控制
下面把代码纳入到版本管理:
$ git add -A
$ git commit -m "用户可以创建话题"
推荐文章: