14、项目优化与功能增强:任务管理系统的多维度升级

项目优化与功能增强:任务管理系统的多维度升级

在项目开发过程中,持续优化和功能增强是提升系统性能和用户体验的关键。本文将围绕任务管理系统展开,详细介绍如何对现有项目进行优化,并添加一系列新功能,包括仪表盘页面的完善、文件上传、团队与成员管理以及通知系统的实现。

项目优化目标与方向

为了让项目更加完善,我们将对现有功能进行回顾,并添加一些新特性。重点关注仪表盘页面,它能让用户快速了解当前工作区的项目、成员和任务之间的交互情况。同时,还将引入通知系统、评论系统和文件附件功能,以提升系统的实用性和交互性。

仪表盘页面内容规划

仪表盘页面是系统的核心页面之一,需要提供足够的信息来快速报告应用程序中的活动,同时保持加载时间合理。对于最小可行产品(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
  1. 定义任务状态值 :在 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'),
        ));
}
  1. 创建查询已完成任务的方法 :在 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();
}
  1. 查找当前工作区的所有已完成任务 :为了查找当前工作区的所有已完成任务,我们需要结合 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
  1. 更新任务表单 :打开 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;
    //...
}
  1. 更新用户实体 :在用户实体中添加 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 实体能够跟踪创建时间和最后更新时间。以下是具体的实现步骤:

  1. 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 字段用于软删除。

  1. 更新数据库
$ bin/console doctrine:schema:update --force
通知系统的实现思路

在实现通知系统时,我们可以采用事件监听的方式。当任务发生特定事件(如创建、分配、完成等)时,触发相应的事件监听器,生成通知信息并发送给相关人员。以下是一个简单的示例:

  1. 定义事件监听器
// 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 时,会在会话中添加相应的通知信息。

  1. 注册事件监听器
# src/AppBundle/Resources/config/services.yml
services:
    # ...
    app.task_event_listener:
        class: AppBundle\EventListener\TaskEventListener
        arguments: ['@session']
        tags:
            - { name: kernel.event_subscriber }
评论系统的实现

评论系统可以让团队成员对每个任务进行交流和反馈。以下是实现评论系统的步骤:

  1. 定义评论实体
// 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 实体建立了关联,每个评论都有作者、内容和创建时间。

  1. 更新 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 集合,用于关联评论实体。

  1. 创建评论表单
// 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 字段。

  1. 在控制器中处理评论
// 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

通过这个流程图,我们可以清晰地看到整个项目优化和功能增强的步骤和顺序,有助于我们更好地理解和实施开发过程。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值