AJAX表单验证项目实战:实时用户名检查
项目概述
本项目实现一个注册页面的用户名实时验证功能,使用AJAX技术在用户输入时异步检查用户名是否已被占用,提供即时反馈。
技术栈
- 前端:HTML5, CSS3, JavaScript (原生AJAX)
- 后端:Python Flask
- 数据存储:简单文本文件存储已注册用户名
功能需求
- 用户在注册表单输入用户名
- 输入框失去焦点或用户停止输入后自动触发检查
- 实时显示"用户名可用"或"用户名已被占用"提示
- 显示加载状态指示正在检查
项目结构
ajax-form-validation/
├── static/
│ ├── css/
│ │ └── style.css # 样式文件
│ └── js/
│ └── script.js # AJAX逻辑
├── templates/
│ └── index.html # 注册表单页面
├── data/
│ └── users.txt # 存储已注册用户名
└── app.py # Flask后端
前端设计
HTML结构
- 注册表单包含用户名、密码等字段
- 用户名输入框添加事件监听
- 提示信息区域用于显示验证结果
CSS样式
- 输入框状态样式(正常、验证中、有效、无效)
- 提示信息样式(成功、错误、加载中)
- 响应式设计适配不同设备
JavaScript逻辑
- 使用addEventListener监听输入事件
- 实现AJAX请求函数
- 处理服务器响应并更新UI
- 添加防抖处理避免频繁请求
后端设计
API接口
- GET /check-username?username=xxx 检查用户名是否存在
- 返回JSON格式响应:{ “available”: true/false }
数据处理
- 从users.txt读取已注册用户名
- 检查请求的用户名是否存在
- 返回检查结果
以下是代码:
- index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>用户注册 - AJAX表单验证示例</title><link rel="stylesheet" href="../static/css/style.css" />
</head><body><div class="container"><h1>用户注册</h1><form id="registerForm"><div class="form-group"><label for="username">用户名:</label><div class="input-group"><input type="text" id="username" name="username" required minlength="3" maxlength="20" placeholder="请输入用户名"><div id="usernameFeedback" class="feedback"></div></div><small>用户名长度为3-20个字符</small></div><div class="form-group"><label for="password">密码:</label><input type="password" id="password" name="password" required minlength="6" placeholder="请输入密码"></div><div class="form-group"><label for="email">邮箱:</label><input type="email" id="email" name="email" required placeholder="请输入邮箱"></div><button type="submit" id="submitBtn">注册</button></form></div><script src="../static/js/script.js"></script>
</body>
</html>
- style.css
* {box-sizing: border-box;margin: 0;padding: 0;font-family: 'Arial', sans-serif;
}body {background-color: #f5f5f5;display: flex;justify-content: center;align-items: center;min-height: 100vh;padding: 20px;
}.container {background-color: white;padding: 30px;border-radius: 8px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);width: 100%;max-width: 400px;
}h1 {text-align: center;margin-bottom: 25px;color: #333;
}.form-group {margin-bottom: 20px;
}label {display: block;margin-bottom: 8px;color: #555;font-weight: 500;
}input {width: 100%;padding: 10px;border: 1px solid #ddd;border-radius: 4px;font-size: 16px;transition: border-color 0.3s;
}input:focus {outline: none;border-color: #4CAF50;box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
}.input-group {position: relative;
}.feedback {position: absolute;right: 10px;top: 50%;transform: translateY(-50%);font-size: 14px;display: none;
}.feedback.loading {color: #999;display: inline;
}.feedback.available {color: #4CAF50;display: inline;
}.feedback.unavailable {color: #F44336;display: inline;
}small {display: block;margin-top: 5px;color: #777;font-size: 12px;
}button {width: 100%;padding: 12px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;font-size: 16px;cursor: pointer;transition: background-color 0.3s;
}button:hover {background-color: #45a049;
}button:disabled {background-color: #cccccc;cursor: not-allowed;
}
- script.js
document.addEventListener('DOMContentLoaded', function() {const usernameInput = document.getElementById('username');const feedbackElement = document.getElementById('usernameFeedback');const submitBtn = document.getElementById('submitBtn');let isUsernameAvailable = false;// 防抖函数function debounce(func, delay = 500) {let timeoutId;return function(...args) {clearTimeout(timeoutId);timeoutId = setTimeout(() => func.apply(this, args), delay);};}// 检查用户名function checkUsername() {const username = usernameInput.value.trim();// 如果用户名为空,不检查if (!username) {feedbackElement.style.display = 'none';isUsernameAvailable = false;updateSubmitButton();return;}// 显示加载状态feedbackElement.textContent = '检查中...';feedbackElement.className = 'feedback loading';// 创建AJAX请求const xhr = new XMLHttpRequest();xhr.open('GET', `/check-username?username=${encodeURIComponent(username)}`, true);xhr.onload = function() {if (xhr.status === 200) {try {const response = JSON.parse(xhr.responseText);if (response.available) {feedbackElement.textContent = '用户名可用';feedbackElement.className = 'feedback available';isUsernameAvailable = true;} else {feedbackElement.textContent = '用户名已被占用';feedbackElement.className = 'feedback unavailable';isUsernameAvailable = false;}} catch (e) {feedbackElement.textContent = '检查失败,请重试';feedbackElement.className = 'feedback unavailable';isUsernameAvailable = false;}} else {feedbackElement.textContent = '服务器错误,请稍后再试';feedbackElement.className = 'feedback unavailable';isUsernameAvailable = false;}updateSubmitButton();};xhr.onerror = function() {feedbackElement.textContent = '网络错误,请检查连接';feedbackElement.className = 'feedback unavailable';isUsernameAvailable = false;updateSubmitButton();};xhr.send();}// 更新提交按钮状态function updateSubmitButton() {submitBtn.disabled = !isUsernameAvailable || !usernameInput.value.trim();}// 监听输入事件,使用防抖usernameInput.addEventListener('input', debounce(checkUsername));// 监听失焦事件usernameInput.addEventListener('blur', checkUsername);// 表单提交处理document.getElementById('registerForm').addEventListener('submit', function(e) {e.preventDefault();if (isUsernameAvailable) {// 这里可以添加表单提交逻辑alert('注册成功!');}});
});
运行结果:
data.txt文件中有admin用户名的情况下
- 用户名存在的情况下,注册不了
- 用户名不存在的情况下,注册成功