Skip to content

AJAX 投票

本示例演示如何创建一个简单的投票功能,使用 AJAX(Specifically,现代的 fetch API)异步提交用户投票并显示结果,无需刷新整个页面。

投票界面概念:

Do you enjoy learning modern PHP and AJAX?




示例解释 - HTML 和 JavaScript(使用 fetch)

Section titled “示例解释 - HTML 和 JavaScript(使用 fetch)”

当用户选择一个选项并点击按钮时,JavaScript 会捕获投票,并使用 fetch API 将其发送到 PHP 后端脚本。然后,使用响应(包含更新后的投票结果)来更新 <div id="poll-results"> 的内容。

<!DOCTYPE html>
<html>
<head>
<title>AJAX Poll</title>
<style>
#poll-results table { border-collapse: collapse; margin-top: 10px; }
#poll-results td { padding: 5px; border: 1px solid #ccc; }
.poll-bar { background-color: lightblue; height: 20px; display: inline-block; vertical-align: middle; }
.error { color: red; margin-top: 10px; }
</style>
</head>
<body>
<div id="poll-container">
<h3>Do you enjoy learning modern PHP and AJAX?</h3>
<form id="poll-form">
<label>
<input type="radio" name="vote" value="yes" required> Yes
</label>
<br>
<label>
<input type="radio" name="vote" value="no"> No
</label>
<br><br>
<button type="button" id="submit-vote">Submit Vote</button>
</form>
<div id="poll-results" style="margin-top: 15px;"></div>
<div id="poll-error" class="error"></div>
</div>
<script>
document.getElementById('submit-vote').addEventListener('click', function() {
const form = document.getElementById('poll-form');
const resultsDiv = document.getElementById('poll-results');
const errorDiv = document.getElementById('poll-error');
const selectedOption = form.querySelector('input[name="vote"]:checked');
errorDiv.textContent = ''; // Clear previous errors
resultsDiv.innerHTML = 'Voting...'; // Provide feedback
if (!selectedOption) {
errorDiv.textContent = 'Please select an option.';
resultsDiv.innerHTML = '';
return;
}
const voteValue = selectedOption.value;
const formData = new FormData();
formData.append('vote', voteValue);
// Send the vote to the PHP backend using fetch (POST method recommended for state changes)
fetch('poll_vote.php', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
// Handle HTTP errors (like 404, 500)
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.text(); // Get the response body as text (HTML)
})
.then(html => {
// Update the results area with the HTML received from PHP
resultsDiv.innerHTML = html;
})
.catch(error => {
// Handle fetch errors (network issues, HTTP errors)
console.error('Fetch Error:', error);
errorDiv.textContent = 'Error submitting vote. Please try again. (' + error.message + ')';
resultsDiv.innerHTML = ''; // Clear 'Voting...' message
});
});
</script>
</body>
</html>

JavaScript 分析:

  • 一个事件监听器等待按钮点击事件。
  • 它找到被选中的单选按钮的值。
  • 创建一个 FormData 对象来存放 vote 值。
  • fetch() 使用表单数据向 poll_vote.php 发送一个 POST 请求。
  • .then() 处理成功的响应:检查 HTTP 状态是否为 OK,然后将响应正文读取为文本(期望从 PHP 脚本获取 HTML)。
  • 接收到的 HTML 被插入到 #poll-results div 中。
  • .catch() 处理 fetch 过程中的错误(例如,网络故障、服务器错误)。
  • 包含了基本的验证和用户反馈。

这个 PHP 脚本接收投票,更新存储的结果(本例中使用简单的文本文件),然后生成 HTML 来显示当前结果。

关于文件存储的说明:使用纯文本文件存储结果虽然简单,但由于潜在的竞态条件(race conditions,即多个用户同时写入),不适用于高流量网站。数据库(如 MySQL 或 PostgreSQL)是更标准、更健壮的解决方案。本示例使用文件锁定(LOCK_EX)作为防止竞态条件的基本措施。

<?php
declare(strict_types=1);
$filename = "poll_result.txt";
$yes = 0;
$no = 0;
// --- Step 1: Read current results safely ---
$fileHandle = fopen($filename, 'c+'); // Open for read/write, create if not exists
if (!$fileHandle) {
http_response_code(500);
echo "Error: Cannot open data file.";
exit;
}
// Attempt to get an exclusive lock (prevents simultaneous writes)
if (flock($fileHandle, LOCK_EX)) {
$content = fread($fileHandle, (filesize($filename) > 0) ? filesize($filename) : 1); // Read existing content
if ($content) {
$parts = explode("||", $content);
if (count($parts) === 2 && is_numeric($parts[0]) && is_numeric($parts[1])) {
$yes = (int)$parts[0];
$no = (int)$parts[1];
}
}
// --- Step 2: Process the vote (if any) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['vote'])) {
$vote = $_POST['vote']; // Basic: consider more validation
if ($vote === 'yes') {
$yes++;
} elseif ($vote === 'no') {
$no++;
}
// --- Step 3: Write updated results back ---
ftruncate($fileHandle, 0); // Clear the file content
rewind($fileHandle); // Move pointer to the beginning
fwrite($fileHandle, $yes . "||" . $no);
}
flock($fileHandle, LOCK_UN); // Release the lock
} else {
fclose($fileHandle);
http_response_code(503); // Service Unavailable (couldn't get lock)
echo "Error: Could not lock data file. Please try again.";
exit;
}
fclose($fileHandle);
// --- Step 4: Calculate percentages and output HTML results ---
$total = $yes + $no;
$yesPercent = ($total > 0) ? round(($yes / $total) * 100) : 0;
$noPercent = ($total > 0) ? round(($no / $total) * 100) : 0;
// Ensure percentages add up correctly after rounding (adjust largest if needed)
if ($total > 0 && ($yesPercent + $noPercent) !== 100) {
if ($yesPercent > $noPercent) $yesPercent = 100 - $noPercent;
else $noPercent = 100 - $yesPercent;
}
?>
<h2>Result:</h2>
<table>
<tr>
<td>Yes:</td>
<td>
<span class="poll-bar" style="width:<?php echo $yesPercent * 2; // Scale width for display ?>px;"></span>
<?php echo $yesPercent; ?>% (<?php echo $yes; ?> votes)
</td>
</tr>
<tr>
<td>No:</td>
<td>
<span class="poll-bar" style="width:<?php echo $noPercent * 2; ?>px;"></span>
<?php echo $noPercent; ?>% (<?php echo $no; ?> votes)
</td>
</tr>
</table>

PHP 脚本逻辑:

  1. 打开/锁定文件: 以读写模式 (c+) 打开 poll_result.txt,如果文件不存在则创建。尝试获取独占锁 (flock) 以防止竞态条件。
  2. 读取数据: 从文件中读取当前计数(例如 10||5)。处理空文件或无效文件内容。
  3. 处理投票: 检查请求是否为 POST,以及 vote 值(‘yes’ 或 ‘no’)是否存在。增加相应的计数器。
  4. 写入数据: 清空文件内容 (ftruncate),将指针移回文件开头 (rewind),然后将更新后的计数写回(例如 11||5)。
  5. 解锁/关闭文件: 释放锁 (flock) 并关闭文件句柄。
  6. 计算并输出: 计算百分比,并生成包含结果的 HTML 表格,包括简单的视觉条。此 HTML 作为 fetch 请求的响应发送回去。

这个简单的文本文件存储投票计数,使用 || 分隔。

示例内容:

25||12

这表示 25 张“是”票和 12 张“否”票。

重要提示:确保 Web 服务器进程(例如 www-data、apache)对 poll_result.txt 文件及其所在目录具有写权限。不要授予过于宽松的权限(例如 777)。