项目概述
本项目实现一个注册页面的用户名实时验证功能,使用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用户名的情况下
- 用户名存在的情况下,注册不了
- 用户名不存在的情况下,注册成功