PHP项目Symfony form与时间轴

wen PHP项目 3

我来为你详细介绍在PHP Symfony项目中使用表单和时间轴的实现方案。

PHP项目Symfony form与时间轴

Symfony Form 与时间轴集成

基本时间轴表单组件

// src/Form/Type/TimelineType.php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TimelineType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title', TextType::class, [
                'label' => '时间轴标题',
                'attr' => ['class' => 'form-control']
            ])
            ->add('events', CollectionType::class, [
                'entry_type' => TimelineEventType::class,
                'allow_add' => true,
                'allow_delete' => true,
                'by_reference' => false,
                'prototype' => true,
                'prototype_name' => '__event__',
                'attr' => [
                    'class' => 'timeline-events-collection'
                ]
            ]);
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Timeline::class,
        ]);
    }
}

时间轴事件表单类型

// src/Form/Type/TimelineEventType.php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TimelineEventType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('date', DateType::class, [
                'widget' => 'single_text',
                'label' => '事件日期',
                'attr' => ['class' => 'form-control datepicker']
            ])
            ->add('time', TimeType::class, [
                'widget' => 'single_text',
                'label' => '时间',
                'required' => false,
                'attr' => ['class' => 'form-control timepicker']
            ])
            ->add('title', TextType::class, [
                'label' => '事件标题',
                'attr' => ['class' => 'form-control']
            ])
            ->add('description', TextareaType::class, [
                'label' => '事件描述',
                'required' => false,
                'attr' => [
                    'class' => 'form-control',
                    'rows' => 3
                ]
            ])
            ->add('type', ChoiceType::class, [
                'label' => '事件类型',
                'choices' => [
                    '里程碑' => 'milestone',
                    '任务' => 'task',
                    '提醒' => 'reminder',
                    '通知' => 'notification'
                ],
                'attr' => ['class' => 'form-control']
            ])
            ->add('status', ChoiceType::class, [
                'label' => '状态',
                'choices' => [
                    '进行中' => 'in_progress',
                    '已完成' => 'completed',
                    '已取消' => 'cancelled',
                    '待开始' => 'pending'
                ],
                'attr' => ['class' => 'form-control']
            ]);
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => TimelineEvent::class,
        ]);
    }
}

实体类定义

// src/Entity/Timeline.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[ORM\Entity(repositoryClass: TimelineRepository::class)]
class Timeline
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;
    #[ORM\Column(length: 255)]
    private ?string $title = null;
    #[ORM\Column(type: 'datetime')]
    private ?\DateTimeInterface $createdAt = null;
    #[ORM\OneToMany(mappedBy: 'timeline', targetEntity: TimelineEvent::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
    private Collection $events;
    public function __construct()
    {
        $this->events = new ArrayCollection();
        $this->createdAt = new \DateTime();
    }
    // Getters and Setters...
    public function getEvents(): Collection
    {
        return $this->events;
    }
    public function addEvent(TimelineEvent $event): self
    {
        if (!$this->events->contains($event)) {
            $this->events->add($event);
            $event->setTimeline($this);
        }
        return $this;
    }
    public function removeEvent(TimelineEvent $event): self
    {
        if ($this->events->removeElement($event)) {
            if ($event->getTimeline() === $this) {
                $event->setTimeline(null);
            }
        }
        return $this;
    }
}

控制器实现

// src/Controller/TimelineController.php
namespace App\Controller;
use App\Entity\Timeline;
use App\Form\Type\TimelineType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class TimelineController extends AbstractController
{
    #[Route('/timeline/new', name: 'timeline_new')]
    public function new(Request $request, EntityManagerInterface $entityManager): Response
    {
        $timeline = new Timeline();
        $form = $this->createForm(TimelineType::class, $timeline);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $entityManager->persist($timeline);
            $entityManager->flush();
            $this->addFlash('success', '时间轴已创建');
            return $this->redirectToRoute('timeline_show', ['id' => $timeline->getId()]);
        }
        return $this->render('timeline/new.html.twig', [
            'form' => $form->createView(),
        ]);
    }
    #[Route('/timeline/{id}/edit', name: 'timeline_edit')]
    public function edit(Request $request, Timeline $timeline, EntityManagerInterface $entityManager): Response
    {
        $form = $this->createForm(TimelineType::class, $timeline);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $entityManager->flush();
            $this->addFlash('success', '时间轴已更新');
            return $this->redirectToRoute('timeline_show', ['id' => $timeline->getId()]);
        }
        return $this->render('timeline/edit.html.twig', [
            'form' => $form->createView(),
            'timeline' => $timeline
        ]);
    }
}

Twig模板(时间轴显示)

{# templates/timeline/show.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div class="container mt-4">
    <h1>{{ timeline.title }}</h1>
    {# 时间轴组件 #}
    <div class="timeline">
        {% for event in timeline.events %}
            <div class="timeline-item">
                <div class="timeline-badge {{ event.type }}">
                    <i class="fas fa-{{ event.type|timeline_icon }}"></i>
                </div>
                <div class="timeline-panel">
                    <div class="timeline-heading">
                        <h4 class="timeline-title">{{ event.title }}</h4>
                        <p>
                            <small class="text-muted">
                                <i class="fas fa-calendar"></i> 
                                {{ event.date|date('Y-m-d') }}
                                {% if event.time %}
                                    <i class="fas fa-clock"></i>
                                    {{ event.time|date('H:i') }}
                                {% endif %}
                            </small>
                            <span class="label label-{{ event.status|timeline_status_color }}">
                                {{ event.status|trans }}
                            </span>
                        </p>
                    </div>
                    {% if event.description %}
                        <div class="timeline-body">
                            <p>{{ event.description }}</p>
                        </div>
                    {% endif %}
                </div>
            </div>
        {% endfor %}
    </div>
</div>
{% endblock %}

JavaScript增强(动态添加事件)

// assets/js/timeline-form.js
class TimelineForm {
    constructor() {
        this.collectionHolder = document.querySelector('.timeline-events-collection');
        this.addButton = document.createElement('button');
        this.setupCollection();
    }
    setupCollection() {
        this.addButton.innerText = '添加事件';
        this.addButton.className = 'btn btn-primary mt-2';
        this.collectionHolder.parentNode.appendChild(this.addButton);
        this.addButton.addEventListener('click', (e) => {
            this.addEvent();
        });
        // 添加删除按钮
        this.collectionHolder.querySelectorAll('.timeline-event-item').forEach((item) => {
            this.addDeleteButton(item);
        });
    }
    addEvent() {
        const prototype = this.collectionHolder.dataset.prototype;
        const index = this.collectionHolder.children.length;
        const newForm = prototype.replace(/__event__/g, index);
        const container = document.createElement('div');
        container.className = 'timeline-event-item';
        container.innerHTML = newForm;
        this.addDeleteButton(container);
        this.collectionHolder.appendChild(container);
    }
    addDeleteButton(item) {
        const deleteButton = document.createElement('button');
        deleteButton.innerText = '删除';
        deleteButton.className = 'btn btn-danger btn-sm mt-2';
        deleteButton.addEventListener('click', () => {
            item.remove();
        });
        item.appendChild(deleteButton);
    }
}
// 初始化
document.addEventListener('DOMContentLoaded', () => {
    new TimelineForm();
});

CSS样式

/* assets/css/timeline.css */
.timeline {
    position: relative;
    padding: 20px 0;
}
.timeline::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50px;
    width: 2px;
    background: #e9ecef;
}
.timeline-item {
    position: relative;
    margin-bottom: 30px;
    padding-left: 100px;
}
.timeline-badge {
    position: absolute;
    left: 35px;
    top: 0;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    text-align: center;
    line-height: 30px;
    color: white;
    z-index: 1;
}
.timeline-badge.milestone { background: #007bff; }
.timeline-badge.task { background: #28a745; }
.timeline-badge.reminder { background: #ffc107; }
.timeline-badge.notification { background: #17a2b8; }
.timeline-panel {
    border: 1px solid #dee2e6;
    border-radius: 5px;
    padding: 15px;
    background: white;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.timeline-panel::before {
    content: '';
    position: absolute;
    left: 90px;
    top: 10px;
    border: 10px solid transparent;
    border-right-color: #dee2e6;
}
/* 表单样式 */
.timeline-event-item {
    padding: 15px;
    margin-bottom: 10px;
    border: 1px solid #dee2e6;
    border-radius: 5px;
    background: #f8f9fa;
}

这个实现提供了完整的Symfony表单与时间轴集成方案,包括动态添加事件、美观的显示效果和完整的数据处理逻辑。

抱歉,评论功能暂时关闭!