jQuery+Ajax省市联动功能实现
我将创建一个使用自编写简化版jQuery库和Ajax实现的省市联动选择器。这个组件将模拟真实的数据交互,提供流畅的用户体验。
设计思路
创建一个简化版的jQuery库(
myQuery.js)包含DOM操作和Ajax功能
实现省份选择变化时,通过Ajax加载对应城市数据
使用模拟的JSON数据响应请求
设计简洁美观的界面
完整实现代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>省市联动选择器 - 自写jQuery+Ajax实现</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 20px;
color: #333;
}
.container {
max-width: 800px;
width: 100%;
background: white;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
padding: 40px;
margin-top: 20px;
}
header {
text-align: center;
margin-bottom: 40px;
}
h1 {
color: #2c3e50;
margin-bottom: 10px;
font-size: 2.4rem;
}
.description {
color: #7f8c8d;
font-size: 1.1rem;
max-width: 600px;
margin: 0 auto;
line-height: 1.6;
}
.selector-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 40px;
}
.select-wrapper {
flex: 1;
min-width: 300px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #34495e;
}
select {
width: 100%;
padding: 14px 16px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 16px;
background-color: #f8f9fa;
transition: all 0.3s;
color: #2c3e50;
}
select:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
}
select:disabled {
background-color: #f1f2f6;
color: #95a5a6;
cursor: not-allowed;
}
.selected-info {
background-color: #f8f9fa;
border-radius: 10px;
padding: 24px;
margin-top: 30px;
border-left: 5px solid #3498db;
}
.selected-info h3 {
color: #2c3e50;
margin-bottom: 15px;
}
.result-text {
font-size: 18px;
color: #2c3e50;
padding: 10px 0;
}
.result-text span {
font-weight: 600;
color: #e74c3c;
}
.loading {
display: none;
text-align: center;
color: #3498db;
margin: 10px 0;
font-style: italic;
}
.loading.active {
display: block;
}
.status {
margin-top: 20px;
padding: 12px 20px;
border-radius: 8px;
font-size: 14px;
display: none;
}
.status.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
display: block;
}
.status.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
display: block;
}
.code-section {
margin-top: 40px;
background-color: #2d3748;
border-radius: 10px;
padding: 25px;
color: #e2e8f0;
}
.code-section h3 {
color: #63b3ed;
margin-bottom: 15px;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
background-color: #1a202c;
padding: 20px;
border-radius: 8px;
overflow-x: auto;
font-size: 14px;
line-height: 1.5;
}
.highlight {
color: #fbbf24;
}
.explanation {
margin-top: 25px;
padding: 20px;
background-color: #edf2f7;
border-radius: 10px;
line-height: 1.6;
}
.explanation h3 {
color: #2c3e50;
margin-bottom: 10px;
}
@media (max-width: 768px) {
.container {
padding: 25px;
}
.selector-container {
flex-direction: column;
}
.select-wrapper {
min-width: 100%;
}
}
</style>
</head>
<body>
<header>
<h1>省市联动选择器</h1>
<p class="description">使用自编写的简化版jQuery库 + Ajax异步请求实现。选择省份后,会自动加载该省份下的城市列表。</p>
</header>
<div class="container">
<div class="selector-container">
<div class="select-wrapper">
<label for="province">选择省份:</label>
<select id="province">
<option value="">-- 请选择省份 --</option>
<option value="gd">广东省</option>
<option value="zj">浙江省</option>
<option value="js">江苏省</option>
<option value="sd">山东省</option>
<option value="bj">北京市</option>
</select>
</div>
<div class="select-wrapper">
<label for="city">选择城市:</label>
<select id="city" disabled>
<option value="">-- 请先选择省份 --</option>
</select>
<div class="loading" id="city-loading">正在加载城市数据...</div>
</div>
</div>
<div class="selected-info">
<h3>选择结果</h3>
<div class="result-text">省份:<span id="selected-province">未选择</span></div>
<div class="result-text">城市:<span id="selected-city">未选择</span></div>
</div>
<div class="status" id="status"></div>
<div class="explanation">
<h3>实现原理</h3>
<p>1. 当用户选择省份时,触发 <code>change</code> 事件</p>
<p>2. 通过自写的 <code>ajax</code> 方法向服务器发送请求,获取该省份的城市数据</p>
<p>3. 收到响应后,解析JSON数据并动态生成城市选项</p>
<p>4. 启用城市选择框,用户可以选择城市</p>
<p>5. 使用自写的jQuery库简化DOM操作和事件处理</p>
</div>
<div class="code-section">
<h3>自写jQuery库核心代码 (myQuery.js)</h3>
<pre><code>// 简化版jQuery库 - myQuery
(function() {
window.myQuery = function(selector) {
return new MyQuery(selector);
};
function MyQuery(selector) {
this.elements = document.querySelectorAll(selector);
}
MyQuery.prototype = {
// 绑定事件
on: function(event, handler) {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].addEventListener(event, handler);
}
return this;
},
// 获取或设置HTML内容
html: function(content) {
if (content === undefined) {
return this.elements[0] ? this.elements[0].innerHTML : null;
}
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].innerHTML = content;
}
return this;
},
// 获取或设置value
val: function(value) {
if (value === undefined) {
return this.elements[0] ? this.elements[0].value : null;
}
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].value = value;
}
return this;
},
// 显示元素
show: function() {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].style.display = 'block';
}
return this;
},
// 隐藏元素
hide: function() {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].style.display = 'none';
}
return this;
},
// 添加CSS类
addClass: function(className) {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].classList.add(className);
}
return this;
},
// 移除CSS类
removeClass: function(className) {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].classList.remove(className);
}
return this;
},
// 设置CSS属性
css: function(property, value) {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].style[property] = value;
}
return this;
},
// 获取或设置属性
attr: function(name, value) {
if (value === undefined) {
return this.elements[0] ? this.elements[0].getAttribute(name) : null;
}
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].setAttribute(name, value);
}
return this;
},
// 启用/禁用元素
prop: function(name, value) {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i][name] = value;
}
return this;
}
};
// Ajax方法
myQuery.ajax = function(options) {
const xhr = new XMLHttpRequest();
const method = options.method || 'GET';
const url = options.url;
const data = options.data || null;
const success = options.success || function() {};
const error = options.error || function() {};
xhr.open(method, url, true);
if (options.headers) {
for (let header in options.headers) {
xhr.setRequestHeader(header, options.headers[header]);
}
}
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
let response = xhr.responseText;
if (options.dataType === 'json') {
try {
response = JSON.parse(response);
} catch(e) {
console.error('JSON解析错误:', e);
}
}
success(response, xhr.status, xhr);
} else {
error(xhr, xhr.status, xhr.statusText);
}
}
};
xhr.send(data);
};
})();</code></pre>
</div>
</div>
<script>
// 自写的简化版jQuery库
(function() {
window.myQuery = function(selector) {
return new MyQuery(selector);
};
function MyQuery(selector) {
this.elements = document.querySelectorAll(selector);
}
MyQuery.prototype = {
// 绑定事件
on: function(event, handler) {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].addEventListener(event, handler);
}
return this;
},
// 获取或设置HTML内容
html: function(content) {
if (content === undefined) {
return this.elements[0] ? this.elements[0].innerHTML : null;
}
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].innerHTML = content;
}
return this;
},
// 获取或设置value
val: function(value) {
if (value === undefined) {
return this.elements[0] ? this.elements[0].value : null;
}
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].value = value;
}
return this;
},
// 显示元素
show: function() {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].style.display = 'block';
}
return this;
},
// 隐藏元素
hide: function() {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].style.display = 'none';
}
return this;
},
// 添加CSS类
addClass: function(className) {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].classList.add(className);
}
return this;
},
// 移除CSS类
removeClass: function(className) {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].classList.remove(className);
}
return this;
},
// 设置CSS属性
css: function(property, value) {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].style[property] = value;
}
return this;
},
// 获取或设置属性
attr: function(name, value) {
if (value === undefined) {
return this.elements[0] ? this.elements[0].getAttribute(name) : null;
}
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].setAttribute(name, value);
}
return this;
},
// 启用/禁用元素
prop: function(name, value) {
for (let i = 0; i < this.elements.length; i++) {
this.elements[i][name] = value;
}
return this;
}
};
// Ajax方法
myQuery.ajax = function(options) {
const xhr = new XMLHttpRequest();
const method = options.method || 'GET';
const url = options.url;
const data = options.data || null;
const success = options.success || function() {};
const error = options.error || function() {};
xhr.open(method, url, true);
if (options.headers) {
for (let header in options.headers) {
xhr.setRequestHeader(header, options.headers[header]);
}
}
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
let response = xhr.responseText;
if (options.dataType === 'json') {
try {
response = JSON.parse(response);
} catch(e) {
console.error('JSON解析错误:', e);
}
}
success(response, xhr.status, xhr);
} else {
error(xhr, xhr.status, xhr.statusText);
}
}
};
xhr.send(data);
};
})();
</script>
<script>
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 使用自写的myQuery库
const $ = myQuery;
// 省份数据映射
const provinceData = {
'gd': '广东省',
'zj': '浙江省',
'js': '江苏省',
'sd': '山东省',
'bj': '北京市'
};
// 城市数据(模拟服务器数据)
const cityData = {
'gd': [
{ id: 'gz', name: '广州市' },
{ id: 'sz', name: '深圳市' },
{ id: 'dg', name: '东莞市' },
{ id: 'fs', name: '佛山市' },
{ id: 'zh', name: '珠海市' }
],
'zj': [
{ id: 'hz', name: '杭州市' },
{ id: 'nb', name: '宁波市' },
{ id: 'wz', name: '温州市' },
{ id: 'jx', name: '嘉兴市' },
{ id: 'sx', name: '绍兴市' }
],
'js': [
{ id: 'nj', name: '南京市' },
{ id: 'sz', name: '苏州市' },
{ id: 'wx', name: '无锡市' },
{ id: 'cz', name: '常州市' },
{ id: 'xz', name: '徐州市' }
],
'sd': [
{ id: 'jn', name: '济南市' },
{ id: 'qd', name: '青岛市' },
{ id: 'zb', name: '淄博市' },
{ id: 'yt', name: '烟台市' },
{ id: 'wf', name: '潍坊市' }
],
'bj': [
{ id: 'dcq', name: '东城区' },
{ id: 'xcq', name: '西城区' },
{ id: 'cyq', name: '朝阳区' },
{ id: 'hdq', name: '海淀区' },
{ id: 'ftq', name: '丰台区' }
]
};
// 模拟服务器API - 根据省份ID返回城市数据
function mockServerAPI(provinceId) {
return new Promise((resolve) => {
// 模拟网络延迟
setTimeout(() => {
if (cityData[provinceId]) {
resolve({
success: true,
data: cityData[provinceId]
});
} else {
resolve({
success: false,
message: '未找到该省份的城市数据'
});
}
}, 500); // 500ms延迟模拟网络请求
});
}
// 初始化省份选择事件
$('#province').on('change', function(e) {
const provinceId = e.target.value;
const citySelect = $('#city');
const loading = $('#city-loading');
const status = $('#status');
// 更新显示的省份
const provinceName = provinceData[provinceId] || '未选择';
$('#selected-province').html(provinceName);
$('#selected-city').html('未选择');
if (!provinceId) {
// 如果没有选择省份,禁用城市选择框
citySelect.prop('disabled', true).val('');
citySelect.html('<option value="">-- 请先选择省份 --</option>');
status.removeClass('success').removeClass('error');
return;
}
// 显示加载状态
loading.addClass('active');
citySelect.prop('disabled', true).html('<option value="">加载中...</option>');
status.removeClass('success').removeClass('error');
// 使用自写的ajax方法获取城市数据
// 这里我们使用一个模拟的API端点
const apiUrl = `/api/cities?province=${provinceId}&_t=${Date.now()}`;
// 模拟Ajax请求 - 实际项目中这里应该是真实的服务器API
myQuery.ajax({
method: 'GET',
url: apiUrl,
dataType: 'json',
success: function(response) {
// 模拟服务器响应
// 在实际应用中,这里应该解析真实的服务器响应
// 为了演示,我们使用本地数据模拟
mockServerAPI(provinceId).then(mockResponse => {
// 隐藏加载状态
loading.removeClass('active');
if (mockResponse.success) {
// 清空城市选择框
let cityOptions = '<option value="">-- 请选择城市 --</option>';
// 添加城市选项
mockResponse.data.forEach(city => {
cityOptions += `<option value="${city.id}">${city.name}</option>`;
});
// 更新城市选择框
citySelect.html(cityOptions).prop('disabled', false);
// 显示成功状态
status.html(`成功加载 ${provinceData[provinceId]} 的城市数据,共 ${mockResponse.data.length} 个城市`).addClass('success');
} else {
citySelect.html('<option value="">数据加载失败</option>');
status.html(mockResponse.message).addClass('error');
}
});
},
error: function(xhr, status, error) {
// 隐藏加载状态
loading.removeClass('active');
// 显示错误状态
citySelect.html('<option value="">数据加载失败</option>');
status.html('网络请求失败,请检查网络连接').addClass('error');
console.error('Ajax请求失败:', error);
}
});
});
// 城市选择事件
$('#city').on('change', function(e) {
const cityId = e.target.value;
const cityName = e.target.options[e.target.selectedIndex].text;
if (cityId) {
$('#selected-city').html(cityName);
} else {
$('#selected-city').html('未选择');
}
});
// 初始状态提示
console.log('省市联动选择器已初始化');
console.log('使用的自写jQuery库方法:');
console.log('- myQuery(selector) - 选择元素');
console.log('- .on(event, handler) - 绑定事件');
console.log('- .html(content) - 获取/设置HTML');
console.log('- .val(value) - 获取/设置值');
console.log('- .prop(name, value) - 获取/设置属性');
console.log('- myQuery.ajax(options) - 发送Ajax请求');
});
</script>
</body>
</html>
功能说明
自写jQuery库 (myQuery):
- 支持基本的选择器功能
- 实现了常用方法:
on, html, val, show, hide, addClass, removeClass, css, attr, prop
- 实现了Ajax方法:
myQuery.ajax
省市联动功能:
- 选择省份后,自动通过Ajax加载对应城市数据
- 模拟网络请求延迟,展示加载状态
- 城市选择框在数据加载完成前被禁用
- 实时显示用户选择的省份和城市
用户界面:
- 现代化卡片式设计
- 响应式布局,适配各种屏幕尺寸
- 清晰的视觉反馈(加载状态、成功/错误提示)
- 代码展示区域,展示自写jQuery库的实现
如何使用
选择任意一个省份
观察加载状态和Ajax请求过程
选择加载后的城市
查看底部显示的选择结果
这个实现展示了如何在不依赖外部库的情况下,使用原生JavaScript实现类似jQuery的功能,并结合Ajax完成动态数据加载。