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

如何在JavaScript中实现多列切换升降序的国家数据排序?

问题修复:实现单列切换排序与多列组合排序

核心问题梳理

原代码存在以下关键问题导致排序功能失效:

  • switch语句未添加break,点击单个表头会触发所有后续case的排序逻辑,最终仅保留最后一次排序结果
  • 每次点击都重新请求API数据,未缓存原始数据,无法维护排序状态
  • 用户自定义排序后,又执行了region/subregion的强制排序,覆盖了用户选择的排序规则
  • 缺乏多列排序的状态管理机制,无法记录多列排序的优先级与方向

修复后的完整代码

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>Document</title>
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <main class="container">
        <header>
            <div id="name">
                <h1>Name</h1>
            </div>
            <div id="capital">
                <h1>Capital</h1>
            </div>
            <div id="population">
                <h1>Population</h1>
            </div>
            <div id="area">
                <h1>Area</h1>
            </div>

        </header>

      
      <div id="container"></div>
      <div id="subRegionWrapper"> </div>
      <div id="listWrapper"></div>
      <script src="script.js"></script>
    </main>
</body>

</html>

CSS(无需修改)

*{
  transition: 500ms;

}
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;
}

#name {
  padding: 0 20px;
  background-color: rgb(219, 219, 219);
}

#capital {
  padding: 0 20px;
  background-color: rgb(219, 219, 219);
}

#population {
  padding: 0 20px;
  background-color: rgb(219, 219, 219);
}

#area {
  padding: 0 20px;
  background-color: rgb(219, 219, 219);
}

header {
  margin: 0 10%;
  display: flex;
  justify-content: space-between;
  padding: 22px 0;
  color: rgb(5, 5, 5);
}

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;
}

#name:hover {
  background-color: rgb(114, 114, 114);
  cursor: pointer;
}
#capital:hover {
  background-color: rgb(114, 114, 114);
  cursor: pointer;
}
#population:hover {
  background-color: rgb(114, 114, 114);
  cursor: pointer;
}
#area:hover {
  background-color: rgb(114, 114, 114);
  cursor: pointer;
}

JavaScript(核心修改)

// 缓存原始数据
let originalData = [];
// 排序状态数组:记录排序字段、方向(1升序,-1降序)、优先级
let sortStates = [
  { field: 'name', direction: 1, active: false },
  { field: 'capital', direction: 1, active: false },
  { field: 'population', direction: 1, active: false },
  { field: 'area', direction: 1, active: false }
];

const container = document.getElementById('container');

// 初始化:获取数据并渲染
async function init() {
  const res = await fetch("https://restcountries.com/v3.1/all");
  originalData = await res.json();
  // 处理部分国家无capital的情况
  originalData.forEach(country => {
    country.capital = country.capital ? country.capital[0] : '无首都';
  });
  renderData();
}

// 生成组合排序的比较器
function getSortComparator() {
  // 筛选出激活的排序规则,按点击顺序(优先级)排序
  const activeSorts = sortStates.filter(s => s.active);
  
  // 如果没有激活的排序,默认按subregion分组排序
  if (activeSorts.length === 0) {
    return (a, b) => {
      if (a.region > b.region) return 1;
      if (a.region < b.region) return -1;
      if (a.subregion > b.subregion) return 1;
      return -1;
    };
  }

  // 组合排序逻辑:按优先级依次比较
  return (a, b) => {
    for (const sort of activeSorts) {
      let valA, valB;
      switch (sort.field) {
        case 'name':
          valA = a.name.common.toLowerCase();
          valB = b.name.common.toLowerCase();
          break;
        case 'capital':
          valA = a.capital.toLowerCase();
          valB = b.capital.toLowerCase();
          break;
        case 'population':
          valA = a.population;
          valB = b.population;
          break;
        case 'area':
          valA = a.area || 0; // 处理无面积数据的国家
          valB = b.area || 0;
          break;
      }

      if (valA > valB) return sort.direction;
      if (valA < valB) return -sort.direction;
      // 相等则继续比较下一个排序字段
    }
    // 所有排序字段都相等,按subregion兜底
    if (a.subregion > b.subregion) return 1;
    return -1;
  };
}

// 更新排序状态
function updateSortState(field) {
  const sortItem = sortStates.find(s => s.field === field);
  if (!sortItem) return;

  if (sortItem.active) {
    // 切换方向
    sortItem.direction *= -1;
  } else {
    // 激活该排序,添加到优先级队列末尾
    sortItem.active = true;
  }
}

// 渲染数据
function renderData() {
  // 清空容器
  while (container.lastElementChild) {
    container.removeChild(container.lastElementChild);
  }

  // 复制数据并排序
  const sortedData = [...originalData].sort(getSortComparator());

  // 按subregion分组
  const subRegions = sortedData.reduce((r, a) => {
    r[a.subregion] = r[a.subregion] || [];
    r[a.subregion].push(a);
    return r;
  }, {});

  const accordion = document.createElement('div');
  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);
    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.area || 0, item.population);
      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;
}

// 绑定表头点击事件
document.getElementById('area').addEventListener('click', () => {
  updateSortState('area');
  renderData();
});
document.getElementById('population').addEventListener('click', () => {
  updateSortState('population');
  renderData();
});
document.getElementById('capital').addEventListener('click', () => {
  updateSortState('capital');
  renderData();
});
document.getElementById('name').addEventListener('click', () => {
  updateSortState('name');
  renderData();
});

// 初始化页面
init();

关键改动说明

  1. 缓存原始数据:页面加载时仅请求一次API,后续排序基于缓存的原始数据,提升性能并保留数据完整性
  2. 排序状态管理:用sortStates数组记录每个字段的激活状态、排序方向,支持多列排序的优先级管理
  3. 组合排序比较器:动态生成排序逻辑,按激活的排序字段优先级依次比较,实现多列组合排序
  4. 修复数据异常:处理部分国家无首都、无面积的情况,避免排序报错
  5. 优化渲染逻辑:分离数据排序与渲染逻辑,每次排序后重新渲染页面,确保展示最新排序结果
  6. 数字格式化:为人口、面积添加千分位格式化,提升可读性

内容的提问来源于stack exchange,提问作者mat2254

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 12:15:35