news 2026/9/2 18:01:48

淘汰赛对阵图生成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/9/2 21:36:09

Python防止重复资源的链接mysql方法

配置连接池 配置连接池 POOL PooledDB( creatorpymysql, maxconnections5, # 连接池最大连接数 mincached1, # 初始化时连接池至少创建的空闲连接数 maxcached2, # 连接池最大空闲连接数 host“localhost”, port3306, user“root”, password“123456”, databa…

作者头像 李华
网站建设 2026/9/2 20:07:38

必看!这些油炸机性价比供应商,你不容错过!

《油炸机哪家好&#xff1a;排名前五专业深度测评》开篇&#xff1a;定下基调在餐饮行业蓬勃发展的当下&#xff0c;油炸机作为厨房中的重要设备&#xff0c;其性能、品质等方面备受关注。本次测评的目的就是为了给对油炸机感兴趣的人群提供客观、专业的参考&#xff0c;帮助大…

作者头像 李华
网站建设 2026/9/2 11:41:31

数字人不限时长安装教程

要实现无限时长数字人生成&#xff0c;本地部署数字人模型是核心方案&#xff08;摆脱平台时长限制&#xff09;&#xff0c;以下是零基础可落地的步骤&#xff1a; 环境准备&#xff1a;首先安装 Python 3.9 和 CUDA 11.8&#xff08;显卡需 NVIDIA RTX 3060 及以上&#xff…

作者头像 李华
网站建设 2026/9/2 18:42:12

推荐系统测试全链路指标解析与实战

1 推荐系统测试概述 推荐系统作为信息过滤的核心技术&#xff0c;其质量保障需覆盖算法效果、工程性能和用户体验三个维度。测试人员需要建立贯穿离线测试、在线测试和线上监控的完整指标体系&#xff0c;确保推荐结果既准确又高效。 2 核心测试指标分类 2.1 准确性指标 精…

作者头像 李华
网站建设 2026/9/2 11:26:10

hadoop高可用搭建(版本:2.9.2)

集群分布&#xff1a; node01 namenode(active) datanode nodemanager zk node02 namenode&#xff08;standby&#xff09; datanode resourcemanager&#xff08;standby&#xff09; nodemanager zk node03 resourcemanager&#xff08;active&#xff09; datanode nodema…

作者头像 李华