如何为按次区域分组的国家数据列表添加多条件排序功能?
实现国家数据的单/多条件排序功能
问题描述
我有一个按次区域分组的国家数据列表,包含名称、首都、人口、面积等字段。想要添加以下排序功能:
- 点击顶部的名称、首都等表头时,国家数据按对应字段升序或降序排列,再次点击反转排序规则;
- 支持多条件排序,比如可以同时按面积和首都排序。
核心实现思路
1. 维护数据与排序状态
- 全局保存原始国家数据,避免每次排序重新请求接口;
- 用数组维护多条件排序规则,每个规则包含字段名和排序方向(
asc升序/desc降序),数组顺序代表排序优先级。
2. 表头交互逻辑
- 给每个表头元素绑定点击事件;
- 点击时判断该字段是否已在排序规则中:
- 存在则反转排序方向,并移到规则数组最前面提升优先级;
- 不存在则添加为新的排序规则(默认升序);
- 限制最多保留3个排序规则,避免规则过多导致逻辑混乱。
3. 多条件排序函数
根据排序规则数组依次比较:
- 先按第一个规则的字段比较,若结果相等,再用第二个规则的字段比较,以此类推;
- 字符串类型字段用
localeCompare处理(支持中文/英文排序),数值类型直接比较大小; - 为空的字段(如无首都的国家)自动填充默认值,避免排序异常。
4. 重构渲染逻辑
将渲染区域的代码抽成独立函数,排序完成后清空原有DOM,重新渲染分组后的国家数据。
修改后的完整代码
HTML(添加表头排序提示)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Country Sorting</title> <link rel="stylesheet" href="style.css"> </head> <body> <main class="container"> <header> <div id="name" class="sortable"> <h1>Name <span>↑</span></h1> </div> <div id="capital" class="sortable"> <h1>Capital <span>↑</span></h1> </div> <div id="population" class="sortable"> <h1>Population <span>↑</span></h1> </div> <div id="area" class="sortable"> <h1>Area <span>↑</span></h1> </div> </header> <div id="container"></div> <div id="subRegionWrapper"></div> <div id="listWrapper"></div> <script src="script.js"></script> </main> </body> </html>
CSS(新增排序表头样式)
body { margin: 0 15%; min-height: 100vh; flex-direction: column; justify-content: center; align-items: center; background-color: aliceblue; font-family: 'Open Sans', Arial; font-size: 18px; } header{ margin: 0 10%; display:flex; justify-content: space-between; padding: 22px 0; color:rgb(5, 5, 5); } /* 排序表头样式 */ .sortable { cursor: pointer; user-select: none; } .sortable span { font-size: 0.8em; margin-left: 5px; opacity: 0.5; } .sortable.active span { opacity: 1; } ul { list-style: none; list-style-type: none; outline: 2px solid #ddd; padding: 1rem 2rem; border-radius: 0.5rem; list-style-position: inside; color: blue; } ul ol { color: rgb(197, 105, 18); list-style: none; list-style-type: none; font-size: .9em; margin: 0.4rem 0; } .country{ display: flex; justify-content: space-between; } .disp-flex{ display:flex; justify-content: space-between; } .disp-flex > div{ width:23%; padding:15px 0px; } .subregion-header>div:nth-child(1){ position: relative; left:30px; } .accordion { background-color: #eee; color: #444; cursor: pointer; width: 100%; border: none; text-align: left; outline: none; font-size: 15px; margin: 15px 2px; } .accordion li { list-style-type: none; } .active, .accordion:hover { background-color: #ccc; } .panel { margin-left: 5%; display: none; background-color: white; overflow: hidden; }
JavaScript(核心排序与渲染逻辑)
// 全局变量:保存原始数据和排序规则 let allCountries = []; let sortRules = []; // 格式:[{ field: 'name', direction: 'asc' }, ...] async function init() { // 拉取数据并初始化 const res = await fetch("https://restcountries.com/v3.1/all"); allCountries = await res.json(); // 初始化排序规则:默认按区域+次区域排序 sortRules = [ { field: 'region', direction: 'asc' }, { field: 'subregion', direction: 'asc' } ]; // 绑定表头点击事件 bindHeaderEvents(); // 首次渲染 renderCountries(); } function bindHeaderEvents() { const headers = document.querySelectorAll('.sortable'); headers.forEach(header => { header.addEventListener('click', () => { const field = header.id; // 查找当前字段是否已在排序规则中 const existingRuleIndex = sortRules.findIndex(r => r.field === field); if (existingRuleIndex !== -1) { // 反转排序方向 sortRules[existingRuleIndex].direction = sortRules[existingRuleIndex].direction === 'asc' ? 'desc' : 'asc'; // 移到数组最前面,提升优先级 const rule = sortRules.splice(existingRuleIndex, 1)[0]; sortRules.unshift(rule); } else { // 添加新的排序规则(默认升序) sortRules.unshift({ field, direction: 'asc' }); // 最多保留3个排序规则 if (sortRules.length > 3) sortRules.pop(); } // 更新表头箭头显示 updateHeaderArrows(); // 重新渲染 renderCountries(); }); }); } function updateHeaderArrows() { // 重置所有表头箭头 document.querySelectorAll('.sortable span').forEach(span => { span.innerText = '↑'; span.parentElement.classList.remove('active'); }); // 为当前排序规则的表头设置箭头 sortRules.forEach((rule, index) => { const header = document.getElementById(rule.field); if (header) { header.classList.add('active'); const span = header.querySelector('span'); span.innerText = rule.direction === 'asc' ? '↑' : '↓'; // 标记优先级 span.title = `优先级 ${index + 1}`; } }); } function sortCountries() { return [...allCountries].sort((a, b) => { for (const rule of sortRules) { let valA, valB; // 处理不同字段的取值 switch (rule.field) { case 'name': valA = a.name.common.toLowerCase(); valB = b.name.common.toLowerCase(); break; case 'capital': valA = a.capital ? a.capital[0].toLowerCase() : ''; valB = b.capital ? b.capital[0].toLowerCase() : ''; break; case 'population': valA = a.population || 0; valB = b.population || 0; break; case 'area': valA = a.area || 0; valB = b.area || 0; break; case 'region': valA = a.region.toLowerCase(); valB = b.region.toLowerCase(); break; case 'subregion': valA = a.subregion ? a.subregion.toLowerCase() : ''; valB = b.subregion ? b.subregion.toLowerCase() : ''; break; default: valA = ''; valB = ''; } // 比较逻辑 let compareResult; if (typeof valA === 'number') { compareResult = valA - valB; } else { compareResult = valA.localeCompare(valB); } // 如果比较结果不为0,直接返回 if (compareResult !== 0) { return rule.direction === 'asc' ? compareResult : -compareResult; } } return 0; }); } function renderCountries() { const container = document.getElementById('container'); // 清空原有内容 container.innerHTML = ''; const accordion = document.createElement('div'); // 排序后的数据 const sortedData = sortCountries(); // 按次区域分组 const subRegions = sortedData.reduce((r, a) => { const key = a.subregion || '无次区域'; r[key] = r[key] || []; r[key].push(a); return r; }, {}); const dropdownValues = Object.entries(subRegions); dropdownValues.forEach(subRegion => { const accordionWrapper = document.createElement('div'); const panel = document.createElement('div'); panel.classList.add('panel'); accordionWrapper.classList.add('accordion'); const totalArea = subRegion[1].reduce((acc, curr) => acc + (curr.area || 0), 0); const totalPopulation = subRegion[1].reduce((acc, curr) => acc + (curr.population || 0), 0); const li = createSubregion(subRegion[0], totalPopulation, totalArea); accordionWrapper.appendChild(li); accordion.appendChild(accordionWrapper); subRegion[1].forEach(item => { const subLi = createCountry( item.name.common, item.capital ? item.capital[0] : '无首都', item.area || 0, item.population || 0 ); const subOl = document.createElement('ol'); subOl.appendChild(subLi); panel.appendChild(subOl); }); accordion.appendChild(panel); // 折叠面板事件 accordionWrapper.addEventListener('click', function() { this.classList.toggle("active"); const panel = this.nextElementSibling; panel.style.display = panel.style.display === "block" ? "none" : "block"; }); }); container.appendChild(accordion); } // 创建次区域元素 function createSubregion(name, population, area) { var li = document.createElement("li"); li.setAttribute("class", "subregion"); var header = document.createElement("div"); header.setAttribute("class", "subregion-header disp-flex"); var nameDiv = document.createElement("div"); var nameh2 = document.createElement("h2"); nameh2.innerText = name; nameDiv.appendChild(nameh2); header.append(nameDiv); var emptyDiv = document.createElement("div"); header.appendChild(emptyDiv); var populationDiv = document.createElement("div"); var populationh2 = document.createElement("h3"); populationh2.innerText = population.toLocaleString(); populationDiv.appendChild(populationh2); header.append(populationDiv); var areaDiv = document.createElement("div"); var areah2 = document.createElement("h3"); areah2.innerText = area.toLocaleString(); areaDiv.appendChild(areah2); header.append(areaDiv); li.appendChild(header); return li; } // 创建国家元素 function createCountry(name, capital, area, population) { var country = document.createElement("li"); country.setAttribute("class", "country disp-flex") var namediv = document.createElement("div"); var nameh4 = document.createElement("h4"); nameh4.innerText = name; namediv.appendChild(nameh4); country.appendChild(namediv); var capitaldiv = document.createElement("div"); var capitalh4 = document.createElement("h4"); capitalh4.innerText = capital; capitaldiv.appendChild(capitalh4); country.appendChild(capitaldiv); var popdiv = document.createElement("div"); var poph4 = document.createElement("h4"); poph4.innerText = population.toLocaleString(); popdiv.appendChild(poph4); country.appendChild(popdiv); var areadiv = document.createElement("div"); var areah4 = document.createElement("h4"); areah4.innerText = area.toLocaleString(); areadiv.appendChild(areah4); country.appendChild(areadiv); return country; } // 初始化应用 init();
使用说明
- 点击任意表头字段,即可按该字段升序排序,再次点击切换为降序;
- 多次点击不同字段,会生成多条件排序规则,最新点击的字段优先级最高;
- 表头箭头会显示当前排序方向,鼠标悬停可查看优先级;
- 数据为空的字段(如无首都的国家)会自动填充默认值,避免排序异常;
- 次区域折叠面板功能保留原有逻辑,点击次区域标题可展开/收起国家列表。
内容的提问来源于stack exchange,提问作者fantomx775
相关产品推荐
相关产品推荐

