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

实现可配置的英雄联盟玩家对局数据展示及配置持久化方案咨询

实现可配置的英雄联盟玩家对局数据展示及配置持久化方案咨询

嘿,这个需求完全可行,而且能让你的展示逻辑变得更灵活易维护!我来给你拆解一下具体的实现步骤,分四个核心部分来做:


1. 统一管理所有可展示的字段配置

首先把所有要展示的对局数据整理成一个配置数组,这样后续不管加新字段还是改显示逻辑,都只需要动这个数组,不用碰渲染代码。每个字段可以包含显示名称、对应的API返回属性名、默认是否可见,还有可选的格式化函数(处理像win这种需要转成Yes/No的特殊字段):

// 全局定义所有可展示的字段配置
const statConfig = [
  // 基础信息组
  { label: 'Summoner Name', key: 'riotIdGameName', defaultVisible: true },
  { label: 'Champion Level', key: 'champLevel', defaultVisible: true },
  { label: 'Won', key: 'win', defaultVisible: true, formatter: (val) => val ? 'Yes' : 'No' },
  // 击杀数据组
  { label: 'Kills', key: 'kills', defaultVisible: true },
  { label: 'Deaths', key: 'deaths', defaultVisible: true },
  { label: 'Assists', key: 'assists', defaultVisible: true },
  { label: 'Double Kills', key: 'doubleKills', defaultVisible: false },
  { label: 'Triple Kills', key: 'tripleKills', defaultVisible: false },
  { label: 'Quadra Kills', key: 'quadraKills', defaultVisible: false },
  { label: 'Penta Kills', key: 'pentaKills', defaultVisible: false },
  // 技能数据组
  { label: 'Spell 1 ID', key: 'spell1Id', defaultVisible: false },
  { label: 'Spell 2 ID', key: 'spell2Id', defaultVisible: false },
  { label: 'Q casts', key: 'spell1Casts', defaultVisible: false },
  { label: 'W casts', key: 'spell2Casts', defaultVisible: false },
  { label: 'E casts', key: 'spell3Casts', defaultVisible: false },
  { label: 'R casts', key: 'spell4Casts', defaultVisible: false },
  { label: 'Summoner 1 casts', key: 'summoner1Casts', defaultVisible: false },
  { label: 'Summoner 2 casts', key: 'summoner2Casts', defaultVisible: false },
  // Ping数据组
  { label: 'Command pings', key: 'commandPings', defaultVisible: false },
  { label: 'On My Way pings', key: 'onMyWayPings', defaultVisible: false },
  { label: 'Assist pings', key: 'assistMePings', defaultVisible: false },
  { label: 'Enemy Missing pings', key: 'enemyMissingPings', defaultVisible: false },
  { label: 'Need Vision pings', key: 'needVisionPings', defaultVisible: false },
  { label: 'Hold pings', key: 'holdPings', defaultVisible: false },
  { label: 'Retreat pings', key: 'getBackPings', defaultVisible: false },
  { label: 'Enemy vision pings', key: 'enemyVisionPings', defaultVisible: false },
  { label: 'All in pings', key: 'allInPings', defaultVisible: false },
  // 伤害数据
  { label: 'Damage dealt', key: 'totalDamageDealtToChampions', defaultVisible: true }
];

2. 封装LocalStorage的读写逻辑

写两个简单的工具函数,用来加载用户保存的配置,以及把新配置存到LocalStorage里。第一次打开网站时,如果没有保存的配置,就用我们定义的默认可见状态:

// 加载用户配置(没有则返回默认配置)
function getUserStatConfig() {
  const saved = localStorage.getItem('lolPlayerStatsConfig');
  return saved ? JSON.parse(saved) : statConfig.map(item => ({ key: item.key, visible: item.defaultVisible }));
}

// 保存用户配置到LocalStorage
function saveUserStatConfig(config) {
  localStorage.setItem('lolPlayerStatsConfig', JSON.stringify(config));
}

// 初始化当前配置
let currentUserConfig = getUserStatConfig();

3. 实现配置弹窗UI

做一个简单的模态弹窗,让用户勾选想要展示的字段。你可以把这个弹窗的按钮放到页面合适的位置(比如右上角):

HTML部分

<!-- 配置弹窗(默认隐藏) -->
<div id="statsConfigModal" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #fff; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 1000;">
  <h3>选择要展示的对局数据</h3>
  <!-- 分组展示字段,更清晰 -->
  <div style="margin: 1rem 0;">
    <h4>基础信息</h4>
    <div id="basicStatsGroup"></div>
  </div>
  <div style="margin: 1rem 0;">
    <h4>击杀数据</h4>
    <div id="killStatsGroup"></div>
  </div>
  <div style="margin: 1rem 0;">
    <h4>技能数据</h4>
    <div id="spellStatsGroup"></div>
  </div>
  <div style="margin: 1rem 0;">
    <h4>Ping数据</h4>
    <div id="pingStatsGroup"></div>
  </div>
  <div style="margin-top: 1.5rem; display: flex; gap: 1rem; justify-content: flex-end;">
    <button id="resetConfigBtn">恢复默认</button>
    <button id="saveConfigBtn">保存设置</button>
    <button id="closeConfigBtn">关闭</button>
  </div>
</div>

<!-- 打开配置的按钮 -->
<button id="openConfigBtn">数据展示设置</button>

JavaScript部分

写代码来渲染复选框,绑定弹窗的打开、关闭、保存、重置事件:

// 按分组渲染复选框
function renderConfigCheckboxes() {
  // 先清空所有分组容器
  document.getElementById('basicStatsGroup').innerHTML = '';
  document.getElementById('killStatsGroup').innerHTML = '';
  document.getElementById('spellStatsGroup').innerHTML = '';
  document.getElementById('pingStatsGroup').innerHTML = '';

  statConfig.forEach(stat => {
    const checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    checkbox.id = `stat-${stat.key}`;
    // 匹配当前用户配置的可见状态,没有则用默认值
    checkbox.checked = currentUserConfig.find(item => item.key === stat.key)?.visible ?? stat.defaultVisible;

    const label = document.createElement('label');
    label.htmlFor = checkbox.id;
    label.textContent = stat.label;
    label.style.marginLeft = '0.5rem';
    label.style.display = 'block';
    label.style.marginBottom = '0.3rem';

    // 根据字段分组插入到对应的容器
    let targetContainer;
    if (['riotIdGameName', 'champLevel', 'win'].includes(stat.key)) {
      targetContainer = document.getElementById('basicStatsGroup');
    } else if (stat.key.includes('Kill') || ['kills', 'deaths', 'assists'].includes(stat.key)) {
      targetContainer = document.getElementById('killStatsGroup');
    } else if (stat.key.includes('spell') || stat.key.includes('Summoner')) {
      targetContainer = document.getElementById('spellStatsGroup');
    } else if (stat.key.includes('ping')) {
      targetContainer = document.getElementById('pingStatsGroup');
    } else {
      // 其他字段放到基础组
      targetContainer = document.getElementById('basicStatsGroup');
    }

    targetContainer.appendChild(checkbox);
    targetContainer.appendChild(label);
  });
}

// 绑定弹窗事件
document.getElementById('openConfigBtn').addEventListener('click', () => {
  renderConfigCheckboxes();
  document.getElementById('statsConfigModal').style.display = 'block';
});

document.getElementById('closeConfigBtn').addEventListener('click', () => {
  document.getElementById('statsConfigModal').style.display = 'none';
});

document.getElementById('saveConfigBtn').addEventListener('click', () => {
  // 收集用户勾选的状态
  const updatedConfig = statConfig.map(stat => {
    const checkbox = document.getElementById(`stat-${stat.key}`);
    return { key: stat.key, visible: checkbox.checked };
  });
  // 更新当前配置并保存到LocalStorage
  currentUserConfig = updatedConfig;
  saveUserStatConfig(currentUserConfig);
  // 关闭弹窗,也可以在这里重新渲染玩家弹窗让配置立即生效
  document.getElementById('statsConfigModal').style.display = 'none';
});

document.getElementById('resetConfigBtn').addEventListener('click', () => {
  // 恢复默认配置
  currentUserConfig = statConfig.map(item => ({ key: item.key, visible: item.defaultVisible }));
  // 重新渲染复选框
  renderConfigCheckboxes();
});

4. 动态渲染玩家数据弹窗

把原来的硬编码渲染逻辑改成根据用户配置动态生成内容,这样用户勾选的字段才会显示:

function renderPlayerPopup(participant) {
  let popupHtml = `<h2>${participant.championName}</h2>`;

  // 过滤出当前用户配置中可见的字段
  const visibleStats = statConfig.filter(stat => {
    const configItem = currentUserConfig.find(item => item.key === stat.key);
    return configItem?.visible ?? stat.defaultVisible;
  });

  // 遍历可见字段生成HTML
  visibleStats.forEach(stat => {
    let value = participant[stat.key];
    // 处理特殊格式化(比如win字段转Yes/No)
    if (stat.formatter) {
      value = stat.formatter(value);
    }
    // 避免显示undefined,给空值设默认文本
    value = value ?? 'N/A';
    popupHtml += `<p><strong>${stat.label}:</strong> ${value}</p>`;
  });

  popupContent.innerHTML = popupHtml;
}

一些额外的优化建议

  • 可以给弹窗加个半透明背景,点击背景也能关闭弹窗,提升用户体验。
  • 如果API返回的字段有数值型的(比如击杀数),可以加千位分隔符让显示更友好。
  • 后续要加新的对局数据,只需要在statConfig数组里加一条配置就行,完全不用改渲染和配置UI的代码,扩展性拉满。

备注:内容来源于stack exchange,提问作者hello1231231231

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 12:05:29