重构赛事的树状图
This commit is contained in:
parent
874e2fc4dc
commit
1977b791e5
421
package-lock.json
generated
421
package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.9.0",
|
||||
"d3": "^7.9.0",
|
||||
"jszip": "^3.10.1",
|
||||
"process": "^0.11.10",
|
||||
"vue": "^3.5.13",
|
||||
@ -1579,6 +1580,14 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz",
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
@ -1635,6 +1644,376 @@
|
||||
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||
},
|
||||
"node_modules/d3": {
|
||||
"version": "7.9.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3/-/d3-7.9.0.tgz",
|
||||
"integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
|
||||
"dependencies": {
|
||||
"d3-array": "3",
|
||||
"d3-axis": "3",
|
||||
"d3-brush": "3",
|
||||
"d3-chord": "3",
|
||||
"d3-color": "3",
|
||||
"d3-contour": "4",
|
||||
"d3-delaunay": "6",
|
||||
"d3-dispatch": "3",
|
||||
"d3-drag": "3",
|
||||
"d3-dsv": "3",
|
||||
"d3-ease": "3",
|
||||
"d3-fetch": "3",
|
||||
"d3-force": "3",
|
||||
"d3-format": "3",
|
||||
"d3-geo": "3",
|
||||
"d3-hierarchy": "3",
|
||||
"d3-interpolate": "3",
|
||||
"d3-path": "3",
|
||||
"d3-polygon": "3",
|
||||
"d3-quadtree": "3",
|
||||
"d3-random": "3",
|
||||
"d3-scale": "4",
|
||||
"d3-scale-chromatic": "3",
|
||||
"d3-selection": "3",
|
||||
"d3-shape": "3",
|
||||
"d3-time": "3",
|
||||
"d3-time-format": "4",
|
||||
"d3-timer": "3",
|
||||
"d3-transition": "3",
|
||||
"d3-zoom": "3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-axis": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-axis/-/d3-axis-3.0.0.tgz",
|
||||
"integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-brush": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-brush/-/d3-brush-3.0.0.tgz",
|
||||
"integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-drag": "2 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-selection": "3",
|
||||
"d3-transition": "3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-chord": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/d3-chord/-/d3-chord-3.0.1.tgz",
|
||||
"integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
|
||||
"dependencies": {
|
||||
"d3-path": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-contour": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/d3-contour/-/d3-contour-4.0.2.tgz",
|
||||
"integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
|
||||
"dependencies": {
|
||||
"d3-array": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-delaunay": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
|
||||
"integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
|
||||
"dependencies": {
|
||||
"delaunator": "5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-dispatch": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
|
||||
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-drag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz",
|
||||
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-selection": "3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-dsv": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz",
|
||||
"integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
|
||||
"dependencies": {
|
||||
"commander": "7",
|
||||
"iconv-lite": "0.6",
|
||||
"rw": "1"
|
||||
},
|
||||
"bin": {
|
||||
"csv2json": "bin/dsv2json.js",
|
||||
"csv2tsv": "bin/dsv2dsv.js",
|
||||
"dsv2dsv": "bin/dsv2dsv.js",
|
||||
"dsv2json": "bin/dsv2json.js",
|
||||
"json2csv": "bin/json2dsv.js",
|
||||
"json2dsv": "bin/json2dsv.js",
|
||||
"json2tsv": "bin/json2dsv.js",
|
||||
"tsv2csv": "bin/dsv2dsv.js",
|
||||
"tsv2json": "bin/dsv2json.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-ease": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-fetch": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz",
|
||||
"integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
|
||||
"dependencies": {
|
||||
"d3-dsv": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-force": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz",
|
||||
"integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-quadtree": "1 - 3",
|
||||
"d3-timer": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-format": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz",
|
||||
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-geo": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.1.tgz",
|
||||
"integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
|
||||
"dependencies": {
|
||||
"d3-array": "2.5.0 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-hierarchy": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
|
||||
"integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-path": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz",
|
||||
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-polygon": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-3.0.1.tgz",
|
||||
"integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-quadtree": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
|
||||
"integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-random": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz",
|
||||
"integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||
"dependencies": {
|
||||
"d3-array": "2.10.0 - 3",
|
||||
"d3-format": "1 - 3",
|
||||
"d3-interpolate": "1.2.0 - 3",
|
||||
"d3-time": "2.1.1 - 3",
|
||||
"d3-time-format": "2 - 4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale-chromatic": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
|
||||
"integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3",
|
||||
"d3-interpolate": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-selection": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-shape": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||
"dependencies": {
|
||||
"d3-path": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz",
|
||||
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||
"dependencies": {
|
||||
"d3-array": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time-format": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||
"dependencies": {
|
||||
"d3-time": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-timer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-transition": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz",
|
||||
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3",
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-ease": "1 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-timer": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"d3-selection": "2 - 3"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-zoom": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz",
|
||||
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-drag": "2 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-selection": "2 - 3",
|
||||
"d3-transition": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz",
|
||||
@ -1692,6 +2071,14 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/delaunator": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/delaunator/-/delaunator-5.0.1.tgz",
|
||||
"integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
|
||||
"dependencies": {
|
||||
"robust-predicates": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@ -2103,6 +2490,17 @@
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz",
|
||||
@ -2113,6 +2511,14 @@
|
||||
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz",
|
||||
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/is-docker": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz",
|
||||
@ -2556,6 +2962,11 @@
|
||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/robust-predicates": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz",
|
||||
"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.40.1",
|
||||
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.40.1.tgz",
|
||||
@ -2607,11 +3018,21 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/rw": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz",
|
||||
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
|
||||
|
@ -10,6 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.9.0",
|
||||
"d3": "^7.9.0",
|
||||
"jszip": "^3.10.1",
|
||||
"process": "^0.11.10",
|
||||
"vue": "^3.5.13",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,26 +3,18 @@
|
||||
<h1>单败淘汰赛赛程树状图</h1>
|
||||
<div class="container">
|
||||
<div class="control-panel">
|
||||
<h2>选择比赛</h2>
|
||||
<div class="tournament-info">
|
||||
<select v-model="selectedTournamentId" @change="handleTournamentChange">
|
||||
<option value="">-- 请选择比赛 --</option>
|
||||
<option v-for="tournament in tournaments" :key="tournament.id" :value="tournament.id">
|
||||
{{ tournament.name }}
|
||||
</option>
|
||||
</select>
|
||||
<div v-if="selectedTournamentId">
|
||||
<strong>已选择:</strong> {{ selectedTournamentName }}
|
||||
<div>
|
||||
<strong>当前比赛:</strong> {{ tournament.name || '加载中...' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>参赛者列表</h2>
|
||||
<div id="player-list">
|
||||
<div v-if="!selectedTournamentId" class="loading">请先选择比赛</div>
|
||||
<div v-else-if="loading" class="loading">加载中...</div>
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
<div v-else>
|
||||
<p v-if="participants.length === 0">暂无参赛者</p>
|
||||
<template v-else>
|
||||
<template v-else>
|
||||
<p>共 {{ participants.length }} 位参赛者</p>
|
||||
<ul>
|
||||
<li v-for="participant in participants" :key="participant.id">
|
||||
@ -45,42 +37,15 @@
|
||||
</div>
|
||||
|
||||
<div class="bracket-container" id="bracketContainer">
|
||||
<div class="bracket" id="bracket">
|
||||
<template v-for="(roundMatches, rIdx) in roundsMatches" :key="rIdx">
|
||||
<div class="round">
|
||||
<div class="round-title">第 {{ rIdx + 1 }} 轮</div>
|
||||
<template v-for="match in roundMatches" :key="match.id">
|
||||
<div class="match" :id="'match-' + match.id">
|
||||
<!-- 参赛者1 -->
|
||||
<div class="participant" :class="{ winner: match.winner && match.participant1 && match.winner.id === match.participant1.id }">
|
||||
{{ match.participant1 ? match.participant1.name : '轮空' }}
|
||||
<input type="number" min="0" class="score-input"
|
||||
v-model.number="match.score1"
|
||||
:disabled="match.decided || !match.participant1 || !match.participant2"
|
||||
:placeholder="match.participant1 ? (match.participant1.win || '0') : '0'"
|
||||
>
|
||||
</div>
|
||||
<!-- 参赛者2 -->
|
||||
<div class="participant" :class="{ winner: match.winner && match.participant2 && match.winner.id === match.participant2.id }">
|
||||
{{ match.participant2 ? match.participant2.name : '轮空' }}
|
||||
<input type="number" min="0" class="score-input"
|
||||
v-model.number="match.score2"
|
||||
:disabled="match.decided || !match.participant1 || !match.participant2"
|
||||
:placeholder="match.participant2 ? (match.participant2.win || '0') : '0'"
|
||||
>
|
||||
</div>
|
||||
<!-- 确认比分按钮 -->
|
||||
<button class="score-btn"
|
||||
:disabled="match.decided || !match.participant1 || !match.participant2"
|
||||
@click="confirmScore(match)">
|
||||
确认比分
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<div class="zoom-controls">
|
||||
<button class="zoom-btn" @click="zoomIn" title="放大 (+)">+</button>
|
||||
<button class="zoom-btn" @click="zoomOut" title="缩小 (-)">-</button>
|
||||
<button class="zoom-btn" @click="resetZoom" title="重置视图 (0)">⌂</button>
|
||||
</div>
|
||||
<svg class="bracket-lines" id="bracketLines"></svg>
|
||||
<div class="interaction-hint" v-if="tournament.matches.length > 0">
|
||||
<span>💡 拖拽移动 | 滚轮缩放 | 快捷键: +放大 -缩小 0重置</span>
|
||||
</div>
|
||||
<svg id="d3-bracket" class="d3-bracket"></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -89,6 +54,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch, nextTick, onUnmounted } from 'vue';
|
||||
import { getSignUpResultList, updateSignUpResult, getTournamentList } from '@/api/tournament';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
const props = defineProps({
|
||||
tournamentId: {
|
||||
@ -97,13 +63,14 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const tournaments = ref([]);
|
||||
const selectedTournamentId = ref('');
|
||||
const selectedTournamentName = ref('');
|
||||
const participants = ref([]);
|
||||
const loading = ref(false);
|
||||
const finalRanking = ref(null);
|
||||
|
||||
// Store zoom behavior for external control
|
||||
let zoomBehavior = null;
|
||||
let svgSelection = null;
|
||||
|
||||
const tournament = ref({
|
||||
id: props.tournamentId,
|
||||
name: '',
|
||||
@ -113,7 +80,7 @@ const tournament = ref({
|
||||
});
|
||||
|
||||
const canGenerateBracket = computed(() => {
|
||||
return selectedTournamentId.value && participants.value.length >= 2;
|
||||
return participants.value.length >= 2;
|
||||
});
|
||||
|
||||
const roundsMatches = computed(() => {
|
||||
@ -125,45 +92,62 @@ const roundsMatches = computed(() => {
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
fetchTournamentList();
|
||||
loadTournamentData();
|
||||
|
||||
// Add keyboard shortcuts for zoom control
|
||||
const handleKeydown = (event) => {
|
||||
if (event.target.tagName === 'INPUT') return; // Don't trigger when typing in inputs
|
||||
|
||||
switch(event.key) {
|
||||
case '+':
|
||||
case '=':
|
||||
event.preventDefault();
|
||||
zoomIn();
|
||||
break;
|
||||
case '-':
|
||||
event.preventDefault();
|
||||
zoomOut();
|
||||
break;
|
||||
case '0':
|
||||
event.preventDefault();
|
||||
resetZoom();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
|
||||
// Store cleanup function
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
const svg = d3.select('#d3-bracket');
|
||||
if (svg.node()) svg.selectAll('*').remove();
|
||||
});
|
||||
});
|
||||
|
||||
const fetchTournamentList = async () => {
|
||||
const loadTournamentData = async () => {
|
||||
if (!props.tournamentId) return;
|
||||
|
||||
loading.value = true;
|
||||
tournament.value.id = props.tournamentId;
|
||||
|
||||
try {
|
||||
const data = await getTournamentList();
|
||||
tournaments.value = data;
|
||||
} catch (err) {
|
||||
console.error('获取比赛列表失败:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTournamentChange = async () => {
|
||||
if (!selectedTournamentId.value) {
|
||||
selectedTournamentName.value = '';
|
||||
participants.value = [];
|
||||
tournament.value.id = null;
|
||||
tournament.value.name = '';
|
||||
tournament.value.participants = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedTournament = tournaments.value.find(t => t.id === selectedTournamentId.value);
|
||||
if (selectedTournament) {
|
||||
selectedTournamentName.value = selectedTournament.name;
|
||||
tournament.value.id = selectedTournamentId.value;
|
||||
tournament.value.name = selectedTournament.name;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const data = await getSignUpResultList();
|
||||
const filtered = data.filter(item => item.tournament_id == selectedTournamentId.value);
|
||||
participants.value = filtered.map(item => ({ id: item.id, name: item.sign_name }));
|
||||
tournament.value.participants = participants.value;
|
||||
} catch (err) {
|
||||
console.error('获取参赛者失败:', err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
// Get tournament list to find the tournament name
|
||||
const tournaments = await getTournamentList();
|
||||
const selectedTournament = tournaments.find(t => t.id === props.tournamentId);
|
||||
if (selectedTournament) {
|
||||
tournament.value.name = selectedTournament.name;
|
||||
}
|
||||
|
||||
// Get participants for this tournament
|
||||
const data = await getSignUpResultList();
|
||||
const filtered = data.filter(item => item.tournament_id == props.tournamentId);
|
||||
participants.value = filtered.map(item => ({ id: item.id, name: item.sign_name }));
|
||||
tournament.value.participants = participants.value;
|
||||
} catch (err) {
|
||||
console.error('获取比赛数据失败:', err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
@ -171,7 +155,7 @@ const generateBracket = () => {
|
||||
if (!canGenerateBracket.value) return;
|
||||
finalRanking.value = null;
|
||||
generateSingleEliminationBracket();
|
||||
nextTick(() => drawConnections());
|
||||
nextTick(() => drawD3Bracket());
|
||||
};
|
||||
|
||||
const generateSingleEliminationBracket = async () => {
|
||||
@ -253,52 +237,262 @@ const generateSingleEliminationBracket = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const drawConnections = () => {
|
||||
try {
|
||||
const bracketLinesSVG = document.getElementById('bracketLines');
|
||||
const bracketDiv = document.getElementById('bracket');
|
||||
if (!bracketDiv || !bracketLinesSVG) return;
|
||||
bracketLinesSVG.innerHTML = '';
|
||||
const bracketRect = bracketDiv.getBoundingClientRect();
|
||||
const matchElements = {};
|
||||
document.querySelectorAll('.match').forEach(el => {
|
||||
const id = el.id.replace('match-', '');
|
||||
matchElements[id] = el;
|
||||
const drawD3Bracket = () => {
|
||||
const svg = d3.select('#d3-bracket');
|
||||
svg.selectAll('*').remove(); // Clear previous content
|
||||
|
||||
if (!tournament.value.matches.length) return;
|
||||
|
||||
const margin = { top: 40, right: 40, bottom: 40, left: 40 };
|
||||
const matchWidth = 180;
|
||||
const matchHeight = 120;
|
||||
const roundGap = 50;
|
||||
const matchGap = 20;
|
||||
|
||||
const rounds = tournament.value.rounds;
|
||||
const maxMatchesInRound = Math.pow(2, rounds - 1);
|
||||
|
||||
const totalWidth = rounds * (matchWidth + roundGap) + margin.left + margin.right;
|
||||
const totalHeight = maxMatchesInRound * (matchHeight + matchGap) + margin.top + margin.bottom;
|
||||
|
||||
// Get container dimensions for proper sizing
|
||||
const container = document.getElementById('bracketContainer');
|
||||
const containerWidth = container.clientWidth - 30; // Account for padding
|
||||
const containerHeight = container.clientHeight - 30;
|
||||
|
||||
svg
|
||||
.attr('width', containerWidth)
|
||||
.attr('height', containerHeight)
|
||||
.attr('viewBox', `0 0 ${totalWidth} ${totalHeight}`)
|
||||
.attr('preserveAspectRatio', 'xMidYMid meet');
|
||||
|
||||
// Create zoom behavior
|
||||
const zoom = d3.zoom()
|
||||
.scaleExtent([0.1, 3]) // Allow zoom from 10% to 300%
|
||||
.on('zoom', (event) => {
|
||||
g.attr('transform', `translate(${margin.left},${margin.top}) ${event.transform}`);
|
||||
});
|
||||
tournament.value.matches.forEach(match => {
|
||||
if (match.round === tournament.value.rounds) return;
|
||||
if (!match.decided) return;
|
||||
const nextRound = match.round + 1;
|
||||
const nextMatchNumber = Math.floor((match.matchNumber - 1) / 2) + 1;
|
||||
const nextMatchId = `${nextRound}-${nextMatchNumber}`;
|
||||
const nextMatchEl = matchElements[nextMatchId];
|
||||
const currentMatchEl = matchElements[match.id];
|
||||
if (!nextMatchEl || !currentMatchEl) return;
|
||||
const winnerIndex = (match.winner.id === (match.participant1 && match.participant1.id)) ? 1 : 2;
|
||||
const curRect = currentMatchEl.getBoundingClientRect();
|
||||
const nextRect = nextMatchEl.getBoundingClientRect();
|
||||
const startX = curRect.right - bracketRect.left;
|
||||
const startY = curRect.top - bracketRect.top + (winnerIndex === 1 ? 20 : 50);
|
||||
const endX = nextRect.left - bracketRect.left;
|
||||
const endY = nextRect.top - bracketRect.top + (match.matchNumber % 2 === 1 ? 20 : 50);
|
||||
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
path.setAttribute('stroke', '#666');
|
||||
path.setAttribute('fill', 'none');
|
||||
path.setAttribute('stroke-width', '2');
|
||||
const midX = (startX + endX) / 2;
|
||||
const d = `M${startX},${startY} L${midX},${startY} L${midX},${endY} L${endX},${endY}`;
|
||||
path.setAttribute('d', d);
|
||||
bracketLinesSVG.appendChild(path);
|
||||
|
||||
// Store references for external control
|
||||
zoomBehavior = zoom;
|
||||
svgSelection = svg;
|
||||
|
||||
// Apply zoom to SVG
|
||||
svg.call(zoom);
|
||||
|
||||
// Add cursor styles for pan/zoom
|
||||
svg.style('cursor', 'grab')
|
||||
.on('mousedown', () => svg.style('cursor', 'grabbing'))
|
||||
.on('mouseup', () => svg.style('cursor', 'grab'));
|
||||
|
||||
// Create main group for content
|
||||
const g = svg.append('g')
|
||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||
|
||||
// Draw matches for each round
|
||||
for (let round = 1; round <= rounds; round++) {
|
||||
const roundMatches = tournament.value.matches.filter(m => m.round === round);
|
||||
const matchesInRound = roundMatches.length;
|
||||
const roundX = (round - 1) * (matchWidth + roundGap);
|
||||
const roundStartY = (maxMatchesInRound * (matchHeight + matchGap) - matchesInRound * (matchHeight + matchGap)) / 2;
|
||||
|
||||
// Round title
|
||||
g.append('text')
|
||||
.attr('x', roundX + matchWidth / 2)
|
||||
.attr('y', -10)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('class', 'round-title')
|
||||
.style('font-weight', 'bold')
|
||||
.style('font-size', '14px')
|
||||
.text(`第 ${round} 轮`);
|
||||
|
||||
roundMatches.forEach((match, idx) => {
|
||||
const matchY = roundStartY + idx * (matchHeight + matchGap);
|
||||
|
||||
// Match container
|
||||
const matchGroup = g.append('g')
|
||||
.attr('class', 'match-group')
|
||||
.attr('data-match-id', match.id);
|
||||
|
||||
// Match background
|
||||
matchGroup.append('rect')
|
||||
.attr('x', roundX)
|
||||
.attr('y', matchY)
|
||||
.attr('width', matchWidth)
|
||||
.attr('height', matchHeight)
|
||||
.attr('class', 'match-bg')
|
||||
.style('fill', '#fafafa')
|
||||
.style('stroke', '#ddd')
|
||||
.style('stroke-width', 1)
|
||||
.style('rx', 6);
|
||||
|
||||
// Participant 1
|
||||
const p1Group = matchGroup.append('g')
|
||||
.attr('class', 'participant-group')
|
||||
.attr('transform', `translate(${roundX + 10}, ${matchY + 20})`);
|
||||
|
||||
p1Group.append('rect')
|
||||
.attr('width', matchWidth - 20)
|
||||
.attr('height', 30)
|
||||
.style('fill', match.winner && match.participant1 && match.winner.id === match.participant1.id ? '#e8f5e8' : '#fff')
|
||||
.style('stroke', '#ccc')
|
||||
.style('rx', 3);
|
||||
|
||||
p1Group.append('text')
|
||||
.attr('x', 8)
|
||||
.attr('y', 20)
|
||||
.style('font-size', '12px')
|
||||
.style('font-weight', match.winner && match.participant1 && match.winner.id === match.participant1.id ? 'bold' : 'normal')
|
||||
.style('fill', match.winner && match.participant1 && match.winner.id === match.participant1.id ? '#2a7f2a' : '#333')
|
||||
.text(match.participant1 ? match.participant1.name : '轮空');
|
||||
|
||||
// Score input for participant 1
|
||||
if (match.participant1 && match.participant2 && !match.decided) {
|
||||
const scoreInput1 = p1Group.append('foreignObject')
|
||||
.attr('x', matchWidth - 60)
|
||||
.attr('y', 5)
|
||||
.attr('width', 40)
|
||||
.attr('height', 20);
|
||||
|
||||
scoreInput1.append('xhtml:input')
|
||||
.attr('type', 'number')
|
||||
.attr('min', '0')
|
||||
.style('width', '35px')
|
||||
.style('height', '18px')
|
||||
.style('font-size', '11px')
|
||||
.style('text-align', 'center')
|
||||
.style('border', '1px solid #aaa')
|
||||
.style('border-radius', '3px')
|
||||
.property('value', match.score1 || '')
|
||||
.on('input', function() {
|
||||
match.score1 = +this.value;
|
||||
});
|
||||
} else if (match.decided) {
|
||||
p1Group.append('text')
|
||||
.attr('x', matchWidth - 35)
|
||||
.attr('y', 20)
|
||||
.style('font-size', '12px')
|
||||
.style('font-weight', 'bold')
|
||||
.style('text-anchor', 'middle')
|
||||
.text(match.score1 || 0);
|
||||
}
|
||||
|
||||
// Participant 2
|
||||
const p2Group = matchGroup.append('g')
|
||||
.attr('class', 'participant-group')
|
||||
.attr('transform', `translate(${roundX + 10}, ${matchY + 55})`);
|
||||
|
||||
p2Group.append('rect')
|
||||
.attr('width', matchWidth - 20)
|
||||
.attr('height', 30)
|
||||
.style('fill', match.winner && match.participant2 && match.winner.id === match.participant2.id ? '#e8f5e8' : '#fff')
|
||||
.style('stroke', '#ccc')
|
||||
.style('rx', 3);
|
||||
|
||||
p2Group.append('text')
|
||||
.attr('x', 8)
|
||||
.attr('y', 20)
|
||||
.style('font-size', '12px')
|
||||
.style('font-weight', match.winner && match.participant2 && match.winner.id === match.participant2.id ? 'bold' : 'normal')
|
||||
.style('fill', match.winner && match.participant2 && match.winner.id === match.participant2.id ? '#2a7f2a' : '#333')
|
||||
.text(match.participant2 ? match.participant2.name : '轮空');
|
||||
|
||||
// Score input for participant 2
|
||||
if (match.participant1 && match.participant2 && !match.decided) {
|
||||
const scoreInput2 = p2Group.append('foreignObject')
|
||||
.attr('x', matchWidth - 60)
|
||||
.attr('y', 5)
|
||||
.attr('width', 40)
|
||||
.attr('height', 20);
|
||||
|
||||
scoreInput2.append('xhtml:input')
|
||||
.attr('type', 'number')
|
||||
.attr('min', '0')
|
||||
.style('width', '35px')
|
||||
.style('height', '18px')
|
||||
.style('font-size', '11px')
|
||||
.style('text-align', 'center')
|
||||
.style('border', '1px solid #aaa')
|
||||
.style('border-radius', '3px')
|
||||
.property('value', match.score2 || '')
|
||||
.on('input', function() {
|
||||
match.score2 = +this.value;
|
||||
});
|
||||
} else if (match.decided) {
|
||||
p2Group.append('text')
|
||||
.attr('x', matchWidth - 35)
|
||||
.attr('y', 20)
|
||||
.style('font-size', '12px')
|
||||
.style('font-weight', 'bold')
|
||||
.style('text-anchor', 'middle')
|
||||
.text(match.score2 || 0);
|
||||
}
|
||||
|
||||
// Confirm score button
|
||||
if (match.participant1 && match.participant2 && !match.decided) {
|
||||
const buttonGroup = matchGroup.append('g')
|
||||
.attr('transform', `translate(${roundX + 10}, ${matchY + 90})`);
|
||||
|
||||
const button = buttonGroup.append('rect')
|
||||
.attr('width', matchWidth - 20)
|
||||
.attr('height', 25)
|
||||
.style('fill', '#007acc')
|
||||
.style('stroke', 'none')
|
||||
.style('rx', 4)
|
||||
.style('cursor', 'pointer')
|
||||
.on('click', () => confirmScore(match));
|
||||
|
||||
buttonGroup.append('text')
|
||||
.attr('x', (matchWidth - 20) / 2)
|
||||
.attr('y', 17)
|
||||
.style('fill', 'white')
|
||||
.style('font-size', '12px')
|
||||
.style('font-weight', 'bold')
|
||||
.style('text-anchor', 'middle')
|
||||
.style('pointer-events', 'none')
|
||||
.text('确认比分');
|
||||
}
|
||||
|
||||
// Store match position for connection drawing
|
||||
match._x = roundX + matchWidth;
|
||||
match._y = matchY + matchHeight / 2;
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('drawConnections error:', e);
|
||||
}
|
||||
|
||||
// Draw connections between matches
|
||||
drawD3Connections(g, matchWidth, matchHeight, roundGap, matchGap);
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
const bracketLinesSVG = document.getElementById('bracketLines');
|
||||
if (bracketLinesSVG) bracketLinesSVG.innerHTML = '';
|
||||
});
|
||||
const drawD3Connections = (g, matchWidth, matchHeight, roundGap, matchGap) => {
|
||||
const rounds = tournament.value.rounds;
|
||||
|
||||
for (let round = 1; round < rounds; round++) {
|
||||
const currentRoundMatches = tournament.value.matches.filter(m => m.round === round && m.decided);
|
||||
|
||||
currentRoundMatches.forEach(match => {
|
||||
const nextRound = round + 1;
|
||||
const nextMatchNumber = Math.floor((match.matchNumber - 1) / 2) + 1;
|
||||
const nextMatch = tournament.value.matches.find(m => m.round === nextRound && m.matchNumber === nextMatchNumber);
|
||||
|
||||
if (nextMatch && match.winner) {
|
||||
const startX = match._x;
|
||||
const startY = match._y;
|
||||
const endX = nextMatch._x - matchWidth;
|
||||
const endY = nextMatch._y;
|
||||
|
||||
const midX = startX + roundGap / 2;
|
||||
|
||||
// Draw connection line
|
||||
g.append('path')
|
||||
.attr('d', `M${startX},${startY} L${midX},${startY} L${midX},${endY} L${endX},${endY}`)
|
||||
.style('stroke', '#4CAF50')
|
||||
.style('stroke-width', 2)
|
||||
.style('fill', 'none')
|
||||
.style('opacity', 0.8);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const emit = defineEmits(['refreshPlayers']);
|
||||
|
||||
@ -318,8 +512,8 @@ async function confirmScore(match) {
|
||||
match.winner = s1 > s2 ? match.participant1 : match.participant2;
|
||||
match.decided = true;
|
||||
updateNextRound(match);
|
||||
// 重新绘制连线
|
||||
nextTick(() => drawConnections());
|
||||
// 重新绘制D3树状图
|
||||
nextTick(() => drawD3Bracket());
|
||||
if (match.round === tournament.value.rounds) {
|
||||
calculateFinalRanking();
|
||||
}
|
||||
@ -357,8 +551,8 @@ async function confirmScore(match) {
|
||||
};
|
||||
await updateSignUpResult(p1.id, p1Data);
|
||||
await updateSignUpResult(p2.id, p2Data);
|
||||
// 刷新报名数据和赛程
|
||||
await handleTournamentChange();
|
||||
// 刷新比赛数据
|
||||
await loadTournamentData();
|
||||
// 强制同步 participants 和 tournament.value.participants
|
||||
participants.value = [...participants.value];
|
||||
tournament.value.participants = [...participants.value];
|
||||
@ -464,10 +658,43 @@ const calculateFinalRanking = () => {
|
||||
};
|
||||
};
|
||||
|
||||
// 监听赛程变化自动重绘连线
|
||||
// Zoom control functions
|
||||
const zoomIn = () => {
|
||||
if (svgSelection && zoomBehavior) {
|
||||
svgSelection.transition().duration(300).call(
|
||||
zoomBehavior.scaleBy, 1.5
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const zoomOut = () => {
|
||||
if (svgSelection && zoomBehavior) {
|
||||
svgSelection.transition().duration(300).call(
|
||||
zoomBehavior.scaleBy, 1 / 1.5
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const resetZoom = () => {
|
||||
if (svgSelection && zoomBehavior) {
|
||||
svgSelection.transition().duration(500).call(
|
||||
zoomBehavior.transform,
|
||||
d3.zoomIdentity
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 监听赛程变化自动重绘D3树状图
|
||||
watch(roundsMatches, () => {
|
||||
nextTick(() => drawConnections());
|
||||
nextTick(() => drawD3Bracket());
|
||||
});
|
||||
|
||||
// 监听tournamentId变化自动加载新的比赛数据
|
||||
watch(() => props.tournamentId, (newId) => {
|
||||
if (newId) {
|
||||
loadTournamentData();
|
||||
}
|
||||
}, { immediate: false });
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@ -530,7 +757,7 @@ watch(roundsMatches, () => {
|
||||
.tournament-bracket-root .bracket-container {
|
||||
flex: 3;
|
||||
position: relative;
|
||||
overflow-x: auto;
|
||||
overflow: auto;
|
||||
background: #fff;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
@ -541,76 +768,73 @@ watch(roundsMatches, () => {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.tournament-bracket-root .bracket {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: 220px;
|
||||
gap: 40px 10px;
|
||||
position: relative;
|
||||
padding-bottom: 50px;
|
||||
min-width: 600px;
|
||||
min-height: 350px;
|
||||
}
|
||||
|
||||
.tournament-bracket-root .round {
|
||||
display: grid;
|
||||
grid-auto-rows: 70px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.tournament-bracket-root .round-title {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.tournament-bracket-root .match {
|
||||
background: #fafafa;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 0.1);
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tournament-bracket-root .participant {
|
||||
.tournament-bracket-root .zoom-controls {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6px;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
z-index: 10;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.tournament-bracket-root .participant.winner {
|
||||
font-weight: bold;
|
||||
color: #2a7f2a;
|
||||
}
|
||||
|
||||
.tournament-bracket-root .score-input {
|
||||
width: 40px;
|
||||
font-size: 14px;
|
||||
padding: 2px 4px;
|
||||
margin-left: 6px;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.tournament-bracket-root .score-btn {
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
.tournament-bracket-root .zoom-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: #007acc;
|
||||
border: none;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
padding: 6px 0;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.tournament-bracket-root .score-btn:disabled {
|
||||
background: #aaa;
|
||||
cursor: not-allowed;
|
||||
.tournament-bracket-root .zoom-btn:hover {
|
||||
background: #005a9e;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.tournament-bracket-root .zoom-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.tournament-bracket-root .interaction-hint {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
left: 15px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
z-index: 5;
|
||||
user-select: none;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.tournament-bracket-root .interaction-hint:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tournament-bracket-root .d3-bracket {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.tournament-bracket-root .d3-bracket:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.tournament-bracket-root #finalRanking {
|
||||
@ -621,21 +845,6 @@ watch(roundsMatches, () => {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.tournament-bracket-root svg.bracket-lines {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
overflow: visible;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.tournament-bracket-root svg.bracket-lines path {
|
||||
stroke: #666;
|
||||
fill: none;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.tournament-bracket-root .tournament-info {
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
@ -647,12 +856,4 @@ watch(roundsMatches, () => {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tournament-bracket-root select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
</style>
|
@ -57,37 +57,37 @@ const routes = [
|
||||
component: () => import('@/views/index/ConfigEditor.vue'),
|
||||
meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-mod'] }
|
||||
},
|
||||
// {
|
||||
// path: 'competition',
|
||||
// name: 'Competition',
|
||||
// component: () => import('@/views/index/Competition.vue'),
|
||||
// // meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-competitor'] }
|
||||
// meta: { requiresAuth: true}
|
||||
// },
|
||||
// {
|
||||
// path: 'competition/add',
|
||||
// name: 'AddCompetition',
|
||||
// component: () => import('@/views/index/AddContestant.vue'),
|
||||
// meta: { requiresAuth: true }
|
||||
// },
|
||||
// {
|
||||
// path: 'competition/detail',
|
||||
// name: 'CompetitionDetail',
|
||||
// component: () => import('@/views/index/CompetitionDetail.vue'),
|
||||
// meta: { requiresAuth: true }
|
||||
// },
|
||||
// {
|
||||
// path: 'competition/signup',
|
||||
// name: 'CompetitionSignUp',
|
||||
// component: () => import('@/views/index/CompetitionSignUp.vue'),
|
||||
// meta: { requiresAuth: true }
|
||||
// },
|
||||
{
|
||||
path: 'competition',
|
||||
name: 'Competition',
|
||||
component: () => import('@/views/competition/Competition.vue'),
|
||||
component: () => import('@/views/index/Competition.vue'),
|
||||
// meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-competitor'] }
|
||||
meta: { requiresAuth: true}
|
||||
},
|
||||
{
|
||||
path: 'competition/add',
|
||||
name: 'AddCompetition',
|
||||
component: () => import('@/views/index/AddContestant.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: 'competition/detail',
|
||||
name: 'CompetitionDetail',
|
||||
component: () => import('@/views/index/CompetitionDetail.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: 'competition/signup',
|
||||
name: 'CompetitionSignUp',
|
||||
component: () => import('@/views/index/CompetitionSignUp.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
// {
|
||||
// path: 'competition',
|
||||
// name: 'Competition',
|
||||
// component: () => import('@/views/competition/Competition.vue'),
|
||||
// meta: { requiresAuth: true}
|
||||
// },
|
||||
{
|
||||
path: 'editors-maps',
|
||||
name: 'EditorsMaps',
|
||||
|
@ -1,11 +1,271 @@
|
||||
<script setup lang="ts">
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import TournamentBracket from '@/components/TournamentBracket.vue';
|
||||
|
||||
// 比赛状态
|
||||
const activeTab = ref('upcoming'); // upcoming, ongoing, completed
|
||||
const tournaments = ref([]);
|
||||
const loading = ref(false);
|
||||
|
||||
// 当前选中的比赛ID
|
||||
const selectedTournamentId = ref(null);
|
||||
|
||||
// 页面加载时获取比赛列表
|
||||
onMounted(() => {
|
||||
fetchTournaments();
|
||||
});
|
||||
|
||||
// 获取比赛列表
|
||||
const fetchTournaments = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 这里应该调用API获取比赛列表
|
||||
// 模拟数据
|
||||
tournaments.value = [
|
||||
{ id: 1, name: '2023年夏季赛', status: 'completed', startDate: '2023-06-01', endDate: '2023-08-30' },
|
||||
{ id: 2, name: '2023年秋季赛', status: 'ongoing', startDate: '2023-09-01', endDate: '2023-11-30' },
|
||||
{ id: 3, name: '2024年春季赛', status: 'upcoming', startDate: '2024-03-01', endDate: '2024-05-30' }
|
||||
];
|
||||
} catch (error) {
|
||||
console.error('获取比赛列表失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 选择比赛
|
||||
const selectTournament = (tournamentId) => {
|
||||
selectedTournamentId.value = tournamentId;
|
||||
};
|
||||
|
||||
// 刷新参赛者列表
|
||||
const refreshParticipants = () => {
|
||||
// 这里可以添加刷新逻辑
|
||||
console.log('刷新参赛者列表');
|
||||
};
|
||||
|
||||
// 过滤比赛列表
|
||||
const filteredTournaments = () => {
|
||||
return tournaments.value.filter(tournament => {
|
||||
if (activeTab.value === 'upcoming') return tournament.status === 'upcoming';
|
||||
if (activeTab.value === 'ongoing') return tournament.status === 'ongoing';
|
||||
if (activeTab.value === 'completed') return tournament.status === 'completed';
|
||||
return true;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="competition-container">
|
||||
<h1 class="page-title">比赛中心</h1>
|
||||
|
||||
<!-- 比赛类型选项卡 -->
|
||||
<div class="tabs">
|
||||
<div
|
||||
class="tab"
|
||||
:class="{ active: activeTab === 'upcoming' }"
|
||||
@click="activeTab = 'upcoming'"
|
||||
>
|
||||
即将开始
|
||||
</div>
|
||||
<div
|
||||
class="tab"
|
||||
:class="{ active: activeTab === 'ongoing' }"
|
||||
@click="activeTab = 'ongoing'"
|
||||
>
|
||||
正在进行
|
||||
</div>
|
||||
<div
|
||||
class="tab"
|
||||
:class="{ active: activeTab === 'completed' }"
|
||||
@click="activeTab = 'completed'"
|
||||
>
|
||||
已结束
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 比赛列表 -->
|
||||
<div class="tournament-list">
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
<div v-else-if="filteredTournaments().length === 0" class="no-data">
|
||||
暂无{{ activeTab === 'upcoming' ? '即将开始' : activeTab === 'ongoing' ? '正在进行' : '已结束' }}的比赛
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
v-for="tournament in filteredTournaments()"
|
||||
:key="tournament.id"
|
||||
class="tournament-card"
|
||||
:class="{ active: selectedTournamentId === tournament.id }"
|
||||
@click="selectTournament(tournament.id)"
|
||||
>
|
||||
<div class="tournament-header">
|
||||
<h3>{{ tournament.name }}</h3>
|
||||
<span class="status-badge" :class="tournament.status">
|
||||
{{ tournament.status === 'upcoming' ? '即将开始' :
|
||||
tournament.status === 'ongoing' ? '正在进行' : '已结束' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="tournament-dates">
|
||||
<span>开始日期: {{ tournament.startDate }}</span>
|
||||
<span>结束日期: {{ tournament.endDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 比赛详情 -->
|
||||
<div v-if="selectedTournamentId" class="tournament-detail">
|
||||
<TournamentBracket
|
||||
:tournamentId="selectedTournamentId"
|
||||
@refreshPlayers="refreshParticipants"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="select-prompt">
|
||||
请从左侧选择一个比赛查看详情
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.competition-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
color: #007acc;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: #007acc;
|
||||
border-bottom: 2px solid #007acc;
|
||||
}
|
||||
|
||||
.tournament-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.tournament-card {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.tournament-card:hover {
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.tournament-card.active {
|
||||
border-color: #007acc;
|
||||
box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.2);
|
||||
}
|
||||
|
||||
.tournament-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tournament-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.upcoming {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.status-badge.ongoing {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-badge.completed {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tournament-dates {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.loading, .no-data, .select-prompt {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
background: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tournament-detail {
|
||||
margin-top: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.competition-container {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.page-title, .tabs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tournament-list {
|
||||
width: 30%;
|
||||
margin-right: 2%;
|
||||
}
|
||||
|
||||
.tournament-detail, .select-prompt {
|
||||
width: 68%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -75,7 +75,10 @@
|
||||
v-if="competition.status === 'finish'"
|
||||
:tournament-id="parseInt(route.query.id)"
|
||||
/>
|
||||
<DoubleEliminationBracket v-if="competition.format === '双败淘汰' && competition.status === 'starting'" />
|
||||
<DoubleEliminationBracket
|
||||
v-if="competition.format === '双败淘汰' && competition.status === 'starting'"
|
||||
:tournament-id="parseInt(route.query.id)"
|
||||
/>
|
||||
<tournament-bracket
|
||||
v-else-if="competition.status === 'starting'"
|
||||
:tournament-id="parseInt(route.query.id)"
|
||||
|
@ -150,34 +150,34 @@
|
||||
|
||||
<!-- 右侧边栏 -->
|
||||
<div class="sidebar">
|
||||
<!-- <!– 赛事信息 –>-->
|
||||
<!-- <div class="section-card">-->
|
||||
<!-- <div class="section-header">-->
|
||||
<!-- <h3>-->
|
||||
<!-- <i class="fas fa-trophy"></i>-->
|
||||
<!-- 最新赛事-->
|
||||
<!-- </h3>-->
|
||||
<!-- <router-link to="/competition" class="more-link">查看全部</router-link>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="competitions-list">-->
|
||||
<!-- <div-->
|
||||
<!-- v-for="competition in latestCompetitions"-->
|
||||
<!-- :key="competition.id"-->
|
||||
<!-- class="competition-item"-->
|
||||
<!-- @click="viewCompetition(competition.id)"-->
|
||||
<!-- >-->
|
||||
<!-- <div class="competition-status" :class="competition.status">-->
|
||||
<!-- {{ getStatusText(competition.status) }}-->
|
||||
<!-- </div>-->
|
||||
<!-- <h4>{{ competition.name }}</h4>-->
|
||||
<!-- <div class="competition-time">-->
|
||||
<!-- <i class="fas fa-calendar"></i>-->
|
||||
<!-- {{ formatDate(competition.start_time) }}-->
|
||||
<!-- </div>-->
|
||||
<!-- <p>主办方:{{ competition.organizer }}</p>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- 赛事信息 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h3>
|
||||
<i class="fas fa-trophy"></i>
|
||||
最新赛事
|
||||
</h3>
|
||||
<router-link to="/competition" class="more-link">查看全部</router-link>
|
||||
</div>
|
||||
<div class="competitions-list">
|
||||
<div
|
||||
v-for="competition in latestCompetitions"
|
||||
:key="competition.id"
|
||||
class="competition-item"
|
||||
@click="viewCompetition(competition.id)"
|
||||
>
|
||||
<div class="competition-status" :class="competition.status">
|
||||
{{ getStatusText(competition.status) }}
|
||||
</div>
|
||||
<h4>{{ competition.name }}</h4>
|
||||
<div class="competition-time">
|
||||
<i class="fas fa-calendar"></i>
|
||||
{{ formatDate(competition.start_time) }}
|
||||
</div>
|
||||
<p>主办方:{{ competition.organizer }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 办事大厅 -->
|
||||
<div class="section-card">
|
||||
|
Loading…
x
Reference in New Issue
Block a user