Compare commits

...

27 Commits

Author SHA1 Message Date
d6432ceb69 单败重构 2025-08-05 21:34:19 +08:00
a72c6fcd2c 单败重构 2025-08-05 17:10:06 +08:00
afd5323ade 单败重构 2025-08-03 13:53:54 +08:00
f5bf2f01f5 单败重构 2025-08-01 20:37:02 +08:00
292d913305 单败重构 2025-08-01 02:05:23 +08:00
5aabaddc31 单败重构 2025-08-01 01:29:18 +08:00
47f97cd291 重构赛事信息(修改树状图前) 2025-07-31 00:59:05 +08:00
605d60ec7e 重构赛事信息(修改树状图前) 2025-07-31 00:17:22 +08:00
0274eb5407 重构赛事的树状图 2025-07-29 16:38:07 +08:00
2762a8b5f1 重构赛事的树状图 2025-07-29 16:37:03 +08:00
1977b791e5 重构赛事的树状图 2025-07-29 16:15:54 +08:00
874e2fc4dc 赛事定到@/views/competition/Competition.vue了 2025-07-25 23:01:43 +08:00
1175524448 重置密码的60s冷却🐱🐱 2025-07-25 22:55:01 +08:00
073321ed7a 重置密码的60s冷却🐱🐱 2025-07-25 22:37:05 +08:00
72c6bda35a 重置密码的60s冷却🐱🐱 2025-07-25 22:36:56 +08:00
4828d0bcd6 重置密码的60s冷却🐱🐱 2025-07-25 19:31:28 +08:00
4c15c42ebc 重置密码的60s冷却🐱🐱 2025-07-25 00:58:00 +08:00
0537bdb86e 重置密码的60s冷却🐱🐱 2025-07-24 21:53:02 +08:00
79f1daf525 首页🐱🐱 2025-07-24 21:19:29 +08:00
e4b562ed86 首页🐱🐱 2025-07-24 21:19:23 +08:00
b588915cb2 首页🐱🐱 2025-07-24 21:17:17 +08:00
2a85a09bc8 修改备案号🐱🐱 2025-07-22 13:26:42 +08:00
c6632b124c 修改备案号🐱🐱 2025-07-22 10:16:50 +08:00
bf3b49e72b 修改密码之后扬了登陆 2025-07-19 14:40:27 +08:00
d44a793019 修改密码之后扬了登陆 2025-07-19 14:23:20 +08:00
c3ccf8d73d Merge remote-tracking branch 'origin/feature/login-screen' into feature/login-screen 2025-07-19 13:40:04 +08:00
04bf94df0c 添加了Config.xml 编辑器 2025-07-19 13:39:02 +08:00
34 changed files with 18753 additions and 947 deletions

420
node_modules/.package-lock.json generated vendored
View File

@ -930,6 +930,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",
@ -986,6 +994,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",
@ -1043,6 +1421,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",
@ -1440,6 +1826,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",
@ -1450,6 +1847,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",
@ -1893,6 +2298,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",
@ -1944,11 +2354,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",

View File

@ -1,43 +1,49 @@
{
"hash": "3498b3cb",
"configHash": "9c7a641a",
"lockfileHash": "c997fc3c",
"browserHash": "38ceb684",
"hash": "caa31cd8",
"configHash": "8bd3ff55",
"lockfileHash": "296e7a14",
"browserHash": "748422e9",
"optimized": {
"axios": {
"src": "../../axios/index.js",
"file": "axios.js",
"fileHash": "97de2fb4",
"fileHash": "17dc0739",
"needsInterop": false
},
"jszip": {
"src": "../../jszip/dist/jszip.min.js",
"file": "jszip.js",
"fileHash": "3cd8a10a",
"fileHash": "73a56b11",
"needsInterop": true
},
"mitt": {
"src": "../../mitt/dist/mitt.mjs",
"file": "mitt.js",
"fileHash": "72a77428",
"needsInterop": false
},
"vue": {
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "74f3ac3b",
"fileHash": "dd9d6977",
"needsInterop": false
},
"vue-router": {
"src": "../../vue-router/dist/vue-router.mjs",
"file": "vue-router.js",
"fileHash": "d2d7abce",
"fileHash": "1b493d5a",
"needsInterop": false
},
"xlsx": {
"src": "../../xlsx/xlsx.mjs",
"file": "xlsx.js",
"fileHash": "dbbdc859",
"fileHash": "e65d0089",
"needsInterop": false
},
"mitt": {
"src": "../../mitt/dist/mitt.mjs",
"file": "mitt.js",
"fileHash": "ac6eb5ab",
"d3": {
"src": "../../d3/src/index.js",
"file": "d3.js",
"fileHash": "89f27bc8",
"needsInterop": false
}
},

View File

@ -40,14 +40,6 @@ export {};
expose?: (exposed: T) => void,
}
};
type __VLS_NormalizeSlotReturns<S, R = NonNullable<S> extends (...args: any) => infer K ? K : any> = R extends any[] ? {
[K in keyof R]: R[K] extends infer V
? V extends Element ? V
: V extends new (...args: any) => infer R ? ReturnType<__VLS_FunctionalComponent<R>>
: V extends (...args: any) => infer R ? R
: any
: never
} : R;
type __VLS_IsFunction<T, K> = K extends keyof T
? __VLS_IsAny<T[K]> extends false
? unknown extends T[K]
@ -113,7 +105,7 @@ export {};
index: number,
][];
function __VLS_getSlotParameters<S, D extends S>(slot: S, decl?: D):
__VLS_PickNotAny<NonNullable<D>, (...args: any) => any> extends (...args: infer P) => any ? P : any[];
D extends (...args: infer P) => any ? P : any[];
function __VLS_asFunctionalDirective<T>(dir: T): T extends import('vue').ObjectDirective
? NonNullable<T['created' | 'beforeMount' | 'mounted' | 'beforeUpdate' | 'updated' | 'beforeUnmount' | 'unmounted']>
: T extends (...args: any) => any

421
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -0,0 +1,545 @@
<?xml version="1.0" encoding="utf-8"?>
<AssetDeclaration xmlns="uri:ea.com:eala:asset" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xai="uri:ea.com:eala:asset:instance">
<Tags></Tags>
<Includes>
<Include type="all" source="DATA:GlobalData/GlobalDefines.xml" />
<Include
type="all"
source="ART:AUAntiAirShip_D.xml" />
<Include
type="all"
source="ART:AUAntiAirShip_FX.w3x" />
<Include
type="all"
source="ART:AUAntiAirShip_FPZ.w3x" />
<Include
type="all"
source="ART:AUAntiAirShip_SKN.w3x" />
<Include
type="all"
source="ART:FXGradient01.xml" />
<Include
type="all"
source="ART:FXTracer.xml" />
<Include
type="all"
source="ART:FXTracerHeroic.xml" />
<!-- needed for temp laserEndParticleSystemFX? -->
<Include
type="all"
source="ART:SUAntiVehicleVehicleTech3_FX.w3x" />
<Include
type="instance"
source="ART:FXGrid_3.xml" />
<Include
type="instance"
source="ART:FXHarpoonBeam.xml" />
<!-- Base Object -->
<Include
type="instance"
source="DATA:BaseObjects/BaseVehicle.xml" />
</Includes>
<!-- aka The Hydrofoil -->
<GameObject
id="AlliedAntiAirShip"
inheritFrom="BaseVehicle"
SelectPortrait="Portrait_AlliedAntiAirShip"
ButtonImage="Button_AlliedAntiAirShip_on"
Side="Allies"
SubGroupPriority="440"
EditorSorting="UNIT"
HealthBoxHeightOffset="30"
TransportSlotCount="10"
BuildTime="10"
CommandSet="AlliedAntiAirShipCommandSet"
KindOf="SELECTABLE CAN_ATTACK CAN_CAST_REFLECTIONS SCORE VEHICLE SHIP CAN_BE_FAVORITE_UNIT"
RadarPriority="UNIT"
ProductionQueueType="WATERCRAFT"
UnitCategory="VEHICLE"
WeaponCategory="CANNON"
VoicePriority="188"
EditorName="AlliedAntiAirShip"
Description="Desc:AlliedAntiAirShip"
TypeDescription="Type:AlliedAntiAirShip"
UnitIntro="Allied_Hydrofoil_UnitIntro">
<DisplayName
xai:joinAction="Replace" xmlns:xai="uri:ea.com:eala:asset:instance">Name:AlliedAntiAirShip</DisplayName>
<ObjectResourceInfo>
<BuildCost Account="=$ACCOUNT_ORE" Amount="900"/>
</ObjectResourceInfo>
<ArmorSet
Armor="AlliedAntiAirShipArmor"
DamageFX="VehicleDamageFX" />
<LocomotorSet
id="DefaultWaterLocomotorSet"
Locomotor="AlliedAntiAirShipWaterLocomotor"
Condition="NORMAL"
Speed="125.0" />
<LocomotorSet
Locomotor="AlliedAntiAirShipWaterLocomotor_LeavingFactory"
Condition="EXITING_PRODUCTION_STRUCTURE"
Speed="125.0" />
<SkirmishAIInformation
UnitBuilderStandardCombatUnit="true" />
<Draws>
<ScriptedModelDraw
id="ModuleTag_Draw"
OkToChangeModelColor="true"
InitialRecoilSpeed="0.1"
MaxRecoilDistance="0.1"
RecoilDamping="2.0"
RecoilSettleSpeed="3.0"
ExtraPublicBone="FX_Weapon_01 FX_Weapon_02" >
<ModelConditionState
ParseCondStateType="PARSE_DEFAULT"
RetainSubObjects="true">
<Model
Name="AUAntiAirShip_SKN" />
<WeaponFireFXBone
WeaponSlotID="1"
WeaponSlotType="PRIMARY_WEAPON"
BoneName="FX_Weapon_01" />
<WeaponRecoilBone
WeaponSlotID="1"
WeaponSlotType="PRIMARY_WEAPON"
BoneName="FX_Weapon_01" />
<WeaponLaunchBone
WeaponSlotID="1"
WeaponSlotType="PRIMARY_WEAPON"
BoneName="FX_Weapon_01" />
<WeaponFireFXBone
WeaponSlotID="1"
WeaponSlotType="SECONDARY_WEAPON"
BoneName="FX_Weapon_02" />
<WeaponRecoilBone
WeaponSlotID="1"
WeaponSlotType="SECONDARY_WEAPON"
BoneName="FX_Weapon_02" />
<WeaponLaunchBone
WeaponSlotID="1"
WeaponSlotType="SECONDARY_WEAPON"
BoneName="FX_Weapon_02" />
<Turret
TurretNameKey="turret"
TurretPitch="Turret_Pitch"
TurretID="1" />
</ModelConditionState>
<ModelConditionState
ParseCondStateType="PARSE_NORMAL"
ConditionsYes="FORMATION_PREVIEW">
<Model
Name="AUAntiAirShip_SKN" />
<Material
ShaderName="FX_FormPreview.fx"
TechniqueName="Default">
<Constants>
<Texture Name="SpecMap">
<Value>FXGradient01</Value>
</Texture>
</Constants>
</Material>
</ModelConditionState>
<ModelConditionState
ParseCondStateType="PARSE_NORMAL"
RetainSubObjects="true"
ConditionsYes="REALLYDAMAGED">
<Model
Name="AUAntiAirShip_SKN" />
<Texture
Original="AUAntiAirShip"
New="AUAntiAirShip_D" />
</ModelConditionState>
<AnimationState
ParseCondStateType="PARSE_DEFAULT">
<Script>
CurDrawableShowSubObjectPermanently("GUN")
CurDrawableHideSubObjectPermanently("BEAM")
</Script>
<ParticleSysBone
BoneName="None"
FXParticleSystemTemplate="SmallShipWakeIdle"
FollowBone="false" />
</AnimationState>
<AnimationState
ParseCondStateType="PARSE_NORMAL"
ConditionsYes="MOVING">
<ParticleSysBone
BoneName="NONE"
FXParticleSystemTemplate="AUHydrofoilWaterWake"
FollowBone="false" />
</AnimationState>
<AnimationState
ParseCondStateType="PARSE_NORMAL"
ConditionsYes="WEAPONSTATE_TWO">
<Script>
CurDrawableHideSubObjectPermanently("GUN")
CurDrawableShowSubObjectPermanently("BEAM")
</Script>
</AnimationState>
</ScriptedModelDraw>
<ScriptedModelDraw
id="ModuleTag_DrawZ"
OkToChangeModelColor="true"
InitialRecoilSpeed="0.1"
MaxRecoilDistance="0.1"
RecoilDamping="2.0"
RecoilSettleSpeed="3.0"
ExtraPublicBone="FX_Weapon_01 FX_Weapon_02">
<ModelConditionState
ParseCondStateType="PARSE_DEFAULT"
RetainSubObjects="true">
<Model
Name="" />
</ModelConditionState>
<ModelConditionState
ParseCondStateType="PARSE_NORMAL"
ConditionsYes="FORMATION_PREVIEW">
<Model
Name="AUAntiAirShip_FPZ" />
</ModelConditionState>
<AnimationState
ParseCondStateType="PARSE_DEFAULT">
<Script>
CurDrawableShowSubObjectPermanently("GUN")
CurDrawableHideSubObjectPermanently("BEAM")
</Script>
</AnimationState>
</ScriptedModelDraw>
<!-- Used in the Weapon Scramble Beam -->
<LaserDraw
id="ModuleTag_LaserDraw"
Texture1_UTile="1"
Texture1_VTile="1"
Texture1_UScrollRate="0"
Texture1_VScrollRate="0"
Texture1_NumFrames="1"
Texture1_FrameRate="30"
Texture2_UTile="1"
Texture2_VTile="1"
Texture2_UScrollRate="0"
Texture2_VScrollRate="1"
Texture2_NumFrames="1"
Texture2_FrameRate="30"
LaserWidth="40"
LaserStateID="1">
<FXShader
ShaderName="Laser.fx"
TechniqueIndex="0">
<Constants>
<Texture
Name="Texture1">
<Value>FXGrid_3</Value>
</Texture>
<Texture
Name="Texture2">
<Value>FXInterlacedMask2</Value>
</Texture>
<Float Name="ColorEmissive">
<Value>0.00000</Value>
<Value>2.00000</Value>
<Value>1.000000</Value>
</Float>
</Constants>
</FXShader>
</LaserDraw>
<!-- Used for the Phalanx Gun -->
<TracerModelDraw
id="ModuleTag_TracerModelDraw"
MinLength="10.0"
MaxLength="25.0"
Width="15.0"
MinSpeed="22"
MaxSpeed="32"
SweepSpeed="3.0"
SpreadAngle="5.0"
MinTracersPerFrame="0.4"
MaxTracersPerFrame="0.4"
FrameLifeTime="25"
WeaponSlotType="PRIMARY_WEAPON"
Texture="FXTracer"
UseAdditiveBlending="true" >
<HeadColor
r="1.0"
g="1.0"
b="1.0"
a="1.0" />
<TailColor
r="1.0"
g="0.75"
b="0.65"
a="0.0" />
<ObjectStatusValidation
ForbiddenStatus="WEAPON_UPGRADED_01 GENERIC_TOGGLE_STATE" />
</TracerModelDraw>
<TracerModelDraw
id="ModuleTag_TracerModelDrawVeterancy"
MinLength="10.0"
MaxLength="25.0"
Width="15.0"
MinSpeed="30"
MaxSpeed="30"
SweepSpeed="0.5"
SpreadAngle="0.5"
MinTracersPerFrame="0.4"
MaxTracersPerFrame="0.4"
FrameLifeTime="35"
WeaponSlotType="PRIMARY_WEAPON"
Texture="FXTracerHeroic"
UseAdditiveBlending="true" >
<HeadColor
r="1.0"
g="0.0"
b="0.0"
a="1.0" />
<TailColor
r="1.0"
g="0.75"
b="0.65"
a="0.0" />
<ObjectStatusValidation
RequiredStatus="WEAPON_UPGRADED_01"
ForbiddenStatus="GENERIC_TOGGLE_STATE" />
</TracerModelDraw>
<!-- DRAW PARTICLES -->
<ScriptedModelDraw
id="ModuleTag_Draw_FX"
OkToChangeModelColor="true">
<ModelConditionState
ParseCondStateType="PARSE_DEFAULT">
<Model
Name="AUAntiAirShip_FX" />
</ModelConditionState>
<ModelConditionState
ParseCondStateType="PARSE_NORMAL"
ConditionsYes="DAMAGED">
<Model
Name="AUAntiAirShip_FX" />
<ParticleSysBone
BoneName="FX_BONE01"
FXParticleSystemTemplate="VehicleDamageSmoke"
FollowBone="true" />
</ModelConditionState>
<ModelConditionState
ParseCondStateType="PARSE_NORMAL"
ConditionsYes="REALLYDAMAGED">
<Model
Name="AUAntiAirShip_FX" />
<ParticleSysBone
BoneName="FX_BONE01"
FXParticleSystemTemplate="VehicleDamageSmoke"
FollowBone="true" />
<ParticleSysBone
BoneName="FX_BONE01"
FXParticleSystemTemplate="VehicleDamageFire"
FollowBone="true" />
<ParticleSysBone
BoneName="FX_BONE01"
FXParticleSystemTemplate="VehicleDamageFire02"
FollowBone="true" />
</ModelConditionState>
</ScriptedModelDraw>
</Draws>
<Behaviors>
<WeaponSetUpdate
id="ModuleTag_WeaponSetUpdate">
<WeaponSlotTurret
ID="1">
<!-- This weapon is always around, but the weapon template itself prevents it
from being able to be fired once it's upgraded. -->
<Weapon
Ordering="PRIMARY_WEAPON"
Template="AlliedAntiAirShipPhalanxGun"
ForbiddenObjectStatus="GENERIC_TOGGLE_STATE"
/>
<Weapon
Ordering="SECONDARY_WEAPON"
Template="AlliedAntiAirShipWeaponScrambler"
ObjectStatus="GENERIC_TOGGLE_STATE"/>
<TurretSettings
TurretTurnRate="360"
MinimumPitch="0d"
AllowsPitch="true"
TurretPitchRate="180"
MinIdleScanTime="1.0s"
MaxIdleScanTime="5.0s"
MinIdleScanAngle="10.0"
MaxIdleScanAngle="90.0"
ComeToHaltJiggle="3d">
<TurretAITargetChooserData
IdleScanDelay="=$FAST_IDLE_SCAN_DELAY"
CanAcquireDynamicIfAssignedOutOfRange="true" />
</TurretSettings>
</WeaponSlotTurret>
</WeaponSetUpdate>
<Physics
id="ModuleTag_Physics" />
<CreateObjectDie
id="ModuleTag_CreateObjectDie"
CreationList="AUAntiAirShip_Die_OCL">
<DieMuxData
DeathTypes="ALL"
DeathTypesForbidden="FLOODED"/>
</CreateObjectDie>
<CreateObjectDie
id="ModuleTag_CreateObjectDieWhole"
CreationList="AUAntiAirShip_Die_OCL">
<DieMuxData
DeathTypes="FLOODED" />
</CreateObjectDie>
<DynamicsUpdate
id="ModuleTag_DefaultDynamicsUpdate"
xai:joinAction="Remove" />
<DestroyDie
id="ModuleTag_Die">
<DieMuxData
DeathTypes="ALL" />
</DestroyDie>
<FXListBehavior
id="ModuleTag_FXList">
<DieMuxData
DeathTypes="ALL" />
<Event
Index="onDeath"
FX="FX_ALL_HydrofoilDie" />
</FXListBehavior>
<!-- The toggle for the Weapon Scrambler -->
<SpecialPower
id="ModuleTag_ActivateWeaponScrambler"
SpecialPowerTemplate="SpecialPower_ToggleWeaponScrambler"
UpdateModuleStartsAttack="true" />
<ToggleStatusSpecialAbilityUpdate
id="ModuleTag_ActivateWeaponScramblerUpdate"
SpecialPowerTemplate="SpecialPower_ToggleWeaponScrambler"
Options="RECONSTITUTE_STORED_COMMAND">
<ToggleState
EnterStateSound="ALL_HydroFoil_ScramblerToggleOffMS">
<SkirmishAiInfo
ToggleHint="TOGGLE_DEFAULT">
<StateWeapon
Weapon="AlliedAntiAirShipPhalanxGun" />
</SkirmishAiInfo>
</ToggleState>
<ToggleState
ObjectStatus="GENERIC_TOGGLE_STATE"
ModelConditions="WEAPONSTATE_TWO"
EnterStateSound="ALL_HydroFoil_ScramblerToggleOnMS">
<SkirmishAiInfo
ToggleHint="TOGGLE_LOCKDOWN"
NeverUseInState="RETREAT">
<StateWeapon
Weapon="AlliedAntiAirShipWeaponScrambler" />
</SkirmishAiInfo>
</ToggleState>
</ToggleStatusSpecialAbilityUpdate>
<!-- The special power that is used by the weapon -->
<SpecialPower
id="ModuleTag_WeaponScrambler"
SpecialPowerTemplate="SpecialPower_WeaponScrambler"
TriggerFX="FX_None"
UpdateModuleStartsAttack="false" />
<LaserState
id="ModuleTag_LaserState"
LaserId="1" >
<LaserEndParticleSystem>AlliedHydroScrambler_Sparks</LaserEndParticleSystem>
<LaserStartParticleSystem>AlliedHydroScrambler_Start</LaserStartParticleSystem>
</LaserState>
<StatusBitsUpgrade
id="ModuleTag_VeterancyUpgrade"
StatusToSet="WEAPON_UPGRADED_01">
<TriggeredBy>Upgrade_Veterancy_HEROIC</TriggeredBy>
</StatusBitsUpgrade>
</Behaviors>
<AI>
<AIUpdate
id="ModuleTag_AI"
AutoAcquireEnemiesWhenIdle="YES"
StateMachine="UnitAIStateMachine">
<UnitAITargetChooserData
CanPickDynamicTargets="false"/>
</AIUpdate>
</AI>
<Body>
<ActiveBody
id="ModuleTag_02"
MaxHealth="400" />
</Body>
<ClientBehaviors>
<ModelConditionAudioLoopClientBehavior id="ModuleTag_ShrunkenVoice">
<ModelConditionSound Sound="ALL_Hydrofoil_VoiceShrunken" RequiredFlags="SHRINK_EFFECT" />
</ModelConditionAudioLoopClientBehavior>
<ModelConditionSoundSelectorClientBehavior id="ModuleTag_VoiceAttackWeaponJammer">
<Override RequiredFlags="WEAPONSTATE_TWO">
<AudioArrayVoice>
<AudioEntry Sound="ALL_Hydrofoil_VoiceAttackSpecial" AudioType="voiceAttack" />
<AudioEntry Sound="ALL_Hydrofoil_VoiceSelectMS" AudioType="voiceSelectBattle" />
</AudioArrayVoice>
</Override>
</ModelConditionSoundSelectorClientBehavior>
<ModelConditionAudioLoopClientBehavior xai:joinAction="Replace" xmlns:xai="uri:ea.com:eala:asset:instance" id="ModuleTag_MagneticSatelliteSuckedAway">
<ModelConditionSound Sound="SOV_MagneticSatellite_SuckedAwayWater" RequiredFlags="SUCKED_UP_HIGH" />
</ModelConditionAudioLoopClientBehavior>
</ClientBehaviors>
<Geometry>
<Shape
Type="BOX"
MajorRadius="30.0"
MinorRadius="18.0"
Height="20.0"
ContactPointGeneration="VEHICLE"/>
</Geometry>
<AudioArrayVoice>
<AudioEntry Sound="ALL_Hydrofoil_VoiceAttack" AudioType="voiceAttack" />
<AudioEntry Sound="ALL_Hydrofoil_VoiceMoveAttack" AudioType="voiceAttackAfterMoving" />
<AudioEntry Sound="ALL_Hydrofoil_VoiceCreate" AudioType="voiceCreated" />
<AudioEntry Sound="ALL_Hydrofoil_VoiceMove" AudioType="voiceMove" />
<AudioEntry Sound="ALL_Hydrofoil_VoiceRetreat" AudioType="voiceRetreatToCastle" />
<AudioEntry Sound="ALL_Hydrofoil_VoiceSelectMS" AudioType="voiceSelect" />
<AudioEntry Sound="ALL_Hydrofoil_VoiceSelectBattleMS" AudioType="voiceSelectBattle" />
<AudioEntry Sound="ALL_Hydrofoil_VoiceSelectUnderFireMS" AudioType="voiceSelectUnderFire" />
<!-- <NamedEntry Sound="ALL_Hydrofoil_VoiceAttackSpecial" Name="VoiceWeaponScrambler" /> oops plays on toggle -->
</AudioArrayVoice>
<AudioArraySound>
<AudioEntry
Sound="ALL_Hydrofoil_MoveStart"
AudioType="soundMoveStart" />
<AudioEntry
Sound="VehicleCrush"
AudioType="soundCrushing" />
<AudioEntry
Sound="Ship_Small_MoveLoopWater"
AudioType="soundMoveLoop" />
</AudioArraySound>
<VisionInfo
VisionRange="325"
ShroudClearingRange="=$STANDARD_SHROUD_CLEAR" />
<CrusherInfo
id="id_CrusherInfo"
CrusherLevel="1"
CrushableLevel="20" />
</GameObject>
</AssetDeclaration>

File diff suppressed because it is too large Load Diff

3253
public/Locomotor.xml Normal file

File diff suppressed because it is too large Load Diff

2291
public/LogicCommand.xml Normal file

File diff suppressed because it is too large Load Diff

1097
public/LogicCommandSet.xml Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

64
src/api/record.js Normal file
View File

@ -0,0 +1,64 @@
import axiosInstance from './axiosConfig';
/**
* 上传处理后的录像文件
* 路由: /record/upload
* 方法: POST
* 需要admin权限
* @param {file} file - 表单负载"file"上传
* @returns {id<int>} - HTTP_202_ACCEPTED 录像id
*/
export const uploadRecord = async (file) => {
try {
const formData = new FormData();
formData.append('file', file);
const response = await axiosInstance.post('/record/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
return response.data;
} catch (error) {
console.error(error);
throw error;
}
};
/**
* 获取录像解析状态
* 路由: /record/{id}
* 方法: GET
* 需要登录
* @param {int} id - 录像id
* @returns {id<int>} id - 录像id
* @returns {status<string>} status - 状态processing 处理中success 处理成功fail 处理失败
* @returns {data<json>} data - 录像数据仅当处理成功时有值
*/
export const getRecordStatus = async (id) => {
try {
const response = await axiosInstance.get(`/record/${id}`);
return response.data;
} catch (error) {
console.error(error);
throw error;
}
}
/**
* 获取单位信息
* 路由: /unit
* 方法: GET
* 三个参数仅使用一个即可如果传入多个优先选择上面的
* @param {Object} params - 参数 { id, code, name }
* @returns {id<string>} id
* @returns {code<string>} code
* @returns {name<string>} name
*/
export const unitInfo = async (params = {}) => {
try {
const response = await axiosInstance.get('/unit', { params });
return response.data;
} catch (error) {
console.error(error);
throw error;
}
}

View File

@ -3,239 +3,225 @@ import axiosInstance from './axiosConfig';
/**
* 添加赛事
* @param {Object} tournamentData - 赛事数据
* @param {string} tournamentData.name - 赛事名称
* @param {string} tournamentData.format - 赛事类型(single, double, count)
* @param {number} tournamentData.id - 数据库中id
* @param {string} tournamentData.name - 名称
* @param {string} tournamentData.format - 类型(single, double, count)
* @param {string} tournamentData.organizer - 组织者
* @param {string} tournamentData.qq_code - QQ号
* @param {string} tournamentData.status - 状态(prepare, finish, starting)
* @param {string} tournamentData.start_time - 开始时间(格式年//例2025/05/24)
* @param {string} tournamentData.end_time - 结束时间(格式年//例2025/05/24)
* @returns {Promise} 返回添加赛事的响应数据
* @returns {Promise<Object>} 返回添加赛事的响应数据
*/
export const addTournament = async (tournamentData) => {
try {
const response = await axiosInstance.post('/tournament/add', tournamentData)
return response.data
const response = await axiosInstance.post('/tournament/add', tournamentData);
return response.data;
} catch (error) {
console.error('添加赛事失败:', {
status: error.response?.status,
data: error.response?.data,
message: error.message
})
throw error
console.error('添加赛事失败:', error);
throw error;
}
}
};
/**
* 获取赛事列表
* @returns {Promise} 返回赛事列表数据
* @returns {Promise<Array<Object>>} 返回赛事列表数据
*/
export const getTournamentList = async () => {
try {
const response = await axiosInstance.get('/tournament/getlist')
return response.data
const response = await axiosInstance.get('/tournament/getlist');
return response.data;
} catch (error) {
console.error('获取赛事列表失败:', {
status: error.response?.status,
data: error.response?.data,
message: error.message
})
throw error
console.error('获取赛事列表失败:', error);
throw error;
}
}
};
// 更新赛事
export const updateTournament = async (id, data) => {
/**
* 更新赛事
* @param {number} id - 赛事ID
* @param {Object} tournamentData - 赛事数据
* @param {number} tournamentData.id - 数据库中id
* @param {string} tournamentData.name - 名称
* @param {string} tournamentData.format - 类型(single, double, count)
* @param {string} tournamentData.organizer - 组织者
* @param {string} tournamentData.qq_code - QQ号
* @param {string} tournamentData.status - 状态(prepare, finish, starting)
* @param {string} tournamentData.start_time - 开始时间(格式年//例2025/05/24)
* @param {string} tournamentData.end_time - 结束时间(格式年//例2025/05/24)
* @returns {Promise<Object>} 返回更新赛事的响应数据
*/
export const updateTournament = async (id, tournamentData) => {
try {
console.log('更新赛事,发送数据:', data)
const response = await axiosInstance.put(`/tournament/update/${id}`, {
name: data.name,
format: data.format,
organizer: data.organizer,
qq_code: data.qq_code,
start_time: data.start_time,
end_time: data.end_time,
status: data.status
})
return response.data
const response = await axiosInstance.put(`/tournament/update/${id}`, tournamentData);
return response.data;
} catch (error) {
console.error('更新赛事失败:', error)
if (error.response) {
console.error('错误详情:', {
status: error.response.status,
data: error.response.data,
headers: error.response.headers,
config: error.config
})
// 如果有详细的错误信息,抛出它
if (error.response.data?.detail) {
throw new Error(error.response.data.detail)
}
}
throw error
console.error('更新赛事失败:', error);
throw error;
}
}
};
// 删除赛事
/**
* 删除赛事
* @param {number} id - 赛事ID
* @returns {Promise<Object>} 返回删除赛事的响应数据
*/
export const deleteTournament = async (id) => {
try {
const response = await axiosInstance.delete(`/tournament/delete/${id}`)
return response.data
const response = await axiosInstance.delete(`/tournament/delete/${id}`);
return response.data;
} catch (error) {
console.error('删除赛事失败:', error)
throw error
console.error('删除赛事失败:', error);
throw error;
}
}
};
// 添加报名结果
export const addSignUpResult = async (data) => {
/**
* 添加玩家报名
* @param {Object} signupData - 报名数据
* @param {number} signupData.id - 数据库中id
* @param {string} signupData.type - 类型
* @param {string} signupData.teamname - 队伍名称
* @param {string} signupData.faction - 阵营(alliedsovietempireobvoicerandom)
* @param {string} signupData.username - 用户名
* @param {string} signupData.qq - QQ号
* @returns {Promise<Object>} 返回添加报名的响应数据
*/
export const addSignUp = async (signupData) => {
try {
const response = await axiosInstance.post('/tournament/signup_result/add', {
tournament_id: parseInt(data.tournament_id),
tournament_name: data.tournament_name,
team_name: data.team_name,
sign_name: data.sign_name.trim(),
win: '0',
lose: '0',
status: 'tie'
})
return response.data
const response = await axiosInstance.post('/tournament/signup/add', signupData);
return response.data;
} catch (error) {
console.error('请求错误:', error)
if (error.response?.data?.detail) {
throw new Error(error.response.data.detail)
}
throw error
console.error('添加报名失败:', error);
throw error;
}
}
};
// 获取参赛结果列表
/**
* 获取玩家报名列表
* @returns {Promise<Array<Object>>} 返回报名列表数据
*/
export const getSignUpList = async () => {
try {
const response = await axiosInstance.get('/tournament/signup/getlist');
return response.data;
} catch (error) {
console.error('获取报名列表失败:', error);
throw error;
}
};
/**
* 更新玩家报名
* @param {number} id - 报名ID
* @param {Object} signupData - 报名数据
* @param {number} signupData.id - 数据库中id
* @param {string} signupData.type - 类型
* @param {string} signupData.teamname - 队伍名称
* @param {string} signupData.faction - 阵营(alliedsovietempireobvoicerandom)
* @param {string} signupData.username - 用户名
* @param {string} signupData.qq - QQ号
* @returns {Promise<Object>} 返回更新报名的响应数据
*/
export const updateSignUp = async (id, signupData) => {
try {
const response = await axiosInstance.put(`/tournament/signup/update/${id}`, signupData);
return response.data;
} catch (error) {
console.error('更新报名失败:', error);
throw error;
}
};
/**
* 删除玩家报名
* @param {number} id - 报名ID
* @returns {Promise<Object>} 返回删除报名的响应数据
*/
export const deleteSignUp = async (id) => {
try {
const response = await axiosInstance.delete(`/tournament/signup/delete/${id}`);
return response.data;
} catch (error) {
console.error('删除报名失败:', error);
throw error;
}
};
/**
* 添加报名结果
* @param {Object} resultData - 结果数据
* @param {number} resultData.tournament_id - 赛事id(int)
* @param {string} resultData.tournament_name - 赛事名称
* @param {string} resultData.team_name - 队伍名称(可选)
* @param {string} resultData.sign_name - 参赛人员名称
* @param {string} resultData.win - 参赛人员胜利局数(str)
* @param {string} resultData.lose - 参赛人员失败局数(str)
* @param {string} resultData.status - 参赛人员对局状态(win,lose,tie)
* @param {string} resultData.round - 轮数(str)
* @param {string} resultData.rival_name - 对方name(str)
* @returns {Promise<Object>} 返回添加报名结果的响应数据
*/
export const addSignUpResult = async (resultData) => {
try {
const response = await axiosInstance.post('/tournament/signup_result/add', resultData);
return response.data;
} catch (error) {
console.error('添加报名结果失败:', error);
throw error;
}
};
/**
* 获取报名结果列表
* @returns {Promise<Array<Object>>} 返回报名结果列表数据
*/
export const getSignUpResultList = async () => {
try {
const response = await axiosInstance.get('/tournament/signup_result/getlist')
return response.data
const response = await axiosInstance.get('/tournament/signup_result/getlist');
return response.data;
} catch (error) {
console.error('获取参赛结果列表失败:', {
status: error.response?.status,
data: error.response?.data,
message: error.message
})
throw error
console.error('获取报名结果列表失败:', error);
throw error;
}
}
};
// 更新参赛结果
export const updateSignUpResult = async (id, data) => {
/**
* 更新报名结果
* @param {number} id - 结果ID
* @param {Object} resultData - 结果数据
* @param {number} resultData.tournament_id - 赛事id(int)
* @param {string} resultData.tournament_name - 赛事名称
* @param {string} resultData.team_name - 队伍名称(可选)
* @param {string} resultData.sign_name - 参赛人员名称
* @param {string} resultData.win - 参赛人员胜利局数(str)
* @param {string} resultData.lose - 参赛人员失败局数(str)
* @param {string} resultData.status - 参赛人员对局状态(win,lose,tie)
* @param {string} resultData.round - 轮数(str)
* @param {string} resultData.rival_name - 对方name(str)
* @returns {Promise<Object>} 返回更新报名结果的响应数据
*/
export const updateSignUpResult = async (id, resultData) => {
try {
// // 更新报名信息 (这部分逻辑根据您的要求被注释掉)
// console.log('更新报名信息...')
// await axiosInstance.put(`/tournament/signup/update/${id}`, {
// tournament_id: parseInt(data.tournament_id),
// type: data.team_name ? 'teamname' : 'individual',
// teamname: data.team_name || '',
// faction: data.faction || 'random',
// username: data.sign_name,
// qq: data.qq || ''
// })
// console.log('报名信息更新成功')
// 更新报名结果
console.log('更新报名结果...')
await axiosInstance.put(`/tournament/signup_result/update/${id}`, {
tournament_id: parseInt(data.tournament_id),
tournament_name: data.tournament_name,
team_name: data.team_name || null,
sign_name: data.sign_name,
win: data.win || '0',
lose: data.lose || '0',
status: data.status || 'tie'
})
console.log('报名结果更新成功')
return { success: true }
const response = await axiosInstance.put(`/tournament/signup_result/update/${id}`, resultData);
return response.data;
} catch (error) {
console.error('更新参赛结果失败:', {
status: error.response?.status,
data: error.response?.data,
message: error.message
})
throw error
console.error('更新报名结果失败:', error);
throw error;
}
}
};
// 删除参赛选手
/**
* 删除报名结果
* @param {number} id - 结果ID
* @returns {Promise<Object>} 返回删除报名结果的响应数据
*/
export const deleteSignUpResult = async (id) => {
try {
// 删除报名结果
console.log('删除报名结果...')
await axiosInstance.delete(`/tournament/signup_result/delete/${id}`)
console.log('报名结果删除成功')
// // 删除报名信息 (这部分逻辑根据您的要求被注释掉)
// console.log('删除报名信息...')
// await axiosInstance.delete(`/tournament/signup/delete/${id}`)
// console.log('报名信息删除成功')
return { success: true }
const response = await axiosInstance.delete(`/tournament/signup_result/delete/${id}`);
return response.data;
} catch (error) {
console.error('删除参赛选手失败:', {
status: error.response?.status,
data: error.response?.data,
message: error.message
})
throw error
console.error('删除报名结果失败:', error);
throw error;
}
}
// 添加报名
export const addSignUp = async (data) => {
try {
console.log('开始报名流程,数据:', data)
// 调用报名 API
console.log('调用报名 API...')
await axiosInstance.post('/tournament/signup/add', {
tournament_id: data.id,
type: data.type,
teamname: data.team_name || '',
faction: data.faction || 'random',
username: data.sign_name,
qq: data.qq || ''
})
console.log('报名 API 调用成功')
// 调用报名结果 API
console.log('调用报名结果 API...')
await axiosInstance.post('/tournament/signup_result/add', {
tournament_id: data.id,
tournament_name: data.tournament_name,
team_name: data.team_name || null,
sign_name: data.sign_name,
win: '0',
lose: '0',
status: 'tie'
})
console.log('报名结果 API 调用成功')
return {
signup: { success: true },
result: { success: true }
}
} catch (error) {
console.error('报名请求错误:', {
message: error.message,
response: error.response?.data,
status: error.response?.status,
config: error.config
})
// 如果是服务器返回的错误信息,直接使用
if (error.response?.data?.detail) {
throw new Error(error.response.data.detail)
}
// 其他错误,包装成更友好的错误信息
throw new Error('报名失败,请检查网络连接后重试')
}
}
};

BIN
src/assets/login_4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1024 KiB

BIN
src/assets/login_5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ const props = defineProps({
})
const emit = defineEmits(['close', 'apply'])
function handleClose() {
function handleClose() {
emit('close')
}
function handleApply() {

View File

@ -12,7 +12,7 @@
<div class="player-info">
<div class="player-name">{{ item.username }}</div>
<div class="player-faction">{{ item.faction }}</div>
<div class="player-score">{{ item.score }}</div>
<div class="player-score" :title="`积分: ${item.points}`">{{ item.score }}</div>
</div>
</div>
</div>
@ -31,7 +31,7 @@
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ref, onMounted, watch } from 'vue'
import { getSignUpResultList } from '@/api/tournament'
const props = defineProps({
@ -46,31 +46,77 @@ const rankData = ref([])
const fetchRankData = async () => {
try {
const response = await getSignUpResultList()
//
const results = response
.filter(player => player.tournament_id === props.tournamentId)
.map((player, index) => ({
rank: index + 1,
username: player.sign_name,
faction: player.faction,
win: parseInt(player.win) || 0,
lose: parseInt(player.lose) || 0,
score: `${player.win}${player.lose}`
}))
.sort((a, b) => {
//
if (b.win !== a.win) {
return b.win - a.win
console.log('RankContestant 原始数据:', response)
//
const filteredPlayers = response.filter(player => player.tournament_id === props.tournamentId)
console.log('RankContestant 筛选后数据:', filteredPlayers)
//
const playerStats = {}
filteredPlayers.forEach((player, index) => {
const playerName = player.sign_name
const win = parseInt(player.win || 0)
const lose = parseInt(player.lose || 0)
const round = player.round || '0'
console.log(`RankContestant 处理第${index + 1}条数据:`, {
id: player.id,
sign_name: playerName,
round: round,
win: win,
lose: lose,
status: player.status
})
if (!playerStats[playerName]) {
playerStats[playerName] = {
username: playerName,
faction: player.faction || player.team_name || '',
totalWin: 0,
totalLose: 0,
totalPoints: 0,
rounds: []
}
//
return a.lose - b.lose
}
playerStats[playerName].totalWin += win
playerStats[playerName].totalLose += lose
playerStats[playerName].rounds.push(round)
//
playerStats[playerName].totalPoints = playerStats[playerName].totalWin
})
console.log('RankContestant 分组后统计:', playerStats)
//
const sortedPlayers = Object.values(playerStats)
.sort((a, b) => {
//
if (b.totalWin !== a.totalWin) {
return b.totalWin - a.totalWin
}
//
return a.totalLose - b.totalLose
})
.map((player, index) => ({
...player,
rank: index + 1
}))
id: index + 1, // 使ID
rank: (index + 1).toString(),
username: player.username,
faction: player.faction,
win: player.totalWin,
lose: player.totalLose,
points: player.totalPoints,
status: '',
bracket_type: 'winners',
score: `${player.totalWin}${player.totalLose}负 (${player.totalPoints.toFixed(1)}分)`,
rounds: player.rounds.join(',')
}))
rankData.value = results
console.log('RankContestant 最终排名结果:', sortedPlayers)
rankData.value = sortedPlayers
} catch (error) {
console.error('获取排名数据失败:', error)
}
@ -79,6 +125,13 @@ const fetchRankData = async () => {
onMounted(() => {
fetchRankData()
})
// ID
watch(() => props.tournamentId, () => {
if (props.tournamentId) {
fetchRankData()
}
})
</script>
<style scoped>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,112 @@
<template>
<div class="temp-privilege-form">
<h2>管理员使用qq发送修改密码邮件</h2>
<form @submit.prevent="handleSubmit">
<div class="form-group">
<label for="uuid">用户qq</label>
<input id="uuid" v-model="qq_code" type="text" placeholder="请输入用户qq" required />
</div>
<button class="submit-btn" type="submit" :disabled="loading">提交</button>
</form>
<div v-if="msg" :class="{'success-msg': msgType==='success', 'error-msg': msgType==='error'}">{{ msg }}</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import {getUserByInfo,requestResetPassword} from '@/api/login'
const qq_code = ref('')
const loading = ref(false)
const msg = ref('')
const msgType = ref('')
async function handleSubmit() {
msg.value = ''
msgType.value = ''
loading.value = true
try {
//api
const user = await getUserByInfo({qq_code:qq_code.value})
console.log(user)
await requestResetPassword(user.uuid)
msg.value = '发送成功!'
msgType.value = 'success'
qq_code.value = ''
} catch (e) {
msg.value = '发送失败请稍后再试'
msgType.value = 'error'
}
loading.value = false
}
</script>
<style scoped>
.temp-privilege-form {
background: #fff;
border-radius: 8px;
padding: 32px 24px 24px 24px;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
max-width: 420px;
margin: 40px auto 0 auto;
}
.temp-privilege-form h2 {
margin-bottom: 24px;
color: #2563eb;
text-align: center;
}
.form-group {
margin-bottom: 18px;
display: flex;
flex-direction: column;
}
.form-group label {
margin-bottom: 6px;
color: #333;
font-weight: 500;
}
.form-group input,
.form-group select {
padding: 8px 10px;
border: 1px solid #d1d5db;
border-radius: 4px;
font-size: 15px;
outline: none;
transition: border 0.2s;
}
.form-group input:focus,
.form-group select:focus {
border-color: #2563eb;
}
.custom-exp-input {
margin-top: 8px;
}
.submit-btn {
width: 100%;
background: #2563eb;
color: #fff;
border: none;
border-radius: 4px;
padding: 10px 0;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.submit-btn:disabled {
background: #a5b4fc;
cursor: not-allowed;
}
.success-msg {
color: #22c55e;
text-align: center;
margin-top: 18px;
}
.error-msg {
color: #ef4444;
text-align: center;
margin-top: 18px;
}
</style>

View File

@ -86,8 +86,10 @@
<span class="error-message" v-if="usernameError">{{ usernameError }}</span>
</div>
<div class="login-button">
<button type="submit" :disabled="isSubmitting">
{{ isSubmitting ? '提交中...' : '重置密码' }}
<button type="submit" :disabled="isSubmitting || cooldown > 0">
<template v-if="isSubmitting">提交中...</template>
<template v-else-if="cooldown > 0">请稍候 ({{ cooldown }}s)</template>
<template v-else>重置密码</template>
</button>
</div>
<div class="register-link">
@ -111,7 +113,7 @@
<script setup>
import { ref, onMounted } from 'vue'
import {getCaptcha, forgetPassword, requestResetPassword, getUserByInfo} from '../api/login'
import {getCaptcha, requestResetPassword, getUserByInfo} from '../api/login'
import ErrorDialog from './ErrorDialog.vue'
import SuccessDialog from './SuccessDialog.vue'
@ -132,6 +134,8 @@ const uuidError = ref('')
//
const isSubmitting = ref(false)
const cooldown = ref(0)
let cooldownTimer = null
//
const showError = ref(false)
@ -218,17 +222,23 @@ const uuid_handleForgetPassword = async () => {
if (!uuid_validateForm()) {
return
}
//console.log(uuid.value);
const user = await getUserByInfo({qq_code:username.value})
console.log(user)
await requestResetPassword(user.uuid)
isSubmitting.value = true
cooldown.value = 60
cooldownTimer = setInterval(() => {
if (cooldown.value > 0) {
cooldown.value--
} else {
clearInterval(cooldownTimer)
cooldownTimer = null
}
}, 1000)
}catch ( error){
showErrorMessage(error.message || '不是正确的uuid')
}finally {
isSubmitting.value = false
}
}

View File

@ -16,7 +16,8 @@ const routes = [
children: [
{
path: '',
redirect: '/maps'
name: 'Home',
component: () => import('@/views/index/Home.vue')
},
{
path: 'demands',
@ -48,33 +49,47 @@ const routes = [
path: 'weapon-match',
name: 'WeaponMatch',
component: () => import('@/views/weapon/WeaponMatch.vue'),
meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-mod'] }
// meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-mod'] }
meta: { requiresAuth: true}
},
{
path: 'configEditor',
name: 'ConfigEditor',
component: () => import('@/views/index/ConfigEditor.vue'),
// meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-mod'] }
meta: { requiresAuth: true}
},
{
path: 'competition',
name: 'Competition',
component: () => import('@/views/index/Competition.vue'),
component: () => import('@/views/competition/Competition.vue'),
// meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-competitor'] }
meta: { requiresAuth: true}
},
{
path: 'competition/add',
name: 'AddCompetition',
component: () => import('@/views/index/AddContestant.vue'),
component: () => import('@/views/competition/AddContestant.vue'),
meta: { requiresAuth: true }
},
{
path: 'competition/detail',
name: 'CompetitionDetail',
component: () => import('@/views/index/CompetitionDetail.vue'),
component: () => import('@/views/competition/CompetitionDetail.vue'),
meta: { requiresAuth: true }
},
{
path: 'competition/signup',
name: 'CompetitionSignUp',
component: () => import('@/views/index/CompetitionSignUp.vue'),
component: () => import('@/views/competition/CompetitionSignUp.vue'),
meta: { requiresAuth: true }
},
// {
// path: 'competition',
// name: 'Competition',
// component: () => import('@/views/competition/Competition.vue'),
// meta: { requiresAuth: true}
// },
{
path: 'editors-maps',
name: 'EditorsMaps',
@ -89,13 +104,13 @@ const routes = [
path: 'PIC2TGA',
name: 'PIC2TGA',
component: () => import('@/views/index/PIC2TGA.vue'),
meta: { requiredPrivilege: ['lv-admin','lv-mod','lv-map','lv-competitor'] }
// meta: { requiredPrivilege: ['lv-admin','lv-mod','lv-map','lv-competitor'] }
},
{
path: 'terrainGenerate',
name: 'TerrainGenerate',
component: () => import('@/views/index/TerrainGenerate.vue'),
meta: { requiredPrivilege: ['lv-admin','lv-mod','lv-map','lv-competitor'] }
// meta: { requiredPrivilege: ['lv-admin','lv-mod','lv-map','lv-competitor'] }
}
]
},
@ -163,7 +178,7 @@ router.beforeEach(async (to, from, next) => {
// 登录校验:如果页面需要登录但本地没有有效 token则跳转登录页
if (requiresAuth && !hasValidToken()) {
return next({
path: '/maps',
path: '/',
query: { redirect: to.fullPath }
})
}
@ -174,7 +189,7 @@ router.beforeEach(async (to, from, next) => {
if (!user || !hasPrivilegeWithTemp(user, requiredPrivilege)) {
// 通过事件总线通知弹窗
eventBus.emit('no-privilege')
return next({ path: '/maps', replace: true })
return next({ path: '/', replace: true })
}
}

View File

@ -98,7 +98,6 @@ export const loginSuccess = (accessToken, userId) => {
if (userId) {
localStorage.setItem('user_id', userId);
}
// 设置登录标志
justLoggedIn.value = true;

View File

@ -55,9 +55,9 @@
{{ loading ? '重置中...' : '重置密码' }}
</button>
</div>
<div class="back-link">
<a @click.prevent="goToHome">返回首页</a>
</div>
<!-- <div class="back-link">-->
<!-- <a @click.prevent="goToHome">返回登录页</a>-->
<!-- </div>-->
</form>
</div>
</div>
@ -86,11 +86,14 @@ import SuccessDialog from '@/components/SuccessDialog.vue'
import loginBg from '@/assets/login_1.jpg'
import loginBg1 from '@/assets/login_2.jpg'
import loginBg3 from '@/assets/login_3.jpg'
import loginBg4 from '@/assets/login_4.jpg'
import loginBg5 from '@/assets/login_5.jpg'
import {hasValidToken, logoutUser} from "@/utils/jwt.js";
const route = useRoute()
const router = useRouter()
const images = [loginBg, loginBg1, loginBg3]
const images = [loginBg, loginBg1, loginBg3,loginBg4, loginBg5]
const randomIndex = Math.floor(Math.random() * images.length)
const bgImg = ref(images[randomIndex])
@ -149,7 +152,17 @@ const handleResetPassword = async () => {
// API
await resetPassword(token, newPassword.value)
showSuccessMessage('密码重置成功!请使用新密码登录。')
//
showSuccessMessage('密码重置成功!')
//
setTimeout(() => {
if (hasValidToken()) {
logoutUser()
}
router.push('/backend/login')
}, 1500)
} catch (error) {
console.error('重置密码失败:', error)
const errorMessage = error.response?.data?.detail || error.response?.data?.message || error.message || '重置密码失败,请重试'
@ -165,7 +178,11 @@ const handleSuccessClose = () => {
}
const goToHome = () => {
router.push('/')
if (hasValidToken()) {
logoutUser()
}
router.push('/backend/login')
// router.push('/')
}
onMounted(() => {

View File

@ -27,6 +27,9 @@
<li :class="{active: currentAdminView === 'admin-edit-user-privilege'}">
<a @click="selectAdminView('admin-edit-user-privilege')">管理员修改用户权限</a>
</li>
<li :class="{active: currentAdminView === 'admin-edit-user-password'}">
<a @click="selectAdminView('admin-edit-user-password')">管理员把你密码扬了</a>
</li>
</ul>
</li>
<li>
@ -71,6 +74,7 @@
<AdminEditUserPrivilege v-if="currentAdminView === 'admin-edit-user-privilege'" />
<AffairManagement v-if="currentAdminView === 'affair-management'" />
<TempPrivilegeReview v-if="currentAdminView === 'permission-review'" />
<AdminChangesPwd v-if="currentAdminView === 'admin-edit-user-password'" />
</div>
</div>
</div>
@ -83,6 +87,7 @@ import { getUserInfo } from '@/utils/jwt'
import AdminEditUserPrivilege from '@/components/backend/AdminEditUserPrivilege.vue'
import AffairManagement from '@/components/backend/AffairManagement.vue'
import TempPrivilegeReview from '@/components/backend/TempPrivilegeReview.vue'
import AdminChangesPwd from '@/components/backend/AdminChangesPwd.vue'
const router = useRouter()
const hasToken = ref(false)

View File

@ -37,11 +37,13 @@ import { hasValidToken } from '@/utils/jwt'
import loginBg from '@/assets/login_1.jpg'
import loginBg1 from '@/assets/login_2.jpg'
import loginBg3 from '@/assets/login_3.jpg'
import loginBg4 from '@/assets/login_4.jpg'
import loginBg5 from '@/assets/login_5.jpg'
import ForgetModule from "@/components/forget_module.vue";
import LoginModule from '@/components/login_module.vue'
import RegisterModule from '@/components/register_module.vue'
const images = [loginBg, loginBg1,loginBg3]
const images = [loginBg, loginBg1,loginBg3, loginBg4,loginBg5]
const randomIndex = Math.floor(Math.random() * images.length)
const bgImg = ref(images[randomIndex])

View File

@ -3,61 +3,62 @@
<div class="page-header">
<h1>添加新赛事</h1>
<div class="header-actions">
<button class="btn-excel" @click="handleExcelImport">
<i class="fas fa-file-excel"></i>
通过表格添加
</button>
<!-- <button class="btn-excel" @click="handleExcelImport">-->
<!-- <i class="fas fa-file-excel"></i>-->
<!-- 通过表格添加-->
<!-- </button>-->
</div>
</div>
<!-- Excel导入弹窗 -->
<div v-if="showExcelDialog" class="excel-dialog-overlay">
<div class="excel-dialog">
<h3>通过Excel导入赛事信息</h3>
<div class="excel-upload-area" @click="triggerFileInput" @dragover.prevent @drop.prevent="handleFileDrop"
:class="{ 'is-dragover': isDragover }">
<input type="file" ref="fileInput" accept=".xlsx" @change="handleFileSelect" style="display: none">
<div class="upload-content">
<i class="fas fa-file-excel"></i>
<p>点击或拖拽Excel文件到此处</p>
</div>
</div>
<div v-if="eventPreviewData.length > 0" class="preview-table">
<h4>赛事信息表预览</h4>
<table>
<thead>
<tr>
<th v-for="h in eventPreviewHeaders" :key="h">{{ h }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in eventPreviewData" :key="index">
<td v-for="h in eventPreviewHeaders" :key="h">{{ item[h] }}</td>
</tr>
</tbody>
</table>
</div>
<div v-if="playerPreviewData.length > 0" class="preview-table">
<h4>选手报名表预览</h4>
<table>
<thead>
<tr>
<th v-for="h in playerPreviewHeaders" :key="h">{{ h }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in playerPreviewData" :key="index">
<td v-for="h in playerPreviewHeaders" :key="h">{{ item[h] }}</td>
</tr>
</tbody>
</table>
</div>
<div class="dialog-actions">
<button class="confirm-btn" @click="confirmImport" :disabled="eventPreviewData.length === 0">确认导入</button>
<button class="cancel-btn" @click="closeExcelDialog">取消</button>
</div>
</div>
</div>
<!-- &lt;!&ndash; Excel导入弹窗 &ndash;&gt;-->
<!-- <div v-if="showExcelDialog" class="excel-dialog-overlay">-->
<!-- <div class="excel-dialog">-->
<!-- <h3>通过Excel导入赛事信息</h3>-->
<!-- <div class="excel-upload-area" @click="triggerFileInput" @dragover.prevent @drop.prevent="handleFileDrop"-->
<!-- :class="{ 'is-dragover': isDragover }">-->
<!-- <input type="file" ref="fileInput" accept=".xlsx" @change="handleFileSelect" style="display: none">-->
<!-- <div class="upload-content">-->
<!-- <i class="fas fa-file-excel"></i>-->
<!-- <p>点击或拖拽Excel文件到此处</p>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div v-if="eventPreviewData.length > 0" class="preview-table">-->
<!-- <h4>赛事信息表预览</h4>-->
<!-- <table>-->
<!-- <thead>-->
<!-- <tr>-->
<!-- <th v-for="h in eventPreviewHeaders" :key="h">{{ h }}</th>-->
<!-- </tr>-->
<!-- </thead>-->
<!-- <tbody>-->
<!-- <tr v-for="(item, index) in eventPreviewData" :key="index">-->
<!-- <td v-for="h in eventPreviewHeaders" :key="h">{{ item[h] }}</td>-->
<!-- </tr>-->
<!-- </tbody>-->
<!-- </table>-->
<!-- </div>-->
<!-- <div v-if="playerPreviewData.length > 0" class="preview-table">-->
<!-- <h4>选手报名表预览</h4>-->
<!-- <table>-->
<!-- <thead>-->
<!-- <tr>-->
<!-- <th v-for="h in playerPreviewHeaders" :key="h">{{ h }}</th>-->
<!-- </tr>-->
<!-- </thead>-->
<!-- <tbody>-->
<!-- <tr v-for="(item, index) in playerPreviewData" :key="index">-->
<!-- <td v-for="h in playerPreviewHeaders" :key="h">{{ item[h] }}</td>-->
<!-- </tr>-->
<!-- </tbody>-->
<!-- </table>-->
<!-- </div>-->
<!-- <div class="dialog-actions">-->
<!-- <button class="confirm-btn" @click="confirmImport" :disabled="eventPreviewData.length === 0">确认导入</button>-->
<!-- <button class="cancel-btn" @click="closeExcelDialog">取消</button>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<form @submit.prevent="handleSubmit" class="contest-form">
<div class="form-group">
@ -100,14 +101,28 @@
<button type="button" class="cancel-btn" @click="handleCancel">取消</button>
</div>
</form>
<!-- 对话框组件 -->
<SuccessDialog
:visible="successDialog.visible"
:message="successDialog.message"
@close="successDialog.visible = false"
/>
<ErrorDialog
:visible="errorDialog.visible"
:message="errorDialog.message"
@close="errorDialog.visible = false"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { addTournament, addSignUp, getTournamentList } from '@/api/tournament'
import { addTournament, addSignUp, getTournamentList } from '@/api/tournament.js'
import * as XLSX from 'xlsx'
import SuccessDialog from '@/components/SuccessDialog.vue'
import ErrorDialog from '@/components/ErrorDialog.vue'
const router = useRouter()
const showExcelDialog = ref(false)
@ -129,6 +144,10 @@ const formData = ref({
description: ''
})
// Dialog state
const successDialog = ref({ visible: false, message: '' })
const errorDialog = ref({ visible: false, message: '' })
// Excel
const excelFieldMap = {
'赛事名称': 'name',
@ -190,11 +209,11 @@ const handleSubmit = async () => {
// API
await addTournament(tournamentData)
alert('添加赛事成功!')
successDialog.value = { visible: true, message: '添加赛事成功!' }
router.push('/competition')
} catch (error) {
console.error('提交失败:', error)
alert(error.response?.data?.message || '添加赛事失败,请重试')
errorDialog.value = { visible: true, message: error.response?.data?.message || '添加赛事失败,请重试' }
}
}
@ -310,7 +329,7 @@ const processExcelFile = (file) => {
}
}
} catch (error) {
alert('Excel文件格式错误请检查文件内容')
errorDialog.value = { visible: true, message: 'Excel文件格式错误请检查文件内容' }
}
}
reader.readAsArrayBuffer(file)
@ -340,7 +359,7 @@ const confirmImport = async () => {
try {
await addTournament(tournamentData)
alert('赛事导入成功!')
successDialog.value = { visible: true, message: '赛事导入成功!' }
const tournamentList = await getTournamentList()
console.log('获取到的赛事列表:', tournamentList)
@ -377,16 +396,16 @@ const confirmImport = async () => {
await addSignUp(signUpData)
} catch (error) {
console.error('报名失败:', error)
alert(`报名失败: ${row['队伍名称或者个人参赛名称']}${error.message}`)
errorDialog.value = { visible: true, message: `报名失败: ${row['队伍名称或者个人参赛名称']}${error.message}` }
}
}
alert('选手报名表导入完成!')
successDialog.value = { visible: true, message: '选手报名表导入完成!' }
}
router.push('/competition')
} catch (error) {
console.error('导入失败:', error)
alert(error.response?.data?.message || '赛事导入失败,请重试')
errorDialog.value = { visible: true, message: error.response?.data?.message || '赛事导入失败,请重试' }
}
closeExcelDialog()
}

View File

@ -110,7 +110,7 @@
<script setup>
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { getTournamentList } from '@/api/tournament'
import { getTournamentList } from '@/api/tournament.js'
const router = useRouter()

View File

@ -24,14 +24,14 @@
{{ statusMap[competition.status] }}
</span>
</div>
<div class="edit-controls" v-if="isOrganizer">
<!-- <div class="edit-controls" v-if="isOrganizer">
<button class="edit-mode-btn" @click="toggleEditMode">
{{ isEditMode ? '退出编辑' : '编辑对阵图' }}
</button>
<button v-if="isEditMode" class="save-btn" @click="saveChanges" :disabled="isSaving">
{{ isSaving ? '保存中...' : '保存修改' }}
</button>
</div>
</div> -->
</div>
<!-- 报名玩家列表 -->
@ -46,6 +46,8 @@
<tr>
<th>玩家名称</th>
<th>队伍名称</th>
<th>阵营</th>
<th>QQ</th>
<th>胜场</th>
<th>负场</th>
<th>状态</th>
@ -55,12 +57,14 @@
<tbody>
<tr v-for="player in registeredPlayers" :key="player.id">
<td>{{ player.sign_name }}</td>
<td>{{ player.team_name || '个人' }}</td>
<td>{{ player.team_name === ' ' ? '个人' : player.team_name}}</td>
<td>{{ formatFaction(player.faction) }}</td>
<td>{{ player.qq || '-' }}</td>
<td>{{ player.win }}</td>
<td>{{ player.lose }}</td>
<td>{{ player.status }}</td>
<td class="action-buttons">
<button class="edit-player-btn" @click="handleEditPlayer(player)" >修改</button>
<!-- <button class="edit-player-btn" @click="handleEditPlayer(player)" >修改</button>-->
<button class="remove-btn" @click="handleRemovePlayer(player.id)">移除</button>
</td>
</tr>
@ -75,10 +79,13 @@
v-if="competition.status === 'finish'"
:tournament-id="parseInt(route.query.id)"
/>
<DoubleEliminationBracket
v-if="competition.format === '双败淘汰' && competition.status === 'starting'"
:tournament-id="parseInt(route.query.id)"
/>
<tournament-bracket
v-if="competition.status === 'starting'"
v-else-if="competition.status === 'starting'"
:tournament-id="parseInt(route.query.id)"
@refreshPlayers="fetchRegisteredPlayers"
/>
</template>
@ -209,6 +216,18 @@
</div>
</div>
</div>
<!-- 对话框组件 -->
<SuccessDialog
:visible="successDialog.visible"
:message="successDialog.message"
@close="successDialog.visible = false"
/>
<ErrorDialog
:visible="errorDialog.visible"
:message="errorDialog.message"
@close="errorDialog.visible = false"
/>
</div>
</template>
@ -217,15 +236,20 @@ import { ref, onMounted, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import TournamentBracket from '@/components/TournamentBracket.vue'
import RankContestant from '@/components/RankContestant.vue'
import DoubleEliminationBracket from '@/components/DoubleEliminationBracket.vue'
import {
getTournamentList,
updateTournament,
deleteTournament,
getSignUpList,
getSignUpResultList,
updateSignUpResult,
deleteSignUpResult
} from '@/api/tournament'
import { getStoredUser } from '@/utils/jwt'
deleteSignUpResult,
deleteSignUp
} from '@/api/tournament.js'
import { getStoredUser } from '@/utils/jwt.js'
import SuccessDialog from '@/components/SuccessDialog.vue'
import ErrorDialog from '@/components/ErrorDialog.vue'
const router = useRouter()
const route = useRoute()
@ -243,15 +267,6 @@ const statusMap = {
'finish': '已结束'
}
// const factionMap = {
// 'allied': '',
// 'soviet': '',
// 'empire': '',
// 'ob': 'OB',
// 'voice': '',
// 'random': ''
// }
//
const competition = ref({
name: '',
@ -290,6 +305,10 @@ const playerEditForm = ref({ // 玩家编辑表单
tournament_id: ''
})
// Dialog state
const successDialog = ref({ visible: false, message: '' })
const errorDialog = ref({ visible: false, message: '' })
//
const formatDate = (date) => {
if (!date) return ''
@ -301,10 +320,18 @@ const formatType = (type) => {
return formatMap[type] || type
}
// //
// const formatFaction = (faction) => {
// return factionMap[faction] || faction
// }
//
const formatFaction = (faction) => {
const factionMap = {
'allied': '盟军',
'soviet': '苏联',
'empire': '帝国',
'ob': 'OB',
'voice': '解说',
'random': '随机'
}
return factionMap[faction] || faction
}
//
const handleBack = () => {
@ -374,12 +401,23 @@ const handleUpdate = async () => {
}
await updateTournament(tournamentId, updateData)
alert('更新成功!')
// tournament_name
if (editForm.value.name !== competition.value.name) {
await updateAllPlayersTournamentName(tournamentId, editForm.value.name)
}
// ""
if (selectedStatus.value === 'starting') {
await generateOpponentsForAllPlayers(tournamentId)
}
successDialog.value = { visible: true, message: '更新成功!' }
closeEditDialog()
fetchTournamentDetail() //
} catch (error) {
console.error('更新失败:', error)
alert(error.response?.data?.message || '更新失败,请重试')
errorDialog.value = { visible: true, message: error.response?.data?.message || '更新失败,请重试' }
} finally {
isUpdating.value = false
}
@ -389,13 +427,52 @@ const handleUpdate = async () => {
const handleDelete = async () => {
try {
isDeleting.value = true
const tournamentId = route.query.id
const tournamentId = parseInt(route.query.id)
//
const [signupList, resultList] = await Promise.all([
getSignUpList(),
getSignUpResultList()
])
//
const tournamentSignups = signupList.filter(signup =>
signup.tournament_id === tournamentId
)
//
const tournamentResults = resultList.filter(result =>
result.tournament_id === tournamentId
)
console.log(`准备删除赛事 ${tournamentId},共有 ${tournamentSignups.length} 个报名记录,${tournamentResults.length} 个结果记录`)
//
const deletePromises = []
//
for (const signup of tournamentSignups) {
deletePromises.push(deleteSignUp(signup.id))
}
//
for (const result of tournamentResults) {
deletePromises.push(deleteSignUpResult(result.id))
}
//
if (deletePromises.length > 0) {
await Promise.all(deletePromises)
console.log('所有报名玩家删除完成')
}
//
await deleteTournament(tournamentId)
alert('删除成功!')
successDialog.value = { visible: true, message: '删除成功!' }
router.push('/competition')
} catch (error) {
console.error('删除失败:', error)
alert(error.response?.data?.message || '删除失败,请重试')
errorDialog.value = { visible: true, message: error.response?.data?.message || '删除失败,请重试' }
} finally {
isDeleting.value = false
showDeleteConfirm.value = false
@ -444,12 +521,12 @@ const fetchTournamentDetail = async () => {
}
} else {
console.log('未找到赛事')
alert('未找到赛事信息')
errorDialog.value = { visible: true, message: '未找到赛事信息' }
router.push('/competition')
}
} catch (error) {
console.error('获取赛事详情失败:', error)
alert('获取赛事详情失败,请重试')
errorDialog.value = { visible: true, message: '获取赛事详情失败,请重试' }
} finally {
isLoading.value = false
}
@ -459,15 +536,42 @@ const fetchTournamentDetail = async () => {
const fetchRegisteredPlayers = async () => {
try {
const tournamentId = parseInt(route.query.id)
const response = await getSignUpResultList()
//
console.log('报名玩家原始数据:', response)
//
const [signupList, resultList] = await Promise.all([
getSignUpList(),
getSignUpResultList()
])
console.log('报名信息原始数据:', signupList)
console.log('结果信息原始数据:', resultList)
console.log('当前赛事ID:', tournamentId)
// tournament_id id
registeredPlayers.value = response.filter(player =>
player.tournament_id === tournamentId
//
const tournamentSignups = signupList.filter(signup =>
signup.tournament_id === tournamentId
)
console.log('筛选后的玩家数据:', registeredPlayers.value)
//
const tournamentResults = resultList.filter(result =>
result.tournament_id === tournamentId
)
// 0
registeredPlayers.value = tournamentResults
.filter(result => result.round === '0' || result.round === 0 || !result.round)
.map(result => {
//
const signup = tournamentSignups.find(s => s.username === result.sign_name)
return {
...result,
faction: signup?.faction || 'random', //
qq: signup?.qq || '' // QQ
}
})
console.log('合并后的玩家数据:', registeredPlayers.value)
} catch (error) {
console.error('获取报名玩家列表失败:', error)
}
@ -478,12 +582,38 @@ const handleRemovePlayer = async (playerId) => {
if (!confirm('确定要移除该玩家吗?')) return
try {
await deleteSignUpResult(playerId)
//
const player = registeredPlayers.value.find(p => p.id === playerId)
if (!player) {
throw new Error('未找到玩家信息')
}
//
const signupList = await getSignUpList()
const signupRecord = signupList.find(s =>
s.tournament_id === parseInt(route.query.id) &&
s.username === player.sign_name
)
//
const deletePromises = []
//
deletePromises.push(deleteSignUpResult(playerId))
//
if (signupRecord) {
deletePromises.push(deleteSignUp(signupRecord.id))
}
//
await Promise.all(deletePromises)
await fetchRegisteredPlayers() //
alert('移除成功!')
successDialog.value = { visible: true, message: '移除成功!' }
} catch (error) {
console.error('移除玩家失败:', error)
alert(error.response?.data?.message || '移除失败,请重试')
errorDialog.value = { visible: true, message: error.response?.data?.message || '移除失败,请重试' }
}
}
@ -525,7 +655,14 @@ const handleStatusUpdate = async () => {
console.log('更新赛事状态,发送数据:', updateData)
await updateTournament(tournamentId, updateData)
alert('状态更新成功!')
// ""
if (selectedStatus.value === 'starting') {
console.log('状态变为进行中,开始生成对手...')
await generateOpponentsForAllPlayers(tournamentId)
}
successDialog.value = { visible: true, message: '状态更新成功!' }
closeStatusDialog()
fetchTournamentDetail() //
} catch (error) {
@ -535,7 +672,7 @@ const handleStatusUpdate = async () => {
response: error.response?.data,
status: error.response?.status
})
alert(error.message || '状态更新失败,请重试')
errorDialog.value = { visible: true, message: error.message || '状态更新失败,请重试' }
} finally {
isUpdating.value = false
}
@ -586,7 +723,7 @@ const handlePlayerUpdate = async () => {
await updateSignUpResult(playerEditForm.value.id, updateData)
await fetchRegisteredPlayers() //
closePlayerEditDialog()
alert('更新成功!')
successDialog.value = { visible: true, message: '更新成功!' }
} catch (error) {
console.error('更新玩家信息失败:', error)
console.error('错误详情:', {
@ -594,7 +731,7 @@ const handlePlayerUpdate = async () => {
response: error.response?.data,
status: error.response?.status
})
alert(error.response?.data?.detail || error.message || '更新失败,请重试')
errorDialog.value = { visible: true, message: error.response?.data?.detail || error.message || '更新失败,请重试' }
} finally {
isUpdating.value = false
}
@ -606,37 +743,246 @@ const saveChanges = async () => {
isSaving.value = true
// TODO: API
// await saveTournamentBracket(route.query.id, tournamentRounds.value)
alert('保存成功!')
successDialog.value = { visible: true, message: '保存成功!' }
isEditMode.value = false
} catch (error) {
console.error('保存失败:', error)
alert('保存失败,请重试')
errorDialog.value = { visible: true, message: '保存失败,请重试' }
} finally {
isSaving.value = false
}
}
//
const updateAllPlayersTournamentName = async (tournamentId, newTournamentName) => {
try {
console.log('开始更新所有玩家的赛事名称...')
//
const response = await getSignUpResultList()
const players = response.filter(player => player.tournament_id == tournamentId)
console.log(`找到 ${players.length} 个玩家需要更新赛事名称`)
if (players.length === 0) {
console.log('没有找到需要更新的玩家')
return
}
// tournament_name
const updatePromises = players.map(player =>
updateSignUpResult(player.id, {
...player,
tournament_name: newTournamentName
})
)
//
await Promise.all(updatePromises)
console.log('所有玩家的赛事名称更新完成')
} catch (error) {
console.error('更新玩家赛事名称失败:', error)
throw error
}
}
//
const generateOpponentsForAllPlayers = async (tournamentId) => {
try {
console.log('=== 开始为所有玩家生成对手 ===')
console.log('当前赛事ID:', tournamentId)
//
const response = await getSignUpResultList()
console.log('获取到的所有结果数据:', response)
const players = response.filter(player => player.tournament_id == tournamentId)
console.log('筛选后的玩家数据:', players)
console.log(`找到 ${players.length} 个玩家需要生成对手`)
if (players.length === 0) {
console.log('没有找到需要生成对手的玩家')
return
}
//
console.log('=== 原始玩家信息 ===')
players.forEach((player, index) => {
console.log(`玩家${index + 1}:`, {
id: player.id,
sign_name: player.sign_name,
team_name: player.team_name,
tournament_id: player.tournament_id,
tournament_name: player.tournament_name
})
})
//
const shuffledPlayers = [...players].sort(() => Math.random() - 0.5)
console.log('=== 随机打乱后的玩家顺序 ===')
shuffledPlayers.forEach((player, index) => {
console.log(`${index + 1}位:`, player.sign_name)
})
//
const updatePromises = []
const pairs = []
console.log('=== 开始生成配对 ===')
for (let i = 0; i < shuffledPlayers.length; i += 2) {
if (i + 1 < shuffledPlayers.length) {
//
const player1 = shuffledPlayers[i]
const player2 = shuffledPlayers[i + 1]
console.log(`配对 ${i/2 + 1}: ${player1.sign_name} vs ${player2.sign_name}`)
pairs.push({
player1: player1.sign_name,
player2: player2.sign_name,
type: '对战'
})
// player1
const updateData1 = {
...player1,
rival_name: player2.sign_name,
win: '0',
lose: '0',
status: 'tie',
round: '0'
}
console.log(`更新 ${player1.sign_name} 的对手信息:`, updateData1)
updatePromises.push(updateSignUpResult(player1.id, updateData1))
// player2
const updateData2 = {
...player2,
rival_name: player1.sign_name,
win: '0',
lose: '0',
status: 'tie',
round: '0'
}
console.log(`更新 ${player2.sign_name} 的对手信息:`, updateData2)
updatePromises.push(updateSignUpResult(player2.id, updateData2))
} else {
//
const player = shuffledPlayers[i]
console.log(`轮空: ${player.sign_name}`)
pairs.push({
player1: player.sign_name,
player2: null,
type: '轮空'
})
const updateData = {
...player,
rival_name: '轮空',
win: '0',
lose: '0',
status: 'tie',
round: '0'
}
console.log(`更新 ${player.sign_name} 的轮空信息:`, updateData)
updatePromises.push(updateSignUpResult(player.id, updateData))
}
}
console.log('=== 配对结果总结 ===')
pairs.forEach((pair, index) => {
if (pair.type === '对战') {
console.log(`配对${index + 1}: ${pair.player1} vs ${pair.player2}`)
} else {
console.log(`配对${index + 1}: ${pair.player1} (轮空)`)
}
})
console.log(`总共需要更新 ${updatePromises.length} 个玩家记录`)
//
console.log('=== 开始批量更新 ===')
await Promise.all(updatePromises)
console.log('=== 所有玩家的对手生成完成 ===')
} catch (error) {
console.error('生成对手失败:', error)
throw error
}
}
//
const fetchFinalResults = async () => {
try {
const tournamentId = parseInt(route.query.id)
const response = await getSignUpResultList()
//
const results = response
.filter(player => player.tournament_id === tournamentId)
console.log('原始API数据:', response)
//
const tournamentData = response.filter(player => player.tournament_id === tournamentId)
console.log('筛选后的赛事数据:', tournamentData)
console.log('数据条数:', tournamentData.length)
//
const playerStats = {}
tournamentData.forEach((player, index) => {
const playerName = player.sign_name
const win = parseInt(player.win || 0)
const lose = parseInt(player.lose || 0)
const round = player.round || '0'
console.log(`处理第${index + 1}条数据:`, {
id: player.id,
sign_name: playerName,
round: round,
win: win,
lose: lose,
status: player.status
})
if (!playerStats[playerName]) {
playerStats[playerName] = {
username: playerName,
qq: player.qq || '',
faction: player.faction || 'random',
totalWin: 0,
totalLose: 0,
rounds: []
}
}
playerStats[playerName].totalWin += win
playerStats[playerName].totalLose += lose
playerStats[playerName].rounds.push(round)
})
console.log('分组后的玩家统计:', playerStats)
//
const results = Object.values(playerStats)
.sort((a, b) => {
//
if (b.totalWin !== a.totalWin) {
return b.totalWin - a.totalWin
}
//
return a.totalLose - b.totalLose
})
.map((player, index) => ({
rank: index + 1,
username: player.sign_name,
username: player.username,
qq: player.qq,
faction: player.faction,
score: `${player.win}${player.lose}`
score: `${player.totalWin}${player.totalLose}`,
rounds: player.rounds.join(',')
}))
.sort((a, b) => {
const aScore = parseInt(a.score.split('胜')[0])
const bScore = parseInt(b.score.split('胜')[0])
return bScore - aScore
})
console.log('最终排名结果:', results)
finalResults.value = results
} catch (error) {
console.error('获取最终结果失败:', error)

View File

@ -124,13 +124,27 @@
<button class="btn-submit" @click="handleSubmit">提交报名</button>
</div>
</div>
<!-- 对话框组件 -->
<SuccessDialog
:visible="successDialog.visible"
:message="successDialog.message"
@close="successDialog.visible = false"
/>
<ErrorDialog
:visible="errorDialog.visible"
:message="errorDialog.message"
@close="errorDialog.visible = false"
/>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { addSignUp } from '@/api/tournament'
import {addSignUp, addSignUpResult} from '@/api/tournament.js'
import SuccessDialog from '@/components/SuccessDialog.vue'
import ErrorDialog from '@/components/ErrorDialog.vue'
defineOptions({
name: 'CompetitionSignUp'
@ -158,6 +172,10 @@ const signupForm = ref({
qq: ''
})
// Dialog state
const successDialog = ref({ visible: false, message: '' })
const errorDialog = ref({ visible: false, message: '' })
const formatDate = (date) => {
if (!date) return ''
// //--
@ -190,12 +208,12 @@ const handleSubmit = async () => {
//
if (signupForm.value.type === 'teamname') {
if (!signupForm.value.teamName || !signupForm.value.username) {
alert('请填写完整的队伍信息')
errorDialog.value = { visible: true, message: '请填写完整的队伍信息' }
return
}
} else {
if (!signupForm.value.username) {
alert('请填写完整的个人信息')
errorDialog.value = { visible: true, message: '请填写完整的个人信息' }
return
}
}
@ -203,61 +221,67 @@ const handleSubmit = async () => {
// sign_name
const signName = signupForm.value.username.trim()
if (!signName) {
alert('参赛人员名称不能为空')
errorDialog.value = { visible: true, message: '参赛人员名称不能为空' }
return
}
if (signName.length < 2) {
alert('参赛人员名称至少需要2个字符')
errorDialog.value = { visible: true, message: '参赛人员名称至少需要2个字符' }
return
}
if (signName.length > 20) {
alert('参赛人员名称不能超过20个字符')
errorDialog.value = { visible: true, message: '参赛人员名称不能超过20个字符' }
return
}
// 线
if (!/^[\u4e00-\u9fa5a-zA-Z0-9_]+$/.test(signName)) {
alert('参赛人员名称只能包含中文、英文、数字和下划线')
errorDialog.value = { visible: true, message: '参赛人员名称只能包含中文、英文、数字和下划线' }
return
}
//
if (/^\d+$/.test(signName)) {
alert('参赛人员名称不能为纯数字')
errorDialog.value = { visible: true, message: '参赛人员名称不能为纯数字' }
return
}
// 线
if (/^_+$/.test(signName)) {
alert('参赛人员名称不能为纯下划线')
errorDialog.value = { visible: true, message: '参赛人员名称不能为纯下划线' }
return
}
try {
//
if (!competitionInfo.value.id || !competitionInfo.value.name) {
alert('比赛信息不完整,请返回重试')
errorDialog.value = { visible: true, message: '比赛信息不完整,请返回重试' }
return
}
const submitData = {
id: parseInt(competitionInfo.value.id),
tournament_name: competitionInfo.value.name,
tournament_id: parseInt(competitionInfo.value.id),
type: signupForm.value.type,
team_name: signupForm.value.type === 'teamname' ? signupForm.value.teamName : '',
sign_name: signName,
teamname: signupForm.value.type === 'teamname' ? signupForm.value.teamName : ' ',
username: signupForm.value.username,
faction: signupForm.value.faction,
qq_code: String(competitionInfo.value.qq_code) // qq_code
qq: signupForm.value.qq
}
const signupResultData = {
tournament_id: parseInt(competitionInfo.value.id),
tournament_name: competitionInfo.value.name,
team_name: signupForm.value.type === 'teamname' ? signupForm.value.teamName : ' ',
sign_name: signupForm.value.username,
win: '0',
lose: '0',
status: 'tie',
round: '0',
rival_name: ''
}
console.log('提交的报名数据:', submitData)
const result = await addSignUp(submitData)
console.log('报名结果:', result)
if (result.signup && result.result) {
alert('报名成功!')
const resultSignup = await addSignUpResult(signupResultData)
console.log('提交的报名数据:', submitData, signupResultData)
console.log('报名结果:', result, resultSignup)
successDialog.value = { visible: true, message: '报名成功!' }
setTimeout(() => {
router.push('/competition')
} else {
console.error('报名结果不完整:', result)
throw new Error('报名数据不完整,请重试')
}
}, 1500)
} catch (error) {
console.error('报名失败:', error)
console.error('错误详情:', {
@ -268,13 +292,13 @@ const handleSubmit = async () => {
//
if (error.message.includes('返回数据为空')) {
alert('服务器返回数据为空,请稍后重试')
errorDialog.value = { visible: true, message: '服务器返回数据为空,请稍后重试' }
} else if (error.message.includes('数据不完整')) {
alert('报名数据不完整,请重试')
errorDialog.value = { visible: true, message: '报名数据不完整,请重试' }
} else if (error.message.includes('网络连接')) {
alert('网络连接失败,请检查网络后重试')
errorDialog.value = { visible: true, message: '网络连接失败,请检查网络后重试' }
} else {
alert(error.message || '报名失败,请稍后重试')
errorDialog.value = { visible: true, message: error.message || '报名失败,请稍后重试' }
}
}
}

View File

@ -324,11 +324,13 @@ function handlePasswordChangeError(errorMessage) {
<div class="app">
<nav class="navbar">
<div class="nav-container">
<div class="nav-brand">红色警戒3数据分析中心</div>
<a href="/" class="nav-brand">红色警戒3数据分析中心</a>
<!-- <div class="nav-brand">红色警戒3数据分析中心</div>-->
<button class="mobile-menu-toggle" @click="toggleMobileMenu">
<i class="fas fa-bars"></i>
</button>
<div class="nav-left" :class="{ active: showMobileMenu }">
<a href="/" class="nav-link">首页</a>
<!-- 地图 一级菜单 -->
<div class="nav-dropdown">
<span class="nav-link">地图与作者推荐</span>
@ -343,41 +345,16 @@ function handlePasswordChangeError(errorMessage) {
<span class="nav-link">地形与纹理</span>
<div class="dropdown-content">
<router-link to="/terrain" class="nav-link">地形图列表</router-link>
<!-- <router-link v-if="isLoggedIn" to="/terrainGenerate" class="nav-link" @click.prevent="handleNavClick('/terrainGenerate', ['lv-admin','lv-mod','lv-map','lv-competitor'])">地形纹理合成工具</router-link>-->
<router-link to="/terrainGenerate" class="nav-link" @click.prevent="handleNavClick('/terrainGenerate', ['lv-admin','lv-mod','lv-map','lv-competitor'])">地形纹理合成工具</router-link>
</div>
</div>
<!-- <template v-if="isLoggedIn">-->
<!-- &lt;!&ndash; 在线工具 一级菜单 &ndash;&gt;-->
<!-- <div class="nav-dropdown">-->
<!-- <span class="nav-link">在线工具</span>-->
<!-- <div class="dropdown-content">-->
<!-- <router-link to="/weapon-match" class="nav-link" @click.prevent="handleNavClick('/weapon-match', ['lv-admin','lv-mod'])">Weapon 匹配</router-link>-->
<!-- <router-link to="/PIC2TGA" class="nav-link" @click.prevent="handleNavClick('/PIC2TGA', ['lv-admin','lv-mod','lv-map','lv-competitor'])">在线转tga工具</router-link>-->
<!-- </div>-->
<!-- </div>-->
<!-- &lt;!&ndash; 赛事信息 一级菜单 &ndash;&gt;-->
<!-- <div class="nav-dropdown">-->
<!-- <span class="nav-link">赛事信息</span>-->
<!-- <div class="dropdown-content">-->
<!--&lt;!&ndash; <router-link to="/competition" class="nav-link" @click.prevent="handleNavClick('/competition', ['lv-admin','lv-competitor'])">赛程信息</router-link>&ndash;&gt;-->
<!-- <router-link to="/competition" class="nav-link">赛程信息</router-link>-->
<!-- </div>-->
<!-- </div>-->
<!-- &lt;!&ndash; 公共信息区 一级菜单 &ndash;&gt;-->
<!-- <div class="nav-dropdown">-->
<!-- <span class="nav-link">公共信息区</span>-->
<!-- <div class="dropdown-content">-->
<!-- <router-link to="/demands" class="nav-link">办事大厅</router-link>-->
<!-- </div>-->
<!-- </div>-->
<!-- </template>-->
<!-- 需要登陆才能访问如果没有登陆则点击跳转到登陆页面-->
<!-- 如果登陆了才能执行权限判断-->
<div class="nav-dropdown">
<span class="nav-link">在线工具</span>
<div class="dropdown-content">
<router-link to="/weapon-match" class="nav-link" @click.prevent="handleNavClick('/weapon-match', ['lv-admin','lv-mod'])">Weapon 匹配</router-link>
<router-link to="/configEditor" class="nav-link" @click.prevent="handleNavClick('/configEditor', ['lv-admin','lv-mod'])">Config.xml 编辑器</router-link>
<router-link to="/PIC2TGA" class="nav-link" @click.prevent="handleNavClick('/PIC2TGA', ['lv-admin','lv-mod','lv-map','lv-competitor'])">在线转tga工具</router-link>
</div>
</div>
@ -386,7 +363,7 @@ function handlePasswordChangeError(errorMessage) {
<span class="nav-link">赛事信息</span>
<div class="dropdown-content">
<!-- <router-link to="/competition" class="nav-link" @click.prevent="handleNavClick('/competition', ['lv-admin','lv-competitor'])">赛程信息</router-link>-->
<router-link to="/competition" class="nav-link" @click.prevent="handleNavClick('/competition')">赛程信息</router-link>
<router-link to="competition" class="nav-link" @click.prevent="handleNavClick('/competition')">赛程信息</router-link>
</div>
</div>
<!-- 公共信息区 一级菜单 -->
@ -439,9 +416,15 @@ function handlePasswordChangeError(errorMessage) {
<img src="../assets/logo.png" class="footer-logo">
<span class="footer-title">红色警戒3数据分析中心</span>
</div>
</div>
<div class="footer-bottom">
<p>Byz解忧杂货铺</p>
<div>
<p>Byz解忧杂货铺</p>
</div>
<p class="beian">
<a href="https://beian.miit.gov.cn/" rel="noreferrer" target="_blank">注册号 : 京ICP备2025120142号-1</a>
</p>
<p class="beian">
<img src="../assets/备案图标.png" alt="公安备案图标" class="police-icon"/>
<a href="https://beian.mps.gov.cn/#/query/webSearch?code=11010802045768" rel="noreferrer" target="_blank">京公网安备11010802045768号</a>
@ -453,12 +436,12 @@ function handlePasswordChangeError(errorMessage) {
:message="errorDialogMessage"
@close="errorDialogVisible = false"
/>
<PrivilegeRequestDialog
:visible="privilegeDialogVisible"
:privilegeName="privilegeDialogName"
@close="privilegeDialogVisible = false"
@apply="handlePrivilegeApply"
/>
<!-- <PrivilegeRequestDialog-->
<!-- :visible="privilegeDialogVisible"-->
<!-- :privilegeName="privilegeDialogName"-->
<!-- @close="privilegeDialogVisible = false"-->
<!-- @apply="handlePrivilegeApply"-->
<!-- />-->
<SuccessDialog
:visible="successDialog.visible"
:message="successDialog.message"

File diff suppressed because it is too large Load Diff

1073
src/views/index/Home.vue Normal file

File diff suppressed because it is too large Load Diff