项目优化与功能增强:任务管理系统的多维度升级
在项目开发过程中,持续优化和功能增强是提升系统性能和用户体验的关键。本文将围绕任务管理系统展开,详细介绍如何对现有项目进行优化,并添加一系列新功能,包括仪表盘页面的完善、文件上传、团队与成员管理以及通知系统的实现。
项目优化目标与方向
为了让项目更加完善,我们将对现有功能进行回顾,并添加一些新特性。重点关注仪表盘页面,它能让用户快速了解当前工作区的项目、成员和任务之间的交互情况。同时,还将引入通知系统、评论系统和文件附件功能,以提升系统的实用性和交互性。
仪表盘页面内容规划
仪表盘页面是系统的核心页面之一,需要提供足够的信息来快速报告应用程序中的活动,同时保持加载时间合理。对于最小可行产品(MVP),以下内容足以满足需求:
- 显示任务新评论数量的板块
- 显示到期任务数量的板块
- 显示最近创建任务数量的板块
- 显示已完成任务数量的板块
- 显示最后七个事件(通知)的通知面板
- 展示当前项目可视化进度的图表
实现已完成任务板块
首先,我们从实现已完成任务板块开始。创建一个名为 dashboard.feature 的新特性文件,并添加以下内容:
# /features/dashboard.feature
@userDashboard
Feature: dashboard blocks
In order to see my finished tasks
As a user
I am able to see finished task block in the dashboard
@javascript
Scenario: showing the finished task block in the dashboard
Given I log in as Jack
And I visit "/dashboard"
Then the response status code should be 200
And I should see "Finished Tasks!"
这里的 Jack 是一个测试用户,可以通过夹具或以下命令生成:
$ bin/console fos:user:create Jack jack@mava.info jackpass --env=test
为了检查用户是否已登录,运行以下命令让 Behat 生成代码片段:
bin/behat --tags="userDashboard" --append-snippets
然后,打开 /features/bootstrap/FeaturesContext.php 文件,修改 Given 步骤如下:
/**
* @Given I log in as Jack
*/
public function iLogInAsJack()
{
$this->visit('/login');
$this->fillField('username', 'Jack');
$this->fillField('password', 'jackpass');
$this->pressButton('_submit');
}
/**
* @Given I visit :arg1
*/
public function iVisit($arg1)
{
$this->visit($arg1);
}
运行测试,通常会失败,这意味着我们需要实现感兴趣页面的模板。如果安装了上一章的模板或从原始 GitHub 仓库检出了 chapter08 分支,重新运行测试应该会通过。
实现已完成任务板块的逻辑
为了准确统计已完成任务的数量,我们需要进行以下操作:
1. 添加任务状态属性 :在 Task 实体中添加一个名为 status 的新属性,并更新数据库:
// src/AppBundle/Entity/Task.php
/**
* @var string
*
* @ORM\Column(name="status", type="string", nullable=false)
*/
private $status;
$ bin/console doctrine:schema:update --force
- 定义任务状态值 :在
TaskType表单中定义任务状态的可能值:
// src/AppBundle/Form/TaskType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// … rest of task form
->add('status', ChoiceType::class, array(
'choices' => array('new' => 'new',
'in progress' => 'in progress',
'completed' => 'completed'),
));
}
- 创建查询已完成任务的方法 :在
TaskRepository中创建一个方法来查找具有完成状态的任务:
// src/AppBundle/Entity/TaskRepository
public function finishedTasks($projectId){
$q = $this->createQueryBuilder('t')
->where('t.project = :projectId')
->andWhere('t.status = :completed')
->setParameter('projectId', $projectId)
->setParameter('completed', 'completed')
->getQuery();
return $q->getResult();
}
- 查找当前工作区的所有已完成任务 :为了查找当前工作区的所有已完成任务,我们需要结合
ProjectRepository和TaskRepository的方法。首先,在ProjectRepository中创建一个方法来查找同一工作区的所有项目:
// src/AppBundle/Entity/ProjectRepository
public function getAllProjects($workSpaceId){
$q = $this->createQueryBuilder('p')
->where('p.workspace = :workSpaceId')
->setParameter('workSpace_id', $workSpaceId)
->getQuery();
return $q->getResult();
}
然后,在 TaskRepository 中创建一个方法来查找每个项目的已完成任务:
public function getFinishedTasks($projectId){
$q = $this->createQueryBuilder('t')
->where('t.project = :projectId')
->andWhere('t.status = :completed')
->setParameter('projectID', $projectId)
->setParameter('completed', 'completed')
->getQuery();
return $q->getResult();
}
最后,创建一个实用类 Mava 来管理这些实体请求,并计算所有已完成任务的总数:
<?php
namespace AppBundle\Util;
use Doctrine\ORM\EntityManagerInterface;
class Mava {
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function finishedTasks($wsId)
{
$projects = $this->wsAllProjects($wsId);
$taskRepo = $this->em->getRepository('CoreBundle:Task');
$total = 0;
foreach ($projects as $project){
$total += count($taskRepo->getFinishedTasks($project->getId()));
}
return $total;
}
public function wsAllProjects($wsID){
return $this->em
->getRepository('CoreBundle:Project')
->getAllProjects($wsID);
}
}
实现仪表盘控制器
为了使用实用类中的方法,我们将其定义为一个服务。在 CoreBundle 中添加以下服务定义:
{# /src/AppBundle/Resources/config/services.yml #}
services:
mava_util:
class: AppBundle\Util\Mava
arguments: ['@doctrine.orm.entity_manager']
然后,在仪表盘控制器中访问该服务:
<?php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DashboardController extends Controller
{
public function indexAction($ws=null)
{
$util = $this->get('mava_util');
$finishedTasks = $util->finishedTasks($ws);
return $this->render(
'CoreBundle:Dashboard:index.html.twig', array(
'finishedTasks' => $finishedTasks,
));
}
}
在模板中接收并显示已完成任务的总数:
{# src/AppBundle/Resources/views/Dashboard/index.html.twig #}
{# … #}
<div class="row">
<div class="col-xs-3">
<i class="fa fa-th-list fa-5x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge">{{ finishedTasks }}</div>
<div>Finished Tasks!</div>
</div>
</div>
{# … #}
使用 SonataMediaBundle 实现文件上传
在项目中,有些任务需要添加附件(文档)。为了实现文件上传功能,我们引入 Sonata 项目的 SonataMediaBundle 。
1. 安装和配置 SonataMediaBundle :按照 官方文档 进行安装、注册和配置。注意,可能需要安装 SonataEasyExtendsBundle 和 jms/serializer-bundle 来解决依赖问题。
2. 修改任务实体 :在 Task 实体中修改 attachment 属性,使其指向 Application\Sonata\MediaBundle\Entity\Media 实体:
// src/AppBundle/Entity/Task.php
// …
/**
* @ORM\OneToOne(targetEntity= "Application\Sonata\MediaBundle\Entity\Media",cascade={"persist"})
* @ORM\JoinColumn(name="attachment_id",referencedColumnName="id")
**/
protected $attachment;
更新数据库:
$ bin/console doctrine:schema:update --force
- 更新任务表单 :打开
TaskType.php文件,修改attachment字段:
// src/AppBundle/Form/TaskType.php
<?php
// ...
class TaskType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('attachment', 'sonata_media_type', array(
'provider' => 'sonata.media.provider.file',
'context' => 'default'
))
// ...
}
}
现在,访问 /admin/task/new 页面,你将看到一个功能完整的文件上传字段,文件将上传到 web/uploads/media 文件夹。
团队与成员管理
通常,项目由多个任务组成,需要团队协作完成。因此,我们引入团队的概念,并定义团队、用户和项目实体之间的关系。
1. 定义团队实体 :团队实体可以简单定义为在同一项目上工作的所有人。以下是团队实体的定义:
<?php
class Team
{
/**
* @var integer
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
* @ORM\Column(name="Title", type="string", length=255)
*/
private $title;
/**
* @var string
* @ORM\Column(name="Description", type="text")
*/
private $description;
/**
* @ORM\OneToOne(targetEntity="AppBundle\Entity\Project")
* @ORM\JoinColumn(name="project_id", referencedColumnName="id")
*/
protected $project;
//...
}
- 更新用户实体 :在用户实体中添加
team属性,并设置其 getter 和 setter 方法:
<?php
//...
class User extends BaseUser
{
/**
* @ORM\OneToOne(targetEntity="AppBundle\Entity\Team")
* @ORM\JoinColumn(name="team_id", referencedColumnName="id")
*/
protected $team;
/**
* Set team
* @param \AppBundle\Entity\Team $team
* @return Team
*/
public function setTeam(\AppBundle\Entity\Team $team = null)
{
$this->team = $team;
return $this;
}
/**
* Get team
* @return \AppBundle\Entity\Team
*/
public function getTeam()
{
return $this->team;
}
//...
}
新创建用户的 team 值默认为 null ,避免了新用户或未加入团队用户的错误。
3. 添加团队管理功能 :定义团队管理的管理类 TeamAdmin ,并配置表单、过滤器和列表字段:
<?php
namespace AppBundle\Admin;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
class TeamAdmin extends Admin
{
// Fields to be shown on create/edit forms
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('title' , 'text')
->add('description', 'textarea')
->add('project','entity',
array(
'class' => 'CoreBundle:Project',
'property' => 'title'
));
}
// Fields to be shown on filter forms
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('title')
->add('description');
}
// Fields to be shown on lists
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('title')
->add('description');
}
}
将其作为服务添加到 admin.yml 文件中:
# src/AppBundle/Resources/config/admin.yml
services:
# ...
sonata.admin.team:
class: CoreBundle\Admin\TeamAdmin
tags:
{ name: sonata.admin, manager_type: orm, group: "Content", label: "Team" }
arguments:
- ~
- CoreBundle\Entity\Team
- ~
calls:
- [ setTranslationDomain, [CoreBundle] ]
添加通知系统
通知系统是任务管理应用程序的重要组成部分,它能让用户及时了解系统中发生的事件。以下是一些任务通知的示例,同样适用于项目、工作区、团队和用户:
- 新任务分配时
- 新任务创建时
- 任务有新附件时
- 任务有更改时
- 任务完成时
为了实现通知系统,所有实体都需要一个机制来跟踪创建时间和最后更新时间。接下来,我们将展示如何为 Task 实体实现这个机制,你可以对其他实体进行类似操作或从 Chapter08 分支获取更新后的代码。
通过以上步骤,我们对任务管理系统进行了全面的优化和功能增强,提升了系统的实用性和用户体验。在实际开发中,还可以根据具体需求进一步扩展和完善这些功能。
项目优化与功能增强:任务管理系统的多维度升级
实现任务实体的时间跟踪机制
为了实现通知系统,我们需要让 Task 实体能够跟踪创建时间和最后更新时间。以下是具体的实现步骤:
- 在
Task实体中添加时间属性
// src/AppBundle/Entity/Task.php
// ...
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* @ORM\Entity
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
* @Gedmo\Loggable
*/
class Task
{
// ...
/**
* @var \DateTime
*
* @Gedmo\Timestampable(on="create")
* @ORM\Column(name="created_at", type="datetime")
*/
private $createdAt;
/**
* @var \DateTime
*
* @Gedmo\Timestampable(on="update")
* @ORM\Column(name="updated_at", type="datetime")
*/
private $updatedAt;
/**
* @var \DateTime
*
* @ORM\Column(name="deleted_at", type="datetime", nullable=true)
*/
private $deletedAt;
// Getters and Setters
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
public function getDeletedAt(): ?\DateTimeInterface
{
return $this->deletedAt;
}
public function setDeletedAt(?\DateTimeInterface $deletedAt): self
{
$this->deletedAt = $deletedAt;
return $this;
}
}
这里使用了 Gedmo 扩展包的 Timestampable 功能,它可以自动管理实体的创建时间和更新时间。同时,添加了 deletedAt 字段用于软删除。
- 更新数据库
$ bin/console doctrine:schema:update --force
通知系统的实现思路
在实现通知系统时,我们可以采用事件监听的方式。当任务发生特定事件(如创建、分配、完成等)时,触发相应的事件监听器,生成通知信息并发送给相关人员。以下是一个简单的示例:
- 定义事件监听器
// src/AppBundle/EventListener/TaskEventListener.php
namespace AppBundle\EventListener;
use AppBundle\Entity\Task;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Workflow\Event\GuardEvent;
class TaskEventListener implements EventSubscriberInterface
{
private $session;
public function __construct(SessionInterface $session)
{
$this->session = $session;
}
public static function getSubscribedEvents()
{
return [
'workflow.task.guard_to_in_progress' => 'onGuardToInProgress',
'workflow.task.guard_to_completed' => 'onGuardToCompleted',
];
}
public function onGuardToInProgress(GuardEvent $event)
{
/** @var Task $task */
$task = $event->getSubject();
$this->session->getFlashBag()->add('notice', sprintf('Task "%s" is about to start.', $task->getTitle()));
}
public function onGuardToCompleted(GuardEvent $event)
{
/** @var Task $task */
$task = $event->getSubject();
$this->session->getFlashBag()->add('success', sprintf('Task "%s" has been completed.', $task->getTitle()));
}
}
在这个示例中,我们定义了一个事件监听器,监听任务状态变更的事件。当任务状态变为 in_progress 或 completed 时,会在会话中添加相应的通知信息。
- 注册事件监听器
# src/AppBundle/Resources/config/services.yml
services:
# ...
app.task_event_listener:
class: AppBundle\EventListener\TaskEventListener
arguments: ['@session']
tags:
- { name: kernel.event_subscriber }
评论系统的实现
评论系统可以让团队成员对每个任务进行交流和反馈。以下是实现评论系统的步骤:
- 定义评论实体
// src/AppBundle/Entity/Comment.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
* @ORM\Table(name="comments")
*/
class Comment
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Task", inversedBy="comments")
* @ORM\JoinColumn(nullable=false)
*/
private $task;
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="comments")
* @ORM\JoinColumn(nullable=false)
*/
private $author;
/**
* @ORM\Column(type="text")
* @Assert\NotBlank()
*/
private $content;
/**
* @ORM\Column(type="datetime")
*/
private $createdAt;
public function __construct()
{
$this->createdAt = new \DateTime();
}
// Getters and Setters
public function getId(): ?int
{
return $this->id;
}
public function getTask(): ?Task
{
return $this->task;
}
public function setTask(?Task $task): self
{
$this->task = $task;
return $this;
}
public function getAuthor(): ?User
{
return $this->author;
}
public function setAuthor(?User $author): self
{
$this->author = $author;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
}
评论实体与 Task 实体和 User 实体建立了关联,每个评论都有作者、内容和创建时间。
- 更新
Task实体
// src/AppBundle/Entity/Task.php
// ...
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
class Task
{
// ...
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Comment", mappedBy="task", cascade={"persist", "remove"})
*/
private $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
/**
* @return Collection|Comment[]
*/
public function getComments(): Collection
{
return $this->comments;
}
public function addComment(Comment $comment): self
{
if (!$this->comments->contains($comment)) {
$this->comments[] = $comment;
$comment->setTask($this);
}
return $this;
}
public function removeComment(Comment $comment): self
{
if ($this->comments->contains($comment)) {
$this->comments->removeElement($comment);
// set the owning side to null (unless already changed)
if ($comment->getTask() === $this) {
$comment->setTask(null);
}
}
return $this;
}
}
在 Task 实体中添加了一个 comments 集合,用于关联评论实体。
- 创建评论表单
// src/AppBundle/Form/CommentType.php
namespace AppBundle\Form;
use AppBundle\Entity\Comment;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CommentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('content')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Comment::class,
]);
}
}
创建一个简单的评论表单,包含 content 字段。
- 在控制器中处理评论
// src/AppBundle/Controller/TaskController.php
// ...
use AppBundle\Form\CommentType;
use AppBundle\Entity\Comment;
class TaskController extends Controller
{
// ...
public function addCommentAction(Request $request, Task $task)
{
$comment = new Comment();
$comment->setTask($task);
$comment->setAuthor($this->getUser());
$form = $this->createForm(CommentType::class, $comment);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($comment);
$entityManager->flush();
return $this->redirectToRoute('task_show', ['id' => $task->getId()]);
}
return $this->render('task/add_comment.html.twig', [
'task' => $task,
'form' => $form->createView(),
]);
}
}
在控制器中处理评论的提交,并将评论保存到数据库中。
总结与展望
通过以上一系列的优化和功能增强,我们为任务管理系统添加了仪表盘页面、文件上传、团队与成员管理、通知系统和评论系统等重要功能,大大提升了系统的实用性和用户体验。
在未来的开发中,我们可以进一步扩展这些功能。例如,在通知系统方面,可以实现更复杂的通知规则,如根据用户的偏好设置通知方式(邮件、短信等);在评论系统方面,可以添加评论的编辑和删除功能,以及对评论进行点赞和回复的功能。同时,还可以对系统的性能进行优化,如优化数据库查询、缓存机制等,以提高系统的响应速度和稳定性。
通过持续的优化和功能扩展,我们可以让任务管理系统更好地满足用户的需求,为团队协作提供更强大的支持。
以下是整个开发流程的 mermaid 流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([开始项目优化]):::startend --> B(规划仪表盘页面):::process
B --> C(实现已完成任务板块):::process
C --> D(实现仪表盘控制器):::process
D --> E(使用 SonataMediaBundle 实现文件上传):::process
E --> F(团队与成员管理):::process
F --> G(添加通知系统):::process
G --> H(实现任务实体时间跟踪机制):::process
H --> I(实现评论系统):::process
I --> J([完成优化]):::startend
通过这个流程图,我们可以清晰地看到整个项目优化和功能增强的步骤和顺序,有助于我们更好地理解和实施开发过程。
超级会员免费看

被折叠的 条评论
为什么被折叠?



