<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>淘汰赛对阵图生成demo
张小明
前端开发工程师
新手入门 Java:第一个程序 HelloWorld 详解
作为一名刚接触 Java 的大学生,相信很多小伙伴和我一样,从HelloWorld开启 Java 编程之旅。这个看似简单的程序,却包含了 Java 的基础语法和运行逻辑,今天就带大家一步步拆解,搞定 Java 第一个程序!一、准备…
实际项目开发应用--485通信
一、485通信波特率的选择 长距离485Modbus通信时,波特率设置“小点更好” ——核心原则是“优先保证通信稳定性,再兼顾效率”,高波特率会加剧信号衰减、抗干扰能力下降,反而容易出现丢包、误码;低波特率虽通信速度慢&a…
【JavaSE】十八、URL HTTP请求格式 常见报头 状态码 会话保持
文章目录Ⅰ. URLⅡ. 报文格式Ⅲ. HTTP 请求方法💥 GET 和 POST 的区别Ⅳ. HTTP 常见报头Ⅴ. HTTP 状态码Ⅵ. 会话保持一、Cookie二、Session三、两者区别四、理解 cookie、session、token 三者的区别Ⅰ. URL 统一资源描述定位符 URL(Uniform Resource L…
车间每天报喜不报忧,直到真 OEE 摆上墙,谁都装不下去!
目录 一、车间数据造假,到底有多日常? 1. 停机时间“自动消失” 2. 产量“向上取整”,报废“向下取整” 3. 点检表天天签,谁也没看过 二、为什么大家宁愿造假,也不愿报真实? 1. 指标只考结果…
python3.7-python3.12通过whl安装dlib
1、安装Cmakepip install cmake2、安装boostpip install cmake3、通过whl文件安装dlib下载链接中包括python3.7-python3.12版本对应的dlib库例如我的python版本是3.12,在.whl下载路径下,输入以下指令安装pip install dlib-19.24.2-cp312-cp312-win_amd64…
合并区间(二维vector使用,多维vector使用默认sort)
注意点: 1.sort自带的比较函数是支持多维数组比较的,使用的是字典序比较; 2.对于多维的vector,可以使用back,front,at等函数 比较例子: 二维 vector 示例 vector<vector<int>> v {{2,5},{1,3}…