上一篇
上周五下午,我正在开发一个电商后台管理系统,遇到了一个典型问题:商品分类和子分类的联动选择,产品经理要求当用户选择"电子产品"大类时,下面的子类选项要实时变成"手机"、"电脑"、"平板";而选择"服装"时,子类要变成"男装"、"女装"、"童装"。
最开始的方案是页面加载时就加载所有可能的选项,然后用JavaScript隐藏/显示,但分类有几十个,每个分类又有十几个子类,页面加载变得异常缓慢,这时候,我想到了用AJAX动态获取和更新radio选项,不仅解决了性能问题,还让交互更加流畅。
假设我们有一个简单的表单,需要根据用户选择的国家动态加载对应的城市选项:
<form id="address-form"> <div class="form-group"> <label>选择国家:</label> <select id="country" name="country"> <option value="china">中国</option> <option value="usa">美国</option> <option value="japan">日本</option> </select> </div> <div class="form-group"> <label>选择城市:</label> <div id="city-options"> <!-- 这里将通过AJAX动态加载城市选项 --> </div> </div> </form>
对应的JavaScript代码:
document.getElementById('country').addEventListener('change', function() { const country = this.value; // 显示加载状态 document.getElementById('city-options').innerHTML = '<p>加载中...</p>'; // 发起AJAX请求 fetch('/api/cities?country=' + country) .then(response => response.json()) .then(data => { let html = ''; data.cities.forEach(city => { html += ` <label> <input type="radio" name="city" value="${city.value}"> ${city.name} </label> `; }); document.getElementById('city-options').innerHTML = html; }) .catch(error => { document.getElementById('city-options').innerHTML = '<p>加载失败,请重试</p>'; console.error('AJAX请求失败:', error); }); });
上面的基础实现有个小问题:如果用户已经选择了某个城市,然后切换国家,再切换回来时,之前的选择会丢失,我们可以改进一下:
let selectedCity = null; // 用于保存当前选中的城市 document.getElementById('country').addEventListener('change', function() { const country = this.value; const cityOptions = document.getElementById('city-options'); // 保存当前选中的城市(如果有) const selectedRadio = cityOptions.querySelector('input[name="city"]:checked'); if (selectedRadio) { selectedCity = selectedRadio.value; } cityOptions.innerHTML = '<p>加载中...</p>'; fetch('/api/cities?country=' + country) .then(response => response.json()) .then(data => { let html = ''; data.cities.forEach(city => { const isChecked = city.value === selectedCity ? 'checked' : ''; html += ` <label> <input type="radio" name="city" value="${city.value}" ${isChecked}> ${city.name} </label> `; }); cityOptions.innerHTML = html; }) .catch(error => { cityOptions.innerHTML = '<p>加载失败,请重试</p>'; console.error('AJAX请求失败:', error); }); });
当用户快速切换国家时,可能会触发多次AJAX请求,我们可以使用防抖(debounce)技术来优化:
function debounce(func, delay) { let timeoutId; return function() { const context = this; const args = arguments; clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(context, args), delay); }; } // 缓存已加载的数据 const cityCache = {}; document.getElementById('country').addEventListener('change', debounce(function() { const country = this.value; const cityOptions = document.getElementById('city-options'); // 如果缓存中有数据,直接使用 if (cityCache[country]) { renderCities(cityCache[country]); return; } cityOptions.innerHTML = '<p>加载中...</p>'; fetch('/api/cities?country=' + country) .then(response => response.json()) .then(data => { cityCache[country] = data; // 存入缓存 renderCities(data); }) .catch(error => { cityOptions.innerHTML = '<p>加载失败,请重试</p>'; console.error('AJAX请求失败:', error); }); }, 300)); // 300毫秒内连续触发只会执行一次 function renderCities(data) { let html = ''; data.cities.forEach(city => { html += ` <label> <input type="radio" name="city" value="${city.value}"> ${city.name} </label> `; }); document.getElementById('city-options').innerHTML = html; }
好的用户体验应该让用户清楚地知道系统正在做什么,我们可以添加更友好的状态提示:
document.getElementById('country').addEventListener('change', function() { const country = this.value; const cityOptions = document.getElementById('city-options'); // 清除现有内容,添加加载动画 cityOptions.innerHTML = ` <div class="loading-state"> <div class="spinner"></div> <span>正在加载城市数据...</span> </div> `; fetch('/api/cities?country=' + country) .then(response => { if (!response.ok) { throw new Error('网络响应不正常'); } return response.json(); }) .then(data => { if (data.cities.length === 0) { cityOptions.innerHTML = '<p class="no-data">该国家下暂无城市数据</p>'; return; } let html = '<div class="city-radio-group">'; data.cities.forEach(city => { html += ` <label class="radio-label"> <input type="radio" name="city" value="${city.value}"> <span class="radio-custom"></span> ${city.name} </label> `; }); html += '</div>'; cityOptions.innerHTML = html; }) .catch(error => { cityOptions.innerHTML = ` <div class="error-state"> <span class="error-icon">!</span> <p>加载城市数据失败</p> <button class="retry-btn">重试</button> </div> `; // 添加重试功能 cityOptions.querySelector('.retry-btn').addEventListener('click', () => { this.dispatchEvent(new Event('change')); }); }); });
对应的CSS可以这样写:
.loading-state { display: flex; align-items: center; color: #666; } .spinner { width: 20px; height: 20px; border: 3px solid rgba(0,0,0,0.1); border-radius: 50%; border-top-color: #3498db; margin-right: 10px; animation: spin 1s ease-in-out infinite; } @keyframes spin { to { transform: rotate(360deg); } } .error-state { color: #e74c3c; text-align: center; } .error-icon { display: inline-block; width: 24px; height: 24px; background-color: #e74c3c; color: white; border-radius: 50%; line-height: 24px; margin-bottom: 10px; } .retry-btn { background-color: #3498db; color: white; border: none; padding: 5px 15px; border-radius: 4px; cursor: pointer; margin-top: 10px; } .radio-label { display: block; margin: 8px 0; cursor: pointer; } .radio-custom { display: inline-block; width: 16px; height: 16px; border: 2px solid #ddd; border-radius: 50%; margin-right: 8px; position: relative; vertical-align: middle; } .radio-custom:after { content: ''; display: block; width: 8px; height: 8px; background: #3498db; border-radius: 50%; position: absolute; top: 2px; left: 2px; opacity: 0; } input[type="radio"] { display: none; } input[type="radio"]:checked + .radio-custom:after { opacity: 1; }
安全性:确保后端API对请求参数进行验证,防止SQL注入等攻击
无障碍访问:为动态加载的内容添加适当的ARIA属性,方便屏幕阅读器用户使用
<div id="city-options" aria-live="polite"> <!-- 动态内容 --> </div>
.radio-label { padding: 12px 0; /* 增加点击区域 */ }
if (!navigator.onLine) { cityOptions.innerHTML = '<p>网络已断开,请检查网络连接</p>'; return; }
通过AJAX动态更新radio选项不仅能提升用户体验,还能显著减少初始页面加载时间,关键在于:
下次当你遇到需要根据用户选择动态更新选项的场景时,不妨试试这些技巧,让你的表单真正"活"起来。
本文由 寒琬凝 于2025-08-02发表在【云服务器提供商】,文中图片由(寒琬凝)上传,本平台仅提供信息存储服务;作者观点、意见不代表本站立场,如有侵权,请联系我们删除;若有图片侵权,请您准备原始证明材料和公证书后联系我方删除!
本文链接:https://vps.7tqx.com/wenda/513190.html
发表评论