实现可配置的英雄联盟玩家对局数据展示及配置持久化方案咨询
实现可配置的英雄联盟玩家对局数据展示及配置持久化方案咨询
嘿,这个需求完全可行,而且能让你的展示逻辑变得更灵活易维护!我来给你拆解一下具体的实现步骤,分四个核心部分来做:
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
相关产品推荐
相关产品推荐

