PHP项目Symfony form与排序拖拽

wen PHP项目 1

本文目录导读:

PHP项目Symfony form与排序拖拽

  1. 使用 Sortable 字段类型扩展
  2. 前端拖拽实现方案
  3. 后端处理逻辑
  4. 使用第三方 Bundle
  5. 完整示例:文章管理排序
  6. 高级功能:拖拽到特定位置
  7. 最佳实践建议

我来详细介绍在Symfony项目中实现表单排序拖拽的几种方案。

使用 Sortable 字段类型扩展

安装必要的包

composer require doctrine/doctrine-bundle
composer require symfony/form
composer require symfony/doctrine-bridge

添加 Sortable 行为 (使用 DoctrineExtensions)

Entity 配置:

// src/Entity/Article.php
use Gedmo\Mapping\Annotation as Gedmo;
class Article
{
    /**
     * @Gedmo\SortablePosition
     * @ORM\Column(name="position", type="integer")
     */
    private $position;
}

前端拖拽实现方案

方案A:使用 SortableJS(推荐)

安装:

npm install sortablejs

Symfony Twig 模板:

{# templates/admin/sortable.html.twig #}
{% block body %}
<div class="sortable-container">
    {{ form_start(form) }}
    <ul id="sortable-list" class="list-group">
        {% for item in items %}
            <li class="list-group-item" data-id="{{ item.id }}" data-order="{{ item.position }}">
                <span class="drag-handle">≡</span>
                {{ item.name }}
            </li>
        {% endfor %}
    </ul>
    <input type="hidden" id="sort-order" name="sort_order">
    {{ form_end(form) }}
</div>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<script>
    var el = document.getElementById('sortable-list');
    var sortable = Sortable.create(el, {
        handle: '.drag-handle',
        animation: 150,
        onEnd: function(evt) {
            updateSortOrder();
        }
    });
    function updateSortOrder() {
        var items = document.querySelectorAll('#sortable-list li');
        var order = [];
        items.forEach(function(item, index) {
            order.push({
                id: item.dataset.id,
                position: index + 1
            });
        });
        document.getElementById('sort-order').value = JSON.stringify(order);
    }
</script>
{% endblock %}

方案B:使用 jQuery UI Sortable

npm install jquery-ui
<script>
    $(function() {
        $("#sortable-list").sortable({
            handle: '.drag-handle',
            update: function(event, ui) {
                updateSortOrder();
            }
        });
        function updateSortOrder() {
            var order = [];
            $("#sortable-list li").each(function(index) {
                order.push({
                    id: $(this).data('id'),
                    position: index + 1
                });
            });
            $('#sort-order').val(JSON.stringify(order));
        }
    });
</script>

后端处理逻辑

Controller 处理排序

// src/Controller/Admin/SortController.php
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
class SortController extends AbstractController
{
    #[Route('/admin/sort/update', name: 'admin_sort_update', methods: ['POST'])]
    public function updateSort(Request $request, EntityManagerInterface $em): Response
    {
        $orderData = json_decode($request->request->get('sort_order'), true);
        foreach ($orderData as $data) {
            $entity = $em->getRepository(Article::class)->find($data['id']);
            if ($entity) {
                $entity->setPosition($data['position']);
                $em->persist($entity);
            }
        }
        $em->flush();
        return new Response('Sort updated successfully');
    }
}

使用 Ajax 保存排序

// JavaScript
function saveSortOrder(orderData) {
    fetch('/admin/sort/update', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'X-Requested-With': 'XMLHttpRequest'
        },
        body: new URLSearchParams({
            sort_order: JSON.stringify(orderData)
        })
    })
    .then(response => response.text())
    .then(data => {
        console.log('Sort saved:', data);
    })
    .catch(error => {
        console.error('Error:', error);
    });
}
// 在 Sortable 事件中调用
onEnd: function(evt) {
    var order = getSortOrder();
    saveSortOrder(order);
}

使用第三方 Bundle

KnpDoctrineBehaviors

composer require knplabs/doctrine-behaviors

Entity 配置:

use Knp\DoctrineBehaviors\Model\Sortable\SortableTrait;
class Article
{
    use SortableTrait;
}

Admingenerator Bundle

composer require cedriclombardot/admingenerator-generator-bundle

完整示例:文章管理排序

Entity

// src/Entity/Article.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: ArticleRepository::class)]
class Article
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;
    #[ORM\Column(length: 255)]
    private ?string $name = null;
    #[ORM\Column(type: 'integer')]
    private int $position = 0;
    // Getters and Setters
    public function getId(): ?int { return $this->id; }
    public function getName(): ?string { return $this->name; }
    public function setName(string $name): self { $this->name = $name; return $this; }
    public function getPosition(): int { return $this->position; }
    public function setPosition(int $position): self { $this->position = $position; return $this; }
}

Form Type

// src/Form/ArticleSortType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ArticleSortType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        // 无需添加额外字段,表单用于 CSRF 保护
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'csrf_protection' => true,
        ]);
    }
}

Controller

// src/Controller/Admin/ArticleController.php
namespace App\Controller\Admin;
use App\Entity\Article;
use App\Form\ArticleSortType;
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;
#[Route('/admin/article')]
class ArticleController extends AbstractController
{
    #[Route('/', name: 'admin_article_list')]
    public function list(EntityManagerInterface $em): Response
    {
        $articles = $em->getRepository(Article::class)
            ->findBy([], ['position' => 'ASC']);
        $form = $this->createForm(ArticleSortType::class);
        return $this->render('admin/article/list.html.twig', [
            'articles' => $articles,
            'form' => $form->createView(),
        ]);
    }
    #[Route('/sort', name: 'admin_article_sort', methods: ['POST'])]
    public function sort(Request $request, EntityManagerInterface $em): Response
    {
        if (!$request->isXmlHttpRequest()) {
            throw $this->createNotFoundException();
        }
        $orderData = json_decode($request->request->get('sort_order'), true);
        foreach ($orderData as $data) {
            $article = $em->getRepository(Article::class)->find($data['id']);
            if ($article) {
                $article->setPosition($data['position']);
            }
        }
        $em->flush();
        return $this->json(['success' => true]);
    }
}

高级功能:拖拽到特定位置

支持拖拽到特定分类或父级

// 分组拖拽
var sortable = Sortable.create(document.getElementById('sortable-list'), {
    group: {
        name: 'shared',
        put: function(to) {
            return to.el.children.length < 5; // 限制数量
        }
    }
});
// 拖拽到不同容器
var sortable1 = Sortable.create(document.getElementById('category-1'), {
    group: 'articles',
    onAdd: function(evt) {
        // 处理移动到新分类
        updateCategory(evt.item.dataset.id, 1);
    }
});
var sortable2 = Sortable.create(document.getElementById('category-2'), {
    group: 'articles',
    onAdd: function(evt) {
        updateCategory(evt.item.dataset.id, 2);
    }
});

最佳实践建议

  1. 性能优化:对于大量数据,考虑使用批量更新
  2. 防抖处理:拖拽结束后延时保存
  3. 错误处理:添加失败提示和重试机制
  4. 权限控制:确保只有有权限的用户可以排序
  5. 缓存失效:更新排序后清理相关缓存

这样你就可以在 Symfony 项目中实现完整的拖拽排序功能了!

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