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

如何为子地区分组国家列表添加下拉展开收起及排序筛选功能

实现国家列表的展开收起、排序与筛选功能

需求说明

已完成按子地区分组的国家列表展示,需要新增以下功能:

  • 点击子地区标题(如北美),展开/收起下属所有国家
  • 点击顶部列标题(如面积),对国家进行排序
  • 添加首字母筛选功能,仅显示名称以指定字母开头的国家

修改后的完整代码

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>国家列表</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <main class="container">
        <!-- 首字母筛选栏 -->
        <div class="filter-bar">
            <label for="letter-filter">首字母筛选:</label>
            <select id="letter-filter">
                <option value="">全部</option>
                <option value="A">A</option>
                <option value="B">B</option>
                <option value="C">C</option>
                <option value="D">D</option>
                <option value="E">E</option>
                <option value="F">F</option>
                <option value="G">G</option>
                <option value="H">H</option>
                <option value="I">I</option>
                <option value="J">J</option>
                <option value="K">K</option>
                <option value="L">L</option>
                <option value="M">M</option>
                <option value="N">N</option>
                <option value="O">O</option>
                <option value="P">P</option>
                <option value="Q">Q</option>
                <option value="R">R</option>
                <option value="S">S</option>
                <option value="T">T</option>
                <option value="U">U</option>
                <option value="V">V</option>
                <option value="W">W</option>
                <option value="X">X</option>
                <option value="Y">Y</option>
                <option value="Z">Z</option>
            </select>
        </div>

        <!-- 列标题(可点击排序) -->
        <header class="sortable-header">
            <div class="header-item" data-sort="name">
                <h1>名称</h1>
            </div>
            <div class="header-item" data-sort="capital">
                <h1>首都</h1>
            </div>
            <div class="header-item" data-sort="population">
                <h1>人口</h1>
            </div>
            <div class="header-item" data-sort="area">
                <h1>面积</h1>
            </div>
        </header>

        <!-- 国家列表容器 -->
        <div id="countries-container"></div>

        <script src="script.js"></script>
    </main>
</body>
</html>

CSS

body {
    min-height: 100vh;
    background-color: aliceblue;
    font-family: 'Open Sans', Arial;
    font-size: 18px;
    padding: 2rem;
    margin: 0;
}

.container {
    max-width: 1200px;
    margin: 0 auto;
}

/* 筛选栏样式 */
.filter-bar {
    margin-bottom: 1rem;
    padding: 0.5rem;
    background: white;
    border-radius: 0.5rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

#letter-filter {
    padding: 0.3rem 0.6rem;
    border: 1px solid #ddd;
    border-radius: 0.3rem;
    font-size: 1rem;
}

/* 排序标题样式 */
.sortable-header {
    display: flex;
    justify-content: space-between;
    padding: 1rem;
    background: white;
    border-radius: 0.5rem;
    margin-bottom: 1rem;
    cursor: pointer;
}

.header-item {
    width: 23%;
    text-align: center;
    position: relative;
}

.header-item::after {
    content: " ↕";
    font-size: 0.8rem;
    margin-left: 0.3rem;
}

/* 子地区与国家列表样式 */
#countries-container ul {
    list-style: none;
    padding: 0;
    margin: 0;
}

.subregion {
    background: white;
    border-radius: 0.5rem;
    margin-bottom: 1rem;
    overflow: hidden;
}

.subregion-header {
    display: flex;
    justify-content: space-between;
    padding: 1rem 2rem;
    background: #f0f0f0;
    cursor: pointer;
    align-items: center;
}

.subregion-header h2 {
    margin: 0;
    color: #333;
}

.subregion-stats {
    display: flex;
    gap: 2rem;
}

.subregion-stats h3 {
    margin: 0;
    font-size: 1rem;
    color: #666;
}

.country-list {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease;
}

.country-list.expanded {
    max-height: 2000px;
}

.country {
    display: flex;
    justify-content: space-between;
    padding: 0.8rem 2rem;
    border-bottom: 1px solid #eee;
}

.country:last-child {
    border-bottom: none;
}

.country > div {
    width: 23%;
    text-align: center;
}

.country h4 {
    margin: 0;
    font-size: 0.9rem;
    color: #333;
}

JavaScript

// 全局变量存储原始数据、排序状态、筛选条件
let countriesData = [];
let currentSortKey = "name";
let currentSortOrder = "asc";
let currentFilterLetter = "";

// 初始化页面
async function init() {
    // 获取并处理数据
    const res = await fetch("https://restcountries.com/v3.1/all");
    const rawData = await res.json();
    
    // 格式化数据,处理缺失的首都/面积/人口
    countriesData = rawData.map(country => ({
        name: country.name.common,
        capital: country.capital ? country.capital[0] : "无",
        population: country.population || 0,
        area: country.area || 0,
        subregion: country.subregion || "无所属子地区"
    }));

    // 按子地区和名称初始排序
    sortCountries("name", "asc");
    // 渲染页面
    renderCountries();
    // 绑定事件
    bindEvents();
}

// 绑定所有交互事件
function bindEvents() {
    // 子地区展开收起事件(事件委托)
    document.getElementById("countries-container").addEventListener("click", (e) => {
        if (e.target.closest(".subregion-header")) {
            const subregionItem = e.target.closest(".subregion");
            const countryList = subregionItem.querySelector(".country-list");
            countryList.classList.toggle("expanded");
        }
    });

    // 列标题排序事件
    document.querySelectorAll(".header-item").forEach(item => {
        item.addEventListener("click", () => {
            const sortKey = item.dataset.sort;
            // 切换排序顺序
            if (currentSortKey === sortKey) {
                currentSortOrder = currentSortOrder === "asc" ? "desc" : "asc";
            } else {
                currentSortKey = sortKey;
                currentSortOrder = "asc";
            }
            sortCountries(currentSortKey, currentSortOrder);
            renderCountries();
        });
    });

    // 首字母筛选事件
    document.getElementById("letter-filter").addEventListener("change", (e) => {
        currentFilterLetter = e.target.value;
        renderCountries();
    });
}

// 排序国家数据
function sortCountries(key, order) {
    countriesData.sort((a, b) => {
        let valA, valB;
        // 处理字符串和数字的排序逻辑
        if (key === "name" || key === "capital") {
            valA = a[key].toLowerCase();
            valB = b[key].toLowerCase();
        } else {
            valA = a[key];
            valB = b[key];
        }

        if (order === "asc") {
            return valA > valB ? 1 : -1;
        } else {
            return valA < valB ? 1 : -1;
        }
    });
}

// 渲染国家列表
function renderCountries() {
    const container = document.getElementById("countries-container");
    container.innerHTML = "";

    // 按子地区分组
    const groupedBySubregion = countriesData.reduce((groups, country) => {
        // 应用首字母筛选
        if (currentFilterLetter && !country.name.startsWith(currentFilterLetter)) {
            return groups;
        }
        
        const subregion = country.subregion;
        if (!groups[subregion]) {
            groups[subregion] = [];
        }
        groups[subregion].push(country);
        return groups;
    }, {});

    // 创建列表元素
    const ul = document.createElement("ul");
    Object.entries(groupedBySubregion).forEach(([subregion, countries]) => {
        // 计算子地区的总人口和总面积
        const totalPopulation = countries.reduce((sum, c) => sum + c.population, 0);
        const totalArea = countries.reduce((sum, c) => sum + c.area, 0);

        // 创建子地区项
        const subregionLi = document.createElement("li");
        subregionLi.className = "subregion";

        // 子地区头部
        const header = document.createElement("div");
        header.className = "subregion-header";
        
        const nameDiv = document.createElement("div");
        nameDiv.innerHTML = `<h2>${subregion}</h2>`;
        
        const statsDiv = document.createElement("div");
        statsDiv.className = "subregion-stats";
        statsDiv.innerHTML = `
            <h3>总人口: ${formatNumber(totalPopulation)}</h3>
            <h3>总面积: ${formatNumber(totalArea)} km²</h3>
        `;
        
        header.appendChild(nameDiv);
        header.appendChild(statsDiv);
        subregionLi.appendChild(header);

        // 国家列表
        const countryList = document.createElement("div");
        countryList.className = "country-list";
        
        const ol = document.createElement("ol");
        countries.forEach(country => {
            const countryLi = document.createElement("li");
            countryLi.className = "country";
            countryLi.innerHTML = `
                <div><h4>${country.name}</h4></div>
                <div><h4>${country.capital}</h4></div>
                <div><h4>${formatNumber(country.population)}</h4></div>
                <div><h4>${formatNumber(country.area)} km²</h4></div>
            `;
            ol.appendChild(countryLi);
        });
        
        countryList.appendChild(ol);
        subregionLi.appendChild(countryList);
        ul.appendChild(subregionLi);
    });

    container.appendChild(ul);
}

// 格式化数字为千分位
function formatNumber(num) {
    return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

// 启动初始化
init();

功能实现说明

1. 子地区展开/收起

  • 使用CSS的max-height和过渡动画实现平滑展开收起效果
  • 通过事件委托绑定点击事件,避免为每个子地区单独绑定事件
  • 点击子地区头部时,切换country-list元素的expanded类,控制显示/隐藏

2. 列标题排序

  • 为每个列标题添加data-sort属性,标记排序字段
  • 点击标题时切换排序顺序(升序/降序),更新全局排序状态
  • 根据排序字段类型(字符串/数字)分别处理排序逻辑,确保排序正确
  • 排序后重新渲染整个列表

3. 首字母筛选

  • 添加下拉选择框,包含A-Z所有字母和"全部"选项
  • 选择字母后,更新全局筛选条件,重新渲染列表时只保留名称以指定字母开头的国家
  • 筛选逻辑嵌入到分组渲染过程中,无需额外过滤数组

内容的提问来源于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 16:31:04