多步骤表单可以有效降低用户填写负担,提升提交转化率,但在实际使用中,用户点击按钮后切换浏览器Tab再返回页面,经常会遇到表单自动回到第一步、已填数据全部丢失的问题,这主要是因为表单的进度状态没有被持久化保存。借助PHP的会话存储能力,就能很好地解决这个问题。

问题产生的根本原因
普通的多步骤表单通常通过前端JavaScript控制步骤切换,或者仅用页面参数传递当前步骤,这两种方式都存在状态易丢失的问题:
- 前端JS存储的步骤状态仅在当前页面会话有效,页面刷新、重新加载后状态会被重置
- 仅靠URL参数传递步骤和信息,无法完整保存用户已填写的复杂表单数据,且参数容易被篡改
- 用户切换Tab再返回时,浏览器可能重新加载页面,导致未持久化的状态全部丢失
基于PHP会话的解决方案
PHP的$_SESSION可以在用户会话期间持久化存储数据,我们可以将多步骤表单的当前步骤、已填写的表单数据都存到会话中,每次页面加载时先读取会话中的状态,再渲染对应的表单内容。
第一步:初始化会话并定义表单步骤
首先需要在表单页面的开头启动会话,同时定义多步骤表单的所有步骤和对应的字段:
<?php
// 启动会话,必须在输出任何内容前调用
session_start();
// 定义多步骤表单的步骤配置,每个步骤包含步骤名和需要收集的字段
$formSteps = [
1 => ['name' => '基本信息', 'fields' => ['username', 'phone']],
2 => ['name' => '详细地址', 'fields' => ['province', 'city', 'address']],
3 => ['name' => '确认提交', 'fields' => []]
];
// 总步骤数
$totalSteps = count($formSteps);
// 初始化会话中的表单状态,如果不存在则设置默认值
if (!isset($_SESSION['multi_step_form'])) {
$_SESSION['multi_step_form'] = [
'current_step' => 1, // 当前步骤,默认第一步
'form_data' => [] // 已填写的表单数据
];
}
?>第二步:处理表单提交与步骤切换
当用户点击上一步、下一步或者提交按钮时,先处理提交的数据,再更新会话中的步骤和存储的数据:
<?php
// 处理表单提交逻辑
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
$currentStep = $_SESSION['multi_step_form']['current_step'];
// 如果是下一步操作,先保存当前步骤的表单数据
if ($action === 'next') {
// 获取当前步骤需要收集的字段
$currentFields = $formSteps[$currentStep]['fields'];
foreach ($currentFields as $field) {
if (isset($_POST[$field])) {
// 简单过滤输入,实际项目可根据需求做更严格的校验
$_SESSION['multi_step_form']['form_data'][$field] = htmlspecialchars(trim($_POST[$field]));
}
}
// 步骤加1,不超过总步骤数
if ($currentStep < $totalSteps) {
$_SESSION['multi_step_form']['current_step'] = $currentStep + 1;
}
} elseif ($action === 'prev') {
// 上一步操作,步骤减1,不小于1
if ($currentStep > 1) {
$_SESSION['multi_step_form']['current_step'] = $currentStep - 1;
}
} elseif ($action === 'submit') {
// 最终提交操作,保存最后一步的数据
$currentFields = $formSteps[$currentStep]['fields'];
foreach ($currentFields as $field) {
if (isset($_POST[$field])) {
$_SESSION['multi_step_form']['form_data'][$field] = htmlspecialchars(trim($_POST[$field]));
}
}
// 这里可以处理最终的数据入库、发送邮件等逻辑
// 提交完成后清空会话中的表单数据,避免重复提交
unset($_SESSION['multi_step_form']);
echo '<p>表单提交成功,感谢您的填写</p>';
exit;
}
// 处理完提交后重定向到当前页面,避免表单重复提交
header('Location: ' . $_SERVER['PHP_SELF']);
exit;
}
// 获取当前需要展示的步骤
$currentStep = $_SESSION['multi_step_form']['current_step'];
$currentStepConfig = $formSteps[$currentStep];
$savedData = $_SESSION['multi_step_form']['form_data'];
?>第三步:渲染对应步骤的表单
根据当前步骤渲染对应的表单内容,同时回填之前已经保存的表单数据:
<?php
// 渲染步骤指示器
echo '<div class="step-indicator">';
for ($i = 1; $i <= $totalSteps; $i++) {
$activeClass = $i == $currentStep ? 'active' : '';
$finishedClass = $i < $currentStep ? 'finished' : '';
echo "<span class=\"step {$activeClass} {$finishedClass}\">第{$i}步:{$formSteps[$i]['name']}</span>";
}
echo '</div>';
// 渲染当前步骤的表单
echo '<form method="POST" action="">';
echo '<h3>' . $currentStepConfig['name'] . '</h3>';
// 根据当前步骤渲染对应的表单字段
if ($currentStep == 1) {
echo '<p>用户名:<input type="text" name="username" value="' . ($savedData['username'] ?? '') . '" required></p>';
echo '<p>手机号:<input type="text" name="phone" value="' . ($savedData['phone'] ?? '') . '" required></p>';
} elseif ($currentStep == 2) {
echo '<p>省份:<input type="text" name="province" value="' . ($savedData['province'] ?? '') . '" required></p>';
echo '<p>城市:<input type="text" name="city" value="' . ($savedData['city'] ?? '') . '" required></p>';
echo '<p>详细地址:<textarea name="address">' . ($savedData['address'] ?? '') . '</textarea></p>';
} elseif ($currentStep == 3) {
// 确认页面展示所有已填写的数据
echo '<p>用户名:' . ($savedData['username'] ?? '') . '</p>';
echo '<p>手机号:' . ($savedData['phone'] ?? '') . '</p>';
echo '<p>省份:' . ($savedData['province'] ?? '') . '</p>';
echo '<p>城市:' . ($savedData['city'] ?? '') . '</p>';
echo '<p>详细地址:' . ($savedData['address'] ?? '') . '</p>';
}
// 渲染操作按钮
if ($currentStep > 1) {
echo '<button type="submit" name="action" value="prev">上一步</button>';
}
if ($currentStep < $totalSteps) {
echo '<button type="submit" name="action" value="next">下一步</button>';
} else {
echo '<button type="submit" name="action" value="submit">提交</button>';
}
echo '</form>';
?>注意事项
在实际项目中使用这个方案时,还需要注意以下几点:
- 表单数据的校验需要前后端同时做,PHP端可以做更严格的格式校验、防SQL注入等处理,上面的示例仅做了简单的过滤,生产环境需要完善
- 如果用户长时间未操作,会话会过期,此时可以提示用户重新填写,或者结合数据库做更长期的进度存储
- 如果用户主动清空浏览器缓存或关闭浏览器,会话数据会丢失,若需要更持久的存储,可以将进度存到数据库中,用用户标识关联
- 提交完成后一定要及时清空会话中的表单数据,避免用户刷新页面导致重复提交
通过上面的实现,即使用户在填写多步骤表单的过程中切换浏览器Tab再返回,或者刷新页面,都能保留之前的填写进度和当前步骤,有效提升用户的使用体验。