本文目录导读:

我来为你详细介绍 Symfony Form 和导出模板的实现方案。
Symfony Form 创建
创建表单类
<?php
// src/Form/ExportType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ExportType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('exportFormat', ChoiceType::class, [
'label' => '导出格式',
'choices' => [
'CSV' => 'csv',
'Excel (XLSX)' => 'xlsx',
'PDF' => 'pdf',
'JSON' => 'json',
'XML' => 'xml',
],
'expanded' => true, // 显示为单选按钮
'multiple' => false,
])
->add('dateRange', ChoiceType::class, [
'label' => '日期范围',
'choices' => [
'全部' => 'all',
'#39; => 'today',
'本周' => 'this_week',
'本月' => 'this_month',
'自定义范围' => 'custom',
],
])
->add('startDate', DateType::class, [
'label' => '开始日期',
'widget' => 'single_text',
'required' => false,
'attr' => ['class' => 'datepicker'],
])
->add('endDate', DateType::class, [
'label' => '结束日期',
'widget' => 'single_text',
'required' => false,
'attr' => ['class' => 'datepicker'],
])
->add('includeHeaders', CheckboxType::class, [
'label' => '包含表头',
'required' => false,
'data' => true,
])
->add('columns', ChoiceType::class, [
'label' => '选择导出列',
'choices' => $options['available_columns'],
'multiple' => true,
'expanded' => true,
'attr' => ['class' => 'column-selector'],
])
->add('encoding', ChoiceType::class, [
'label' => '编码方式',
'choices' => [
'UTF-8' => 'UTF-8',
'GBK' => 'GBK',
'ISO-8859-1' => 'ISO-8859-1',
],
'data' => 'UTF-8',
])
->add('template', FileType::class, [
'label' => '上传导出模板',
'mapped' => false,
'required' => false,
'attr' => [
'accept' => '.xlsx,.xls,.csv,.xml',
],
])
->add('export', SubmitType::class, [
'label' => '导出数据',
'attr' => ['class' => 'btn-primary'],
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'available_columns' => [],
]);
}
}
控制器实现
<?php
// src/Controller/ExportController.php
namespace App\Controller;
use App\Form\ExportType;
use App\Service\ExportService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class ExportController extends AbstractController
{
#[Route('/export', name: 'app_export')]
public function export(Request $request, ExportService $exportService): Response
{
// 获取可用的列选项
$availableColumns = [
'ID' => 'id',
'用户名' => 'username',
'邮箱' => 'email',
'创建时间' => 'created_at',
'状态' => 'status',
// ... 更多列
];
$form = $this->createForm(ExportType::class, null, [
'available_columns' => $availableColumns,
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$templateFile = $form->get('template')->getData();
try {
// 获取要导出的数据
$exportData = $this->getExportData($data);
// 调用导出服务
$response = $exportService->export(
$exportData,
$data['exportFormat'],
$data['includeHeaders'],
$data['columns'],
$templateFile
);
return $response;
} catch (\Exception $e) {
$this->addFlash('error', '导出失败: ' . $e->getMessage());
}
}
return $this->render('export/index.html.twig', [
'form' => $form->createView(),
'exportHistory' => $this->getExportHistory(),
]);
}
private function getExportData(array $filters): array
{
// 根据筛选条件查询数据
$queryBuilder = $this->getDoctrine()
->getRepository(User::class)
->createQueryBuilder('u');
if ($filters['dateRange'] !== 'all') {
// 处理日期范围
$dateFilter = $this->buildDateFilter($filters);
$queryBuilder->andWhere('u.createdAt BETWEEN :start AND :end')
->setParameter('start', $dateFilter['start'])
->setParameter('end', $dateFilter['end']);
}
return $queryBuilder->getQuery()->getResult();
}
private function buildDateFilter(array $filters): array
{
$now = new \DateTime();
$start = clone $now;
$end = clone $now;
switch ($filters['dateRange']) {
case 'today':
$start->setTime(0, 0, 0);
break;
case 'this_week':
$start->modify('monday this week')->setTime(0, 0, 0);
break;
case 'this_month':
$start->modify('first day of this month')->setTime(0, 0, 0);
break;
case 'custom':
$start = $filters['startDate'];
$end = $filters['endDate'];
break;
}
return ['start' => $start, 'end' => $end];
}
private function getExportHistory(): array
{
// 获取最近的导出历史
return $this->getDoctrine()
->getRepository(ExportHistory::class)
->findBy(['user' => $this->getUser()], ['createdAt' => 'DESC'], 10);
}
}
导出服务层
<?php
// src/Service/ExportService.php
namespace App\Service;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
class ExportService
{
public function export(
array $data,
string $format,
bool $includeHeaders,
array $selectedColumns,
$templateFile = null
): Response {
switch ($format) {
case 'csv':
return $this->exportCsv($data, $includeHeaders, $selectedColumns);
case 'xlsx':
return $this->exportExcel($data, $includeHeaders, $selectedColumns, $templateFile);
case 'pdf':
return $this->exportPdf($data, $includeHeaders, $selectedColumns);
case 'json':
return $this->exportJson($data, $selectedColumns);
case 'xml':
return $this->exportXml($data, $selectedColumns);
default:
throw new \InvalidArgumentException('不支持的导出格式');
}
}
private function processData(array $data, array $selectedColumns): array
{
$processed = [];
foreach ($data as $item) {
$row = [];
foreach ($selectedColumns as $column) {
$row[$column] = $this->getColumnValue($item, $column);
}
$processed[] = $row;
}
return $processed;
}
private function getColumnValue($item, string $column): mixed
{
// 处理对象或数组
if (is_object($item)) {
$getter = 'get' . ucfirst($column);
return method_exists($item, $getter) ? $item->$getter() : null;
}
return $item[$column] ?? null;
}
private function exportCsv(array $data, bool $includeHeaders, array $selectedColumns): Response
{
$processedData = $this->processData($data, $selectedColumns);
$handle = fopen('php://temp', 'r+');
if ($includeHeaders) {
fputcsv($handle, array_keys($selectedColumns));
}
foreach ($processedData as $row) {
fputcsv($handle, $row);
}
rewind($handle);
$content = stream_get_contents($handle);
fclose($handle);
$response = new Response($content);
$response->headers->set('Content-Type', 'text/csv');
$response->headers->set('Content-Disposition', 'attachment; filename="export.csv"');
return $response;
}
private function exportExcel(
array $data,
bool $includeHeaders,
array $selectedColumns,
$templateFile = null
): Response {
if ($templateFile) {
// 使用模板文件
$spreadsheet = IOFactory::load($templateFile->getPathname());
$worksheet = $spreadsheet->getActiveSheet();
// 找到数据开始的行
$startRow = $this->findDataStartRow($worksheet);
} else {
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$startRow = 1;
if ($includeHeaders) {
$col = 'A';
foreach ($selectedColumns as $header => $field) {
$worksheet->setCellValue($col . $startRow, $header);
$col++;
}
$startRow++;
}
}
// 填充数据
$processedData = $this->processData($data, $selectedColumns);
foreach ($processedData as $rowIndex => $rowData) {
$col = 'A';
foreach ($rowData as $value) {
$worksheet->setCellValue($col . ($startRow + $rowIndex), $value);
$col++;
}
}
// 自动调整列宽
foreach (range('A', $worksheet->getHighestColumn()) as $col) {
$worksheet->getColumnDimension($col)->setAutoSize(true);
}
// 生成临时文件
$writer = new Xlsx($spreadsheet);
$tempFile = tempnam(sys_get_temp_dir(), 'export');
$writer->save($tempFile);
$response = new BinaryFileResponse($tempFile);
$response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
'export.xlsx'
);
return $response;
}
private function findDataStartRow($worksheet): int
{
// 查找模板中的数据起始行
foreach ($worksheet->getRowIterator() as $row) {
$cellValue = $worksheet->getCell('A' . $row->getRowIndex())->getValue();
if (strpos($cellValue, '{data_start}') !== false) {
return $row->getRowIndex() + 1;
}
}
return 1;
}
}
模板系统实现
<?php
// src/Service/ExportTemplateService.php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Twig\Environment;
class ExportTemplateService
{
private $twig;
private $entityManager;
public function __construct(Environment $twig, EntityManagerInterface $entityManager)
{
$this->twig = $twig;
$this->entityManager = $entityManager;
}
public function createTemplateFromExcel(UploadedFile $file, string $name): ExportTemplate
{
$template = new ExportTemplate();
$template->setName($name);
$template->setFileName($this->uploadTemplate($file));
$template->setCreatedAt(new \DateTime());
// 解析模板结构
$this->parseTemplateStructure($template, $file);
$this->entityManager->persist($template);
$this->entityManager->flush();
return $template;
}
public function renderTemplate(ExportTemplate $template, array $data): string
{
$templateContent = file_get_contents($template->getFilePath());
return $this->twig->createTemplate($templateContent)->render([
'data' => $data,
'export_date' => new \DateTime(),
'app_name' => $_ENV['APP_NAME'],
]);
}
private function uploadTemplate(UploadedFile $file): string
{
$filename = uniqid() . '.' . $file->getClientOriginalExtension();
$file->move($this->getTemplateDirectory(), $filename);
return $filename;
}
private function getTemplateDirectory(): string
{
$dir = __DIR__ . '/../../var/export_templates';
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
return $dir;
}
}
前端模板 (Twig)
{# templates/export/index.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}数据导出{% endblock %}
{% block stylesheets %}
<style>
.export-form {
max-width: 800px;
margin: 20px auto;
}
.export-options {
background: #f8f9fa;
padding: 20px;
border-radius: 5px;
margin-bottom: 20px;
}
.column-selector {
columns: 3;
-webkit-columns: 3;
-moz-columns: 3;
}
.export-history {
margin-top: 30px;
}
.loading-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255,255,255,0.8);
z-index: 1000;
}
.loading-spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
{% endblock %}
{% block body %}
<div class="container export-form">
<h1>数据导出</h1>
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
选择导出格式和列,点击"导出数据"按钮开始导出
</div>
{{ form_start(form, {'attr': {'class': 'export-form', 'id': 'exportForm'}}) }}
<div class="export-options">
<h4>导出格式</h4>
<div class="row">
{% for child in form.exportFormat %}
<div class="col-md-3">
<div class="form-check">
{{ form_widget(child) }}
{{ form_label(child) }}
</div>
</div>
{% endfor %}
</div>
</div>
<div class="export-options">
<h4>日期范围</h4>
<div class="row">
<div class="col-md-6">
{{ form_row(form.dateRange) }}
</div>
</div>
<div class="row date-range-custom" style="display: none;">
<div class="col-md-6">
{{ form_row(form.startDate) }}
</div>
<div class="col-md-6">
{{ form_row(form.endDate) }}
</div>
</div>
</div>
<div class="export-options">
<h4>导出设置</h4>
<div class="row">
<div class="col-md-6">
{{ form_row(form.includeHeaders) }}
</div>
<div class="col-md-6">
{{ form_row(form.encoding) }}
</div>
</div>
</div>
<div class="export-options">
<h4>选择导出列</h4>
{{ form_widget(form.columns, {'attr': {'class': 'column-selector'}}) }}
</div>
<div class="export-options">
<h4>使用模板(可选)</h4>
{{ form_row(form.template) }}
<small class="form-text text-muted">
支持的格式: XLSX, XLS, CSV, XML
</small>
</div>
<div class="text-center">
{{ form_widget(form.export, {'attr': {'class': 'btn btn-lg btn-primary'}}) }}
</div>
{{ form_end(form) }}
{% if exportHistory is not empty %}
<div class="export-history">
<h3>最近的导出记录</h3>
<table class="table">
<thead>
<tr>
<th>时间</th>
<th>格式</th>
<th>记录数</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for history in exportHistory %}
<tr>
<td>{{ history.createdAt|date('Y-m-d H:i:s') }}</td>
<td>{{ history.format }}</td>
<td>{{ history.recordCount }}</td>
<td>
<span class="badge badge-{{ history.status == 'success' ? 'success' : 'danger' }}">
{{ history.status == 'success' ? '成功' : '失败' }}
</span>
</td>
<td>
<a href="{{ path('app_export_download', {'id': history.id}) }}"
class="btn btn-sm btn-secondary">
下载
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<div id="loadingOverlay" class="loading-overlay">
<div class="loading-spinner text-center">
<div class="spinner-border text-primary" role="status">
<span class="sr-only">正在导出...</span>
</div>
<p class="mt-2">正在生成导出文件,请稍候...</p>
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script>
$(document).ready(function() {
// 日期范围选择切换
$('input[name="export_form[dateRange]"]').change(function() {
if ($(this).val() === 'custom') {
$('.date-range-custom').show();
} else {
$('.date-range-custom').hide();
}
});
// 全选/取消全选
$('#selectAllColumns').change(function() {
$('.column-selector input[type="checkbox"]').prop('checked', $(this).is(':checked'));
});
// 导出按钮点击
$('#exportForm').submit(function(e) {
e.preventDefault();
// 验证至少选择了一列
if ($('.column-selector input[type="checkbox"]:checked').length === 0) {
alert('请至少选择一列进行导出');
return;
}
// 显示加载动画
$('#loadingOverlay').show();
// 提交表单
this.submit();
});
// 导出格式预览
$('input[name="export_form[exportFormat]"]').change(function() {
var format = $(this).val();
var previewText = '';
switch(format) {
case 'csv':
previewText = '导出为CSV格式,可用Excel打开';
break;
case 'xlsx':
previewText = '导出为Excel 2007+格式';
break;
case 'pdf':
previewText = '导出为PDF文档格式';
break;
case 'json':
previewText = '导出为JSON数据格式';
break;
}
$('#formatPreview').text(previewText);
});
});
// 显示导出进度
function showExportProgress() {
var progress = 0;
var interval = setInterval(function() {
progress += Math.random() * 10;
if (progress > 90) {
clearInterval(interval);
}
$('#exportProgress').css('width', progress + '%');
}, 1000);
}
</script>
{% endblock %}
高级导出功能
支持大数据导出
<?php
// src/Service/LargeDataExportService.php
namespace App\Service;
use Symfony\Component\HttpFoundation\StreamedResponse;
class LargeDataExportService
{
public function streamCsvExport(callable $dataProvider, array $headers, string $filename): StreamedResponse
{
$response = new StreamedResponse(function() use ($dataProvider, $headers) {
$handle = fopen('php://output', 'w');
// 写入表头
fputcsv($handle, $headers);
// 逐块处理数据
$page = 1;
while ($data = $dataProvider($page)) {
foreach ($data as $row) {
fputcsv($handle, $row);
}
$page++;
flush();
}
fclose($handle);
});
$response->headers->set('Content-Type', 'text/csv');
$response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"');
return $response;
}
}
这个完整的方案包含了:
- 完整的表单系统 - 支持各种导出选项
- 多种格式导出 - CSV、Excel、PDF、JSON、XML
- 模板系统 - 支持自定义导出模板
- 前端优化 - 友好的用户界面和交互
- 高级功能 - 大数据导出、进度显示等
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播