news 2026/8/8 11:19:05

淘汰赛对阵图生成demo

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
淘汰赛对阵图生成demo

<template> <div class="page"> <section class="panel"> <h1>淘汰赛对阵图生成器</h1> <div class="controls"> <label class="field"> <span>队伍数量(2-64)</span> <input v-model.number="teamCount" type="number" min="0" max="64" /> </label> <div class="names"> <div class="names-header"> <span>队伍名称</span> <div class="actions"> <button type="button" @click="autoFillNames">自动填充</button> <button type="button" class="primary" @click="generateBracket">生成对阵</button> </div> </div> <div class="name-grid"> <div v-for="(name, idx) in teamInputs" :key="idx" class="name-item"> <label> <span>#{{ idx + 1 }}</span> <input v-model="teamInputs[idx]" type="text" :placeholder="`队伍${idx + 1}`" /> </label> </div> </div> </div> </div> </section> <section class="panel"> <div class="panel-head"> <h2>对阵图</h2> </div> <div v-if="rounds.length" class="bracket" :style="{ gridTemplateRows: `repeat(${gridRows}, 22px)` }"> <div v-for="(round, rIdx) in rounds" :key="rIdx" class="round"> <div v-for="(team, tIdx) in round" :key="`${rIdx}-${tIdx}`" class="match" :class="{ final: rIdx === rounds.length - 1, hasConnector: rIdx < rounds.length - 1, top: tIdx % 2 === 0, bottom: tIdx % 2 === 1 }" :style="teamGridStyle(rIdx, tIdx)"> <div class="team"> <span class="seed" v-if="rIdx === 0">{{ tIdx + 1 }}</span> <span class="name">{{ team }}</span> </div> </div> </div> </div> </section> </div> </template> <script setup> import { computed, ref, watch } from 'vue' const teamCount = ref(8) const teamInputs = ref(Array.from({ length: teamCount.value }, (_, i) => `队伍${i + 1}`)) const rounds = ref([]) const bracketSize = computed(() => (rounds.value.length ? rounds.value[0].length : 0)) const gridRows = computed(() => bracketSize.value * 2) watch(teamCount, (val) => { const safe = Math.min(64, Math.max(2, Number(val) || 2)) if (safe !== val) teamCount.value = safe if (teamInputs.value.length < safe) { const start = teamInputs.value.length for (let i = start; i < safe; i += 1) { teamInputs.value.push(`队伍${i + 1}`) } } else if (teamInputs.value.length > safe) { teamInputs.value.splice(safe) } }) const isPowerOfTwo = (n) => n > 0 && (n & (n - 1)) === 0 const makeRounds = (teams) => { const size = teams.length const result = [] let current = [] // 第一轮:每个队伍单独一个元素 for (let i = 0; i < size; i++) { current.push(teams[i]) } result.push(current) // 后续轮次:每轮队伍数量减半 while (current.length > 1) { const next = [] for (let i = 0; i < current.length / 2; i++) { next.push('上一场胜者') } result.push(next) current = next } return result } const generateBracket = () => { const names = teamInputs.value.map((t, i) => (t?.trim() ? t.trim() : `队伍${i + 1}`)) const valid = names.filter(Boolean) if (valid.length < 2) { alert('至少需要 2 支队伍') return } if (!isPowerOfTwo(valid.length)) { alert('队伍数量需为 2 的整数次幂,例如 2/4/8/16') return } rounds.value = makeRounds(valid) } const autoFillNames = () => { teamInputs.value = Array.from({ length: teamCount.value }, (_, i) => `队伍${i + 1}`) } const teamGridStyle = (roundIdx, teamIdx) => { // 所有div统一高度为50px,占1行 // 第一轮:队伍占据奇数行(1, 3, 5, 7...) // 后续轮次:胜者占据偶数行,位置在前一轮对应两队的中间 if (roundIdx === 0) { // 第一轮:每个队伍占1行,使用奇数行 const start = teamIdx * 2 + 1 return { gridRow: `${start} / span 1` } } else { // 后续轮次:递归计算前一轮对应两队的位置 // 前一轮的队伍索引是 teamIdx*2 和 teamIdx*2+1 const getRow = (rIdx, tIdx) => { if (rIdx === 0) { return tIdx * 2 + 1 } else { const prevTeam1Row = getRow(rIdx - 1, tIdx * 2) const prevTeam2Row = getRow(rIdx - 1, tIdx * 2 + 1) return Math.round((prevTeam1Row + prevTeam2Row) / 2) } } const centerRow = getRow(roundIdx, teamIdx) return { gridRow: `${centerRow} / span 1` } } } </script> <style scoped> :global(body) { margin: 0; background: #f7f8fb; font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif; color: #1f2933; } .page { max-width: 1200px; margin: 32px auto 64px; padding: 0 20px; display: flex; flex-direction: column; gap: 20px; } .panel { background: #fff; border-radius: 12px; box-shadow: 0 10px 30px rgba(31, 41, 51, 0.08); padding: 20px; } .panel h1, .panel h2 { margin: 0 0 12px; font-weight: 700; } .panel-head { display: flex; align-items: center; gap: 10px; } .hint { color: #5f6b7a; font-size: 14px; } .controls { display: flex; flex-direction: column; gap: 16px; } .field { display: flex; align-items: center; gap: 12px; font-weight: 600; } input[type='number'], input[type='text'] { padding: 8px 10px; border: 1px solid #d5dae1; border-radius: 8px; outline: none; transition: 0.15s ease; font-size: 14px; width: 110px; } input[type='text'] { width: 100%; } input:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); } .names { border: 1px solid #e3e7ef; border-radius: 10px; padding: 12px; } .names-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; font-weight: 600; } .actions { display: flex; gap: 10px; } button { border: 1px solid #cfd6e4; background: #fff; padding: 8px 12px; border-radius: 8px; cursor: pointer; font-weight: 600; transition: 0.15s ease; } button:hover { background: #f0f4ff; border-color: #94b3ff; } button.primary { background: #3b82f6; color: #fff; border-color: #3b82f6; } button.primary:hover { background: #2763c6; } .name-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 10px; } .name-item label { display: flex; align-items: center; gap: 8px; font-weight: 600; color: #475364; } .name-item span { min-width: 36px; text-align: right; color: #6b7687; } .bracket { display: grid; grid-auto-flow: column; grid-auto-columns: 90px; column-gap: 10px; position: relative; padding: 4px 0 2px; overflow-x: auto; } .round { display: grid; grid-template-rows: subgrid; grid-row: 1 / -1; align-content: start; } .round-title { font-weight: 700; color: #334155; margin-bottom: 10px; } .match { position: relative; background: linear-gradient(180deg, #f9fbff 0%, #f0f4ff 100%); border: 1px solid #d8e2f3; border-radius: 5px; padding: 4px 6px; display: flex; align-items: center; height: 22px; box-shadow: 0 3px 6px rgba(59, 130, 246, 0.08); font-size: 11px; } .match.hasConnector::after { content: ''; position: absolute; right: -10px; top: 50%; width: 10px; height: 1.5px; background: #c4d4f5; } .match.hasConnector.top::before, .match.hasConnector.bottom::before { content: ''; position: absolute; right: -10px; width: 1.5px; background: #c4d4f5; } .match.hasConnector.top::before { top: 50%; height: calc(100% + 11px); /* 半场距 */ } .match.hasConnector.bottom::before { bottom: 50%; height: calc(100% + 11px); } .match.final::after { content: none; } .match.final::before, .match.final::after { display: none; } .team { display: flex; align-items: center; gap: 3px; font-weight: 600; color: #1f2937; width: 100%; font-size: 11px; } .seed { display: inline-flex; align-items: center; justify-content: center; min-width: 14px; height: 14px; background: #e6ecfb; color: #3b5ab8; border-radius: 3px; font-size: 9px; } .name { flex: 1; text-align: left; } .champion { position: absolute; top: 50%; right: -60px; transform: translateY(-50%); font-weight: 700; color: #f97316; } @media (max-width: 768px) { .page { margin: 8px auto 16px; padding: 0 8px; } .panel { padding: 10px; } .bracket { grid-auto-columns: 80px; column-gap: 8px; } .match { padding: 4px 5px; height: 20px; font-size: 10px; } .team { font-size: 10px; gap: 2px; } .seed { min-width: 12px; height: 12px; font-size: 8px; } .match.hasConnector::after { right: -10px; width: 10px; height: 1px; } } </style>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/8 5:00:21

实习面试题-Rust 面试题

1.Rust 有哪些特点?它和 C、C++ 有什么区别? 回答重点 Rust 是一门系统编程语言,最大的特点就是在保证高性能的同时,通过编译器在编译期就能保证内存安全。简单来说,Rust 既有 C/C++ 那样的性能,又不会出现 C/C++ 那些令人头疼的内存问题。 Rust 的核心特点包括所有权…

作者头像 李华
网站建设 2026/8/7 23:56:02

视频字幕精确生成方法 用到字幕api开发文档

视频字幕精确生成方法 用到字幕api开发文档 本文档将详细介绍如何调用 /api/stt/audiototexturls 接口完成音频转文字&#xff0c;并基于接口返回的时间轴数据生成 SRT 等格式的精准字幕&#xff0c;帮助开发者快速实现相关功能。 一、接口核心信息梳理 你需要调用的是一个支持…

作者头像 李华
网站建设 2026/8/7 23:51:21

SPA 应用中的路由切换内存泄漏:未注销的 Scroll 监听与全局变量

SPA 应用中的路由切换内存泄漏&#xff1a;未注销的 Scroll 监听与全局变量大家好&#xff0c;我是你们的技术讲师。今天我们来深入探讨一个在现代前端开发中非常常见却又容易被忽视的问题——单页应用&#xff08;SPA&#xff09;中的内存泄漏问题&#xff0c;特别是由 未注销…

作者头像 李华
网站建设 2026/8/8 0:34:14

游泳池漆专用施工涂料如何选?专业视角解析耐水抗氯性能

说到游泳池漆哪个牌子好&#xff0c;很多工程方都会头疼。去年我亲自跟进一个市政泳池项目&#xff0c;施工队试了三种漆都出现脱落。后来改用海瑞的水池蓝池底漆&#xff0c;效果确实稳定。 环保安全与施工便捷的双重优势 游泳池漆哪个牌子好&#xff0c;首先要看环保指标。海…

作者头像 李华
网站建设 2026/8/7 23:48:32

中国RFID设备十大企业综合实力解析

&#xff08;注&#xff1a;以下排名基于技术研发、市场份额、行业应用等维度综合评估&#xff09;行业领军企业远望谷技术深耕物联网识别领域20年&#xff0c;其超高频读写设备在铁路物流管理市占率达38%&#xff0c;自主研发的$ \lambda \frac{c}{f} $抗干扰算法显著提升多标…

作者头像 李华