PHP项目Symfony form与预览功能

wen PHP项目 3

本文目录导读:

PHP项目Symfony form与预览功能

  1. 基础表单创建
  2. 控制器实现预览功能
  3. Twig模板 - 创建文章页面
  4. 预览模态框
  5. 预览页面(独立页面版本)
  6. AJAX预览控制器
  7. 配置
  8. 关键特性说明

我将为您介绍如何在Symfony项目中实现表单与预览功能,这是一个常见的需求,特别是在内容管理系统中。

基础表单创建

// src/Form/ArticleType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
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\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ArticleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title', TextType::class, [
                'label' => '文章标题',
                'attr' => ['class' => 'form-control']
            ])
            ->add('content', TextareaType::class, [
                'label' => '文章内容',
                'attr' => [
                    'class' => 'form-control editor',
                    'rows' => 10
                ]
            ])
            ->add('category', ChoiceType::class, [
                'label' => '分类',
                'choices' => [
                    '技术' => 'tech',
                    '生活' => 'life',
                    '其他' => 'other'
                ],
                'attr' => ['class' => 'form-control']
            ])
            ->add('image', FileType::class, [
                'label' => '封面图片',
                'required' => false,
                'attr' => ['class' => 'form-control-file']
            ]);
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Article::class,
        ]);
    }
}

控制器实现预览功能

// src/Controller/ArticleController.php
namespace App\Controller;
use App\Entity\Article;
use App\Form\ArticleType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class ArticleController extends AbstractController
{
    #[Route('/article/new', name: 'article_new')]
    public function new(Request $request): Response
    {
        $article = new Article();
        $form = $this->createForm(ArticleType::class, $article);
        $form->handleRequest($request);
        // 处理预览请求
        if ($request->isMethod('POST') && $request->request->has('preview')) {
            $form->submit($request->request->get($form->getName()));
            if ($form->isSubmitted()) {
                // 验证表单数据但不保存
                $data = $form->getData();
                // 获取预览数据
                $previewData = [
                    'title' => $data->getTitle(),
                    'content' => $data->getContent(),
                    'category' => $data->getCategory(),
                ];
                // 处理上传图片的预览
                $imageFile = $form->get('image')->getData();
                if ($imageFile) {
                    // 生成临时预览路径
                    $previewData['image_preview'] = $this->generateImagePreview($imageFile);
                }
                return $this->render('article/preview.html.twig', [
                    'article' => $previewData,
                    'form' => $form->createView()
                ]);
            }
        }
        // 处理提交保存
        if ($form->isSubmitted() && $form->isValid()) {
            // 保存文章逻辑
            $entityManager = $this->getDoctrine()->getManager();
            $entityManager->persist($article);
            $entityManager->flush();
            $this->addFlash('success', '文章保存成功!');
            return $this->redirectToRoute('article_show', ['id' => $article->getId()]);
        }
        return $this->render('article/new.html.twig', [
            'form' => $form->createView(),
        ]);
    }
    private function generateImagePreview($imageFile): ?string
    {
        if (!$imageFile) {
            return null;
        }
        // 生成base64编码的图片预览
        $mimeType = $imageFile->getMimeType();
        $contents = base64_encode(file_get_contents($imageFile->getPathname()));
        return "data:{$mimeType};base64,{$contents}";
    }
}

Twig模板 - 创建文章页面

{# templates/article/new.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div class="container mt-4">
    <h1>创建新文章</h1>
    {{ include('article/_preview_modal.html.twig') }}
    {{ form_start(form, {'attr': {'id': 'article-form', 'class': 'needs-validation'}}) }}
        <div class="row">
            <div class="col-md-8">
                <div class="form-group mb-3">
                    {{ form_label(form.title) }}
                    {{ form_widget(form.title) }}
                    {{ form_errors(form.title) }}
                </div>
                <div class="form-group mb-3">
                    {{ form_label(form.content) }}
                    {{ form_widget(form.content) }}
                    {{ form_errors(form.content) }}
                </div>
            </div>
            <div class="col-md-4">
                <div class="form-group mb-3">
                    {{ form_label(form.category) }}
                    {{ form_widget(form.category) }}
                    {{ form_errors(form.category) }}
                </div>
                <div class="form-group mb-3">
                    {{ form_label(form.image) }}
                    {{ form_widget(form.image) }}
                    {{ form_errors(form.image) }}
                    <!-- 图片预览区域 -->
                    <div id="image-preview" class="mt-2" style="display: none;">
                        <img id="preview-image" src="" alt="预览图片" class="img-fluid">
                    </div>
                </div>
            </div>
        </div>
        <div class="form-group mt-4">
            <button type="submit" name="preview" class="btn btn-info" onclick="updatePreview()">
                <i class="fas fa-eye"></i> 预览
            </button>
            <button type="submit" class="btn btn-primary">
                <i class="fas fa-save"></i> 保存
            </button>
            <a href="{{ path('article_index') }}" class="btn btn-secondary">
                <i class="fas fa-times"></i> 取消
            </a>
        </div>
    {{ form_end(form) }}
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
// 图片预览
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
    const file = e.target.files[0];
    if (file) {
        const reader = new FileReader();
        reader.onload = function(e) {
            document.getElementById('image-preview').style.display = 'block';
            document.getElementById('preview-image').src = e.target.result;
        };
        reader.readAsDataURL(file);
    }
});
// 预览功能
function updatePreview() {
    // 收集表单数据
    const formData = new FormData(document.getElementById('article-form'));
    // 这里可以使用AJAX方式获取预览
    // 或者直接提交表单到服务器
}
</script>
{% endblock %}

预览模态框

{# templates/article/_preview_modal.html.twig #}
<!-- 预览模态框 -->
<div class="modal fade" id="previewModal" tabindex="-1" role="dialog">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">文章预览</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <div class="preview-container">
                    <h2 id="preview-title" class="mb-3"></h2>
                    <div id="preview-content"></div>
                    <div id="preview-meta" class="mt-3 text-muted"></div>
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
                <button type="button" class="btn btn-primary" onclick="submitForm()">确认发布</button>
            </div>
        </div>
    </div>
</div>
<script>
// 使用AJAX进行预览
function previewArticle() {
    const form = document.getElementById('article-form');
    const formData = new FormData(form);
    fetch('/article/preview', {
        method: 'POST',
        body: new URLSearchParams(formData),
        headers: {
            'X-Requested-With': 'XMLHttpRequest'
        }
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            document.getElementById('preview-title').textContent = data.title;
            document.getElementById('preview-content').innerHTML = data.content;
            document.getElementById('preview-meta').innerHTML = `
                <strong>分类:</strong>${data.category}<br>
                <strong>创建时间:</strong>${new Date().toLocaleString()}
            `;
            // 显示预览模态框
            $('#previewModal').modal('show');
        }
    })
    .catch(error => console.error('Error:', error));
}
// 实时预览功能
function livePreview() {
    const titleInput = document.querySelector('[name="article[title]"]');
    const contentInput = document.querySelector('[name="article[content]"]');
    Input.addEventListener('input', function() {
        document.getElementById('preview-title').textContent = this.value || '未命名文章';
    });
    contentInput.addEventListener('input', function() {
        document.getElementById('preview-content').innerHTML = marked(this.value); // 使用marked将markdown转为HTML
    });
}
// 页面加载后启用实时预览
document.addEventListener('DOMContentLoaded', function() {
    livePreview();
});
</script>

预览页面(独立页面版本)

{# templates/article/preview.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}文章预览 - {{ article.title }}{% endblock %}
{% block body %}
<div class="container mt-4">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">
                    <h2>文章预览</h2>
                </div>
                <div class="card-body">
                    {% if article.image_preview is defined and article.image_preview %}
                    <div class="text-center mb-4">
                        <img src="{{ article.image_preview }}" alt="封面图片" class="img-fluid" style="max-height: 400px;">
                    </div>
                    {% endif %}
                    <h1 class="mb-4">{{ article.title }}</h1>
                    <div class="mb-3">
                        <span class="badge badge-info">{{ article.category }}</span>
                    </div>
                    <div class="article-content">
                        {{ article.content|raw }}
                    </div>
                    <hr class="my-4">
                    <div class="actions">
                        <a href="{{ path('article_new') }}" class="btn btn-secondary">
                            <i class="fas fa-arrow-left"></i> 返回编辑
                        </a>
                        <button type="submit" form="article-form" class="btn btn-primary">
                            <i class="fas fa-check"></i> 确认发布
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}

AJAX预览控制器

// src/Controller/ArticleController.php - 添加AJAX预览方法
#[Route('/article/preview', name: 'article_preview_ajax', methods: ['POST'])]
public function previewAjax(Request $request): Response
{
    $article = new Article();
    $form = $this->createForm(ArticleType::class, $article);
    $form->submit($request->request->get($form->getName()));
    $response = [
        'success' => false,
        'title' => '',
        'content' => '',
        'category' => ''
    ];
    if ($form->isSubmitted()) {
        $data = $form->getData();
        $response = [
            'success' => true,
            'title' => $data->getTitle(),
            'content' => $data->getContent(),
            'category' => $data->getCategory(),
            'image' => $data->getImage() ? $data->getImage()->getClientOriginalName() : null
        ];
    }
    return $this->json($response);
}

配置

# config/packages/twig.yaml
twig:
    form_themes: ['bootstrap_4_layout.html.twig']
    # 如果使用markdown解析
    globals:
        marked: '@App\Service\MarkdownParser'
# 如果需要处理图片上传
# config/packages/oneup_flysystem.yaml (if using flysystem)

关键特性说明

  1. 双重提交策略:使用表单中的previewsubmit按钮区分预览和保存
  2. 实时预览:通过JavaScript实现表单内容的实时更新
  3. AJAX预览:使用异步请求获取服务器端处理的预览数据
  4. 图片预览:支持上传图片的前端预览和Base64编码预览

这个实现提供了完整的表单编辑和预览功能,可以根据实际需求进行调整和扩展。

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