本文目录导读:

- 创建表单类型
- 控制器处理 AJAX 请求
- 前端模板和 JavaScript
- 使用 Fetch API 发送 JSON 格式
- 使用 jQuery 的替代方案
- 配置 CSRF 保护
- 可选:创建独立的 AJAX 端点
- 注意事项
在 Symfony 项目中使用 AJAX 提交表单是一个常见的需求,以下是完整的实现方案:
创建表单类型
// src/Form/ContactType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, [
'label' => '姓名',
'attr' => ['class' => 'form-control']
])
->add('email', EmailType::class, [
'label' => '邮箱',
'attr' => ['class' => 'form-control']
])
->add('message', TextareaType::class, [
'label' => '消息',
'attr' => ['class' => 'form-control', 'rows' => 5]
])
->add('submit', SubmitType::class, [
'label' => '提交',
'attr' => ['class' => 'btn btn-primary']
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => true,
'csrf_field_name' => '_token',
'csrf_token_id' => 'contact_item',
]);
}
}
控制器处理 AJAX 请求
// src/Controller/ContactController.php
namespace App\Controller;
use App\Form\ContactType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Form\FormInterface;
class ContactController extends AbstractController
{
#[Route('/contact', name: 'contact', methods: ['GET', 'POST'])]
public function contact(Request $request): JsonResponse
{
$form = $this->createForm(ContactType::class);
$form->handleRequest($request);
if ($request->isXmlHttpRequest() && $request->isMethod('POST')) {
// 处理 JSON 格式的提交
if ($request->getContentType() === 'json') {
$data = json_decode($request->getContent(), true);
$form->submit($data);
} else {
// 处理表单数据格式
$form->submit($request->request->all());
}
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
// 这里处理业务逻辑,比如发送邮件
// $this->sendContactEmail($data);
return $this->json([
'success' => true,
'message' => '表单提交成功!',
'data' => $data
]);
}
// 表单验证失败
$errors = $this->getFormErrors($form);
return $this->json([
'success' => false,
'message' => '表单验证失败',
'errors' => $errors
], 422);
}
// 非 AJAX 请求直接返回表单视图
return $this->render('contact/index.html.twig', [
'form' => $form->createView(),
]);
}
private function getFormErrors(FormInterface $form): array
{
$errors = [];
foreach ($form->getErrors(true) as $error) {
$fieldName = $error->getOrigin()->getName();
$errors[$fieldName][] = $error->getMessage();
}
return $errors;
}
}
前端模板和 JavaScript
{# templates/contact/index.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div class="container mt-5">
<h1>联系我们</h1>
<div id="form-messages" class="mb-3"></div>
{{ form_start(form, {'attr': {'id': 'contact-form', 'novalidate': 'novalidate'}}) }}
<div class="form-group mb-3">
{{ form_label(form.name) }}
{{ form_widget(form.name) }}
<div class="invalid-feedback" data-error-for="name"></div>
</div>
<div class="form-group mb-3">
{{ form_label(form.email) }}
{{ form_widget(form.email) }}
<div class="invalid-feedback" data-error-for="email"></div>
</div>
<div class="form-group mb-3">
{{ form_label(form.message) }}
{{ form_widget(form.message) }}
<div class="invalid-feedback" data-error-for="message"></div>
</div>
{{ form_rest(form) }}
<button type="submit" id="submit-btn" class="btn btn-primary">
<span class="spinner-border spinner-border-sm d-none" role="status"></span>
提交
</button>
{{ form_end(form) }}
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('contact-form');
const submitBtn = document.getElementById('submit-btn');
const spinner = submitBtn.querySelector('.spinner-border');
const messagesDiv = document.getElementById('form-messages');
form.addEventListener('submit', function(e) {
e.preventDefault();
// 清除之前的错误信息
clearErrors();
clearMessages();
// 显示加载状态
setLoading(true);
// 收集表单数据
const formData = new FormData(form);
// 发送 AJAX 请求
fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => {
if (!response.ok) {
return response.json().then(data => {
throw data;
});
}
return response.json();
})
.then(data => {
if (data.success) {
showMessage('success', data.message);
form.reset();
// 可选:提交成功后重定向
// window.location.href = '/thank-you';
} else {
showMessage('error', data.message);
if (data.errors) {
displayErrors(data.errors);
}
}
})
.catch(error => {
if (error.errors) {
showMessage('error', error.message);
displayErrors(error.errors);
} else {
showMessage('error', '网络错误,请稍后重试');
}
})
.finally(() => {
setLoading(false);
});
});
function setLoading(loading) {
if (loading) {
submitBtn.disabled = true;
spinner.classList.remove('d-none');
submitBtn.textContent = ' 提交中...';
submitBtn.prepend(spinner);
} else {
submitBtn.disabled = false;
spinner.classList.add('d-none');
submitBtn.textContent = '提交';
}
}
function showMessage(type, message) {
messagesDiv.innerHTML = `
<div class="alert alert-${type === 'success' ? 'success' : 'danger'} alert-dismissible fade show" role="alert">
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
`;
}
function clearMessages() {
messagesDiv.innerHTML = '';
}
function clearErrors() {
document.querySelectorAll('.is-invalid').forEach(el => {
el.classList.remove('is-invalid');
});
document.querySelectorAll('.invalid-feedback').forEach(el => {
el.textContent = '';
});
}
function displayErrors(errors) {
Object.keys(errors).forEach(fieldName => {
const input = document.querySelector(`[name="contact[${fieldName}]"]`);
const errorDiv = document.querySelector(`[data-error-for="${fieldName}"]`);
if (input && errorDiv) {
input.classList.add('is-invalid');
errorDiv.textContent = errors[fieldName].join(', ');
}
});
}
});
</script>
{% endblock %}
使用 Fetch API 发送 JSON 格式
// 替代方案:使用 JSON 格式提交
form.addEventListener('submit', function(e) {
e.preventDefault();
setLoading(true);
clearErrors();
clearMessages();
// 构建 JSON 数据
const formData = new FormData(form);
const jsonData = {};
formData.forEach((value, key) => {
// 处理 Symfony 表单的字段名格式:contact[name]
const match = key.match(/contact\[(.+)\]/);
if (match) {
jsonData[match[1]] = value;
} else {
jsonData[key] = value;
}
});
fetch(form.action, {
method: 'POST',
body: JSON.stringify(jsonData),
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showMessage('success', data.message);
form.reset();
} else {
showMessage('error', data.message);
if (data.errors) {
displayErrors(data.errors);
}
}
})
.catch(error => {
showMessage('error', '提交失败,请重试');
})
.finally(() => {
setLoading(false);
});
});
使用 jQuery 的替代方案
// 如果项目中使用了 jQuery
$('#contact-form').on('submit', function(e) {
e.preventDefault();
var $form = $(this);
var $submitBtn = $('#submit-btn');
var $spinner = $submitBtn.find('.spinner-border');
var $messages = $('#form-messages');
// 清理错误
$('.is-invalid').removeClass('is-invalid');
$('.invalid-feedback').text('');
$messages.empty();
// 禁用按钮
$submitBtn.prop('disabled', true);
$spinner.removeClass('d-none');
$.ajax({
url: $form.attr('action'),
method: 'POST',
data: $form.serialize(),
dataType: 'json',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
success: function(response) {
if (response.success) {
$messages.html(
'<div class="alert alert-success">' + response.message + '</div>'
);
$form[0].reset();
} else {
$messages.html(
'<div class="alert alert-danger">' + response.message + '</div>'
);
if (response.errors) {
$.each(response.errors, function(field, errors) {
var $input = $('[name="contact[' + field + ']"]');
var $errorDiv = $('[data-error-for="' + field + '"]');
$input.addClass('is-invalid');
$errorDiv.text(errors.join(', '));
});
}
}
},
error: function(xhr) {
var response = xhr.responseJSON;
$messages.html(
'<div class="alert alert-danger">' + (response.message || '请求失败') + '</div>'
);
},
complete: function() {
$submitBtn.prop('disabled', false);
$spinner.addClass('d-none');
}
});
});
配置 CSRF 保护
# config/packages/framework.yaml
framework:
csrf_protection: ~
session:
handler_id: null
可选:创建独立的 AJAX 端点
// 专门用于 AJAX 提交的独立端点
#[Route('/contact/ajax', name: 'contact_ajax', methods: ['POST'])]
public function contactAjax(Request $request): JsonResponse
{
if (!$request->isXmlHttpRequest()) {
throw $this->createNotFoundException('Only AJAX requests allowed');
}
$form = $this->createForm(ContactType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
// 保存或处理数据
// ...
return $this->json([
'success' => true,
'message' => '提交成功'
]);
}
$errors = [];
foreach ($form->getErrors(true) as $error) {
$errors[] = $error->getMessage();
}
return $this->json([
'success' => false,
'errors' => $errors
], 400);
}
注意事项
- CSRF Token: 确保在 AJAX 请求中包含 CSRF token
- 错误处理: 提供友好的错误提示
- 加载状态: 显示提交过程中的加载状态
- 数据验证: 前端和后端都要进行数据验证
- 安全性: 验证请求来源,防止 CSRF 攻击
这个实现方案提供了完整的 AJAX 表单提交功能,你可以根据具体需求进行调整。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播