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

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表单与时间轴集成方案,包括动态添加事件、美观的显示效果和完整的数据处理逻辑。