You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为按次区域分组的国家数据列表添加多条件排序功能?

实现国家数据的单/多条件排序功能

问题描述

我有一个按次区域分组的国家数据列表,包含名称、首都、人口、面积等字段。想要添加以下排序功能:

  • 点击顶部的名称、首都等表头时,国家数据按对应字段升序或降序排列,再次点击反转排序规则;
  • 支持多条件排序,比如可以同时按面积和首都排序。

核心实现思路

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 03:35:29