AJAX表单验证项目实战:实时用户名检查功能
我将创建一个完整的用户注册表单,实现实时用户名可用性检查功能。这个功能会在用户输入用户名时,通过AJAX异步请求检查该用户名是否已被占用。
实现思路
创建一个用户注册表单,包含用户名、邮箱和密码字段
在用户名输入框添加事件监听,当用户输入时触发检查
通过AJAX向服务器发送异步请求,检查用户名可用性
实时显示检查结果(用户名是否可用)
添加适当的视觉反馈和用户提示
下面是完整实现:
<!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="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
width: 100%;
max-width: 500px;
background-color: white;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.header {
background: linear-gradient(to right, #4776E6, #8E54E9);
color: white;
padding: 25px 30px;
text-align: center;
}
.header h1 {
font-size: 28px;
margin-bottom: 8px;
}
.header p {
opacity: 0.9;
font-size: 15px;
}
.form-container {
padding: 30px;
}
.form-group {
margin-bottom: 25px;
position: relative;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
font-size: 15px;
}
input {
width: 100%;
padding: 14px 16px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 16px;
transition: all 0.3s ease;
}
input:focus {
border-color: #4776E6;
outline: none;
box-shadow: 0 0 0 3px rgba(71, 118, 230, 0.1);
}
.input-icon {
position: absolute;
right: 16px;
top: 40px;
font-size: 18px;
}
.validation-message {
margin-top: 6px;
font-size: 14px;
display: flex;
align-items: center;
height: 20px;
}
.success {
color: #2ecc71;
}
.error {
color: #e74c3c;
}
.loading {
color: #3498db;
}
.info {
color: #7f8c8d;
}
.submit-btn {
background: linear-gradient(to right, #4776E6, #8E54E9);
color: white;
border: none;
padding: 16px;
width: 100%;
border-radius: 8px;
font-size: 17px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 10px;
}
.submit-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(142, 84, 233, 0.4);
}
.submit-btn:disabled {
background: #cccccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.demo-info {
background-color: #f8f9fa;
border-radius: 8px;
padding: 15px;
margin-top: 25px;
border-left: 4px solid #4776E6;
}
.demo-info h3 {
color: #333;
margin-bottom: 10px;
font-size: 16px;
}
.demo-info ul {
padding-left: 20px;
color: #555;
font-size: 14px;
line-height: 1.5;
}
.demo-info li {
margin-bottom: 5px;
}
.status-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
}
.status-online {
background-color: #2ecc71;
}
.status-offline {
background-color: #e74c3c;
}
.server-status {
display: flex;
align-items: center;
font-size: 14px;
margin-bottom: 10px;
color: #555;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.loading-animation {
animation: pulse 1.5s infinite;
}
.api-response {
background-color: #f1f8ff;
border-radius: 6px;
padding: 12px;
margin-top: 20px;
font-family: monospace;
font-size: 13px;
display: none;
border-left: 3px solid #4776E6;
}
.api-response h4 {
margin-bottom: 8px;
color: #333;
}
@media (max-width: 600px) {
.container {
max-width: 100%;
}
.form-container {
padding: 25px 20px;
}
.header {
padding: 20px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1><i class="fas fa-user-check"></i> 实时用户名检查</h1>
<p>在您输入时验证用户名可用性</p>
<div class="server-status">
<span class="status-indicator status-online" id="serverStatus"></span>
<span id="serverStatusText">服务器连接正常</span>
</div>
</div>
<div class="form-container">
<form id="registrationForm">
<div class="form-group">
<label for="username"><i class="fas fa-user"></i> 用户名</label>
<input
type="text"
id="username"
name="username"
placeholder="输入用户名 (至少3个字符)"
autocomplete="off"
>
<div class="input-icon" id="usernameIcon"></div>
<div class="validation-message" id="usernameMessage"></div>
</div>
<div class="form-group">
<label for="email"><i class="fas fa-envelope"></i> 电子邮箱</label>
<input
type="email"
id="email"
name="email"
placeholder="输入您的电子邮箱"
>
</div>
<div class="form-group">
<label for="password"><i class="fas fa-lock"></i> 密码</label>
<input
type="password"
id="password"
name="password"
placeholder="至少8个字符,包含字母和数字"
>
</div>
<button type="submit" class="submit-btn" id="submitBtn" disabled>
<i class="fas fa-user-plus"></i> 创建账户
</button>
</form>
<div class="api-response" id="apiResponse">
<h4>API响应信息:</h4>
<pre id="responseContent">等待用户名检查...</pre>
</div>
<div class="demo-info">
<h3><i class="fas fa-info-circle"></i> 演示说明</h3>
<ul>
<li>当您在用户名输入框中键入时,系统会自动检查用户名是否可用</li>
<li>已占用的用户名:<code>admin</code>, <code>user123</code>, <code>testuser</code>, <code>john_doe</code></li>
<li>用户名必须至少3个字符,只能包含字母、数字和下划线</li>
<li>所有验证都在前端模拟完成,不会向真实服务器发送请求</li>
<li>此演示模拟了AJAX请求延迟和服务器响应</li>
</ul>
</div>
</div>
</div>
<script>
// 模拟已存在的用户名
const existingUsernames = ['admin', 'user123', 'testuser', 'john_doe', 'alice', 'bob', 'charlie', 'demo'];
// DOM元素
const usernameInput = document.getElementById('username');
const usernameMessage = document.getElementById('usernameMessage');
const usernameIcon = document.getElementById('usernameIcon');
const submitBtn = document.getElementById('submitBtn');
const serverStatus = document.getElementById('serverStatus');
const serverStatusText = document.getElementById('serverStatusText');
const apiResponse = document.getElementById('apiResponse');
const responseContent = document.getElementById('responseContent');
// 服务器状态
let serverOnline = true;
// 初始化
document.addEventListener('DOMContentLoaded', function() {
// 添加输入事件监听
usernameInput.addEventListener('input', handleUsernameInput);
// 添加表单提交事件
document.getElementById('registrationForm').addEventListener('submit', handleFormSubmit);
// 模拟服务器状态变化
simulateServerStatus();
});
// 处理用户名输入
function handleUsernameInput() {
const username = usernameInput.value.trim();
// 清除之前的消息和图标
clearValidation();
// 如果用户名太短,不进行检查
if (username.length < 3) {
showInfo('用户名至少需要3个字符');
updateSubmitButton();
return;
}
// 检查用户名格式
if (!isValidUsername(username)) {
showError('用户名只能包含字母、数字和下划线');
updateSubmitButton();
return;
}
// 显示加载状态
showLoading();
// 模拟网络延迟后检查用户名
setTimeout(() => {
checkUsernameAvailability(username);
}, 600);
}
// 检查用户名是否可用
function checkUsernameAvailability(username) {
// 模拟服务器连接问题
if (!serverOnline) {
showError('无法连接到服务器,请稍后再试');
updateSubmitButton();
logApiResponse({
status: 'error',
message: '服务器连接失败',
timestamp: new Date().toISOString()
});
return;
}
// 模拟API响应延迟
const isAvailable = !existingUsernames.includes(username.toLowerCase());
// 模拟随机服务器错误(10%概率)
const randomError = Math.random() < 0.1;
if (randomError) {
showError('服务器错误,请稍后再试');
logApiResponse({
status: 'error',
message: '服务器内部错误',
timestamp: new Date().toISOString()
});
} else {
if (isAvailable) {
showSuccess(`用户名 "${username}" 可用`);
logApiResponse({
status: 'success',
available: true,
username: username,
message: '用户名可用',
timestamp: new Date().toISOString()
});
} else {
showError(`用户名 "${username}" 已被占用`);
logApiResponse({
status: 'success',
available: false,
username: username,
message: '用户名已被占用',
timestamp: new Date().toISOString()
});
}
}
updateSubmitButton();
}
// 验证用户名格式
function isValidUsername(username) {
const regex = /^[a-zA-Z0-9_]+$/;
return regex.test(username);
}
// 显示加载状态
function showLoading() {
usernameMessage.textContent = '检查用户名可用性...';
usernameMessage.className = 'validation-message loading';
usernameIcon.innerHTML = '<i class="fas fa-spinner loading-animation"></i>';
usernameIcon.style.color = '#3498db';
}
// 显示成功消息
function showSuccess(message) {
usernameMessage.textContent = message;
usernameMessage.className = 'validation-message success';
usernameIcon.innerHTML = '<i class="fas fa-check-circle"></i>';
usernameIcon.style.color = '#2ecc71';
}
// 显示错误消息
function showError(message) {
usernameMessage.textContent = message;
usernameMessage.className = 'validation-message error';
usernameIcon.innerHTML = '<i class="fas fa-times-circle"></i>';
usernameIcon.style.color = '#e74c3c';
}
// 显示信息消息
function showInfo(message) {
usernameMessage.textContent = message;
usernameMessage.className = 'validation-message info';
usernameIcon.innerHTML = '';
}
// 清除验证消息
function clearValidation() {
usernameMessage.textContent = '';
usernameMessage.className = 'validation-message';
usernameIcon.innerHTML = '';
}
// 更新提交按钮状态
function updateSubmitButton() {
const username = usernameInput.value.trim();
const usernameIsValid = username.length >= 3 &&
isValidUsername(username) &&
!existingUsernames.includes(username.toLowerCase());
submitBtn.disabled = !usernameIsValid;
}
// 处理表单提交
function handleFormSubmit(e) {
e.preventDefault();
// 获取表单数据
const username = usernameInput.value.trim();
const email = document.getElementById('email').value.trim();
const password = document.getElementById('password').value;
// 简单验证
if (!username || !email || !password) {
alert('请填写所有必填字段');
return;
}
// 禁用提交按钮
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner loading-animation"></i> 正在创建账户...';
// 模拟注册请求
setTimeout(() => {
alert(`账户创建成功!\n\n用户名: ${username}\n邮箱: ${email}`);
// 重置表单
document.getElementById('registrationForm').reset();
clearValidation();
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-user-plus"></i> 创建账户';
apiResponse.style.display = 'none';
}, 1500);
}
// 记录API响应
function logApiResponse(data) {
apiResponse.style.display = 'block';
responseContent.textContent = JSON.stringify(data, null, 2);
}
// 模拟服务器状态变化
function simulateServerStatus() {
// 每30秒随机切换服务器状态(用于演示)
setInterval(() => {
serverOnline = Math.random() > 0.2; // 80%时间在线
if (serverOnline) {
serverStatus.className = 'status-indicator status-online';
serverStatusText.textContent = '服务器连接正常';
} else {
serverStatus.className = 'status-indicator status-offline';
serverStatusText.textContent = '服务器连接失败 - 演示模式';
}
}, 30000);
}
// 模拟真实AJAX请求
function simulateAjaxRequest(username) {
return new Promise((resolve, reject) => {
// 模拟网络延迟
setTimeout(() => {
// 模拟随机网络错误(5%概率)
const networkError = Math.random() < 0.05;
if (networkError) {
reject(new Error('网络连接失败'));
} else {
const isAvailable = !existingUsernames.includes(username.toLowerCase());
resolve({
success: true,
available: isAvailable,
username: username,
timestamp: new Date().toISOString()
});
}
}, 500 + Math.random() * 1000); // 500-1500ms延迟
});
}
</script>
</body>
</html>
功能说明
实时用户名验证:
- 用户在用户名输入框输入时,自动触发验证
- 显示检查状态(加载中、可用、不可用)
- 提供直观的视觉反馈(图标和颜色)
验证逻辑:
- 检查用户名长度(至少3个字符)
- 验证用户名格式(字母、数字、下划线)
- 检查用户名是否已被占用
模拟服务器交互:
- 模拟AJAX请求延迟
- 模拟服务器响应
- 模拟网络错误和服务器错误
用户体验优化:
- 防抖处理(避免频繁请求)
- 清晰的视觉反馈
- 响应式设计,适配移动设备
- 服务器状态指示器
使用说明
在用户名输入框中输入用户名
系统会自动检查用户名是否可用
可用的用户名会显示绿色提示和勾选图标
已占用的用户名会显示红色提示和错误图标
只有用户名可用时,注册按钮才会启用
点击"创建账户"按钮模拟注册过程
这个实现完全在前端运行,模拟了与后端服务器的交互过程,适合学习和演示AJAX表单验证的原理。