Merge pull request 'feature/login-screen' (#14) from feature/login-screen into master
Reviewed-on: #14
This commit is contained in:
commit
6d6a6cd03a
@ -5,7 +5,7 @@
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<title>红色警戒3数据分析中心</title>
|
||||
<title>红色警戒3工具平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
440
node_modules/.package-lock.json
generated
vendored
440
node_modules/.package-lock.json
generated
vendored
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled2",
|
||||
"version": "0.0.0",
|
||||
"version": "V.1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
@ -54,6 +54,7 @@
|
||||
"resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.27.1.tgz",
|
||||
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
@ -839,6 +840,7 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001716",
|
||||
"electron-to-chromium": "^1.5.149",
|
||||
@ -930,6 +932,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 +996,377 @@
|
||||
"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==",
|
||||
"peer": true,
|
||||
"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 +1424,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 +1829,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 +1850,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",
|
||||
@ -1641,6 +2049,18 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "17.0.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.0.tgz",
|
||||
"integrity": "sha512-KkDYEWEEiYJw/KC+DVm1zzlpMQSMIu6YRltkcCvwheCp8HWPXCk9JwOmHJKBlGfzcpzcIt6x3sMnTsRm/51oDg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@ -1893,11 +2313,17 @@
|
||||
"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",
|
||||
"integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.7"
|
||||
},
|
||||
@ -1944,11 +2370,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",
|
||||
@ -2156,6 +2592,7 @@
|
||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.3.4.tgz",
|
||||
"integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@ -2313,6 +2750,7 @@
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz",
|
||||
"integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.13",
|
||||
"@vue/compiler-sfc": "3.5.13",
|
||||
|
||||
46
node_modules/.vite/deps/_metadata.json
generated
vendored
46
node_modules/.vite/deps/_metadata.json
generated
vendored
@ -1,52 +1,64 @@
|
||||
{
|
||||
"hash": "3498b3cb",
|
||||
"configHash": "9c7a641a",
|
||||
"lockfileHash": "c997fc3c",
|
||||
"browserHash": "38ceb684",
|
||||
"hash": "f6d37eef",
|
||||
"configHash": "4eee8ee7",
|
||||
"lockfileHash": "17b35e94",
|
||||
"browserHash": "38da36af",
|
||||
"optimized": {
|
||||
"axios": {
|
||||
"src": "../../axios/index.js",
|
||||
"file": "axios.js",
|
||||
"fileHash": "97de2fb4",
|
||||
"fileHash": "a5a07628",
|
||||
"needsInterop": false
|
||||
},
|
||||
"d3": {
|
||||
"src": "../../d3/src/index.js",
|
||||
"file": "d3.js",
|
||||
"fileHash": "828cdd5e",
|
||||
"needsInterop": false
|
||||
},
|
||||
"jszip": {
|
||||
"src": "../../jszip/dist/jszip.min.js",
|
||||
"file": "jszip.js",
|
||||
"fileHash": "3cd8a10a",
|
||||
"fileHash": "c0e06a93",
|
||||
"needsInterop": true
|
||||
},
|
||||
"mitt": {
|
||||
"src": "../../mitt/dist/mitt.mjs",
|
||||
"file": "mitt.js",
|
||||
"fileHash": "8e22dbfe",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vue": {
|
||||
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
|
||||
"file": "vue.js",
|
||||
"fileHash": "74f3ac3b",
|
||||
"fileHash": "7a7fc2f0",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vue-router": {
|
||||
"src": "../../vue-router/dist/vue-router.mjs",
|
||||
"file": "vue-router.js",
|
||||
"fileHash": "d2d7abce",
|
||||
"fileHash": "58c64081",
|
||||
"needsInterop": false
|
||||
},
|
||||
"xlsx": {
|
||||
"src": "../../xlsx/xlsx.mjs",
|
||||
"file": "xlsx.js",
|
||||
"fileHash": "dbbdc859",
|
||||
"fileHash": "d95fb4c8",
|
||||
"needsInterop": false
|
||||
},
|
||||
"mitt": {
|
||||
"src": "../../mitt/dist/mitt.mjs",
|
||||
"file": "mitt.js",
|
||||
"fileHash": "ac6eb5ab",
|
||||
"marked": {
|
||||
"src": "../../marked/lib/marked.esm.js",
|
||||
"file": "marked.js",
|
||||
"fileHash": "a5f98500",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
"chunks": {
|
||||
"chunk-U3LI7FBV": {
|
||||
"file": "chunk-U3LI7FBV.js"
|
||||
"chunk-YBGSFZ7G": {
|
||||
"file": "chunk-YBGSFZ7G.js"
|
||||
},
|
||||
"chunk-5FUTL2UF": {
|
||||
"file": "chunk-5FUTL2UF.js"
|
||||
"chunk-FM7WUVZV": {
|
||||
"file": "chunk-FM7WUVZV.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
2
node_modules/.vite/deps/axios.js
generated
vendored
2
node_modules/.vite/deps/axios.js
generated
vendored
@ -1,6 +1,6 @@
|
||||
import {
|
||||
__export
|
||||
} from "./chunk-5FUTL2UF.js";
|
||||
} from "./chunk-FM7WUVZV.js";
|
||||
|
||||
// node_modules/axios/lib/helpers/bind.js
|
||||
function bind(fn, thisArg) {
|
||||
|
||||
2
node_modules/.vite/deps/axios.js.map
generated
vendored
2
node_modules/.vite/deps/axios.js.map
generated
vendored
File diff suppressed because one or more lines are too long
21
node_modules/.vite/deps/chunk-5FUTL2UF.js
generated
vendored
21
node_modules/.vite/deps/chunk-5FUTL2UF.js
generated
vendored
@ -1,21 +0,0 @@
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
||||
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
||||
}) : x)(function(x) {
|
||||
if (typeof require !== "undefined") return require.apply(this, arguments);
|
||||
throw Error('Dynamic require of "' + x + '" is not supported');
|
||||
});
|
||||
var __commonJS = (cb, mod) => function __require2() {
|
||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||
};
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
|
||||
export {
|
||||
__require,
|
||||
__commonJS,
|
||||
__export
|
||||
};
|
||||
7
node_modules/.vite/deps/chunk-5FUTL2UF.js.map
generated
vendored
7
node_modules/.vite/deps/chunk-5FUTL2UF.js.map
generated
vendored
@ -1,7 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
12542
node_modules/.vite/deps/chunk-U3LI7FBV.js
generated
vendored
12542
node_modules/.vite/deps/chunk-U3LI7FBV.js
generated
vendored
File diff suppressed because it is too large
Load Diff
7
node_modules/.vite/deps/chunk-U3LI7FBV.js.map
generated
vendored
7
node_modules/.vite/deps/chunk-U3LI7FBV.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
node_modules/.vite/deps/jszip.js
generated
vendored
2
node_modules/.vite/deps/jszip.js
generated
vendored
@ -1,7 +1,7 @@
|
||||
import {
|
||||
__commonJS,
|
||||
__require
|
||||
} from "./chunk-5FUTL2UF.js";
|
||||
} from "./chunk-FM7WUVZV.js";
|
||||
|
||||
// node_modules/jszip/dist/jszip.min.js
|
||||
var require_jszip_min = __commonJS({
|
||||
|
||||
2
node_modules/.vite/deps/jszip.js.map
generated
vendored
2
node_modules/.vite/deps/jszip.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
node_modules/.vite/deps/mitt.js
generated
vendored
2
node_modules/.vite/deps/mitt.js
generated
vendored
@ -1,4 +1,4 @@
|
||||
import "./chunk-5FUTL2UF.js";
|
||||
import "./chunk-FM7WUVZV.js";
|
||||
|
||||
// node_modules/mitt/dist/mitt.mjs
|
||||
function mitt_default(n) {
|
||||
|
||||
4
node_modules/.vite/deps/vue-router.js
generated
vendored
4
node_modules/.vite/deps/vue-router.js
generated
vendored
@ -16,8 +16,8 @@ import {
|
||||
unref,
|
||||
watch,
|
||||
watchEffect
|
||||
} from "./chunk-U3LI7FBV.js";
|
||||
import "./chunk-5FUTL2UF.js";
|
||||
} from "./chunk-YBGSFZ7G.js";
|
||||
import "./chunk-FM7WUVZV.js";
|
||||
|
||||
// node_modules/@vue/devtools-api/lib/esm/env.js
|
||||
function getDevtoolsGlobalHook() {
|
||||
|
||||
2
node_modules/.vite/deps/vue-router.js.map
generated
vendored
2
node_modules/.vite/deps/vue-router.js.map
generated
vendored
File diff suppressed because one or more lines are too long
4
node_modules/.vite/deps/vue.js
generated
vendored
4
node_modules/.vite/deps/vue.js
generated
vendored
@ -168,8 +168,8 @@ import {
|
||||
withMemo,
|
||||
withModifiers,
|
||||
withScopeId
|
||||
} from "./chunk-U3LI7FBV.js";
|
||||
import "./chunk-5FUTL2UF.js";
|
||||
} from "./chunk-YBGSFZ7G.js";
|
||||
import "./chunk-FM7WUVZV.js";
|
||||
export {
|
||||
BaseTransition,
|
||||
BaseTransitionPropsValidators,
|
||||
|
||||
2
node_modules/.vite/deps/xlsx.js
generated
vendored
2
node_modules/.vite/deps/xlsx.js
generated
vendored
@ -1,4 +1,4 @@
|
||||
import "./chunk-5FUTL2UF.js";
|
||||
import "./chunk-FM7WUVZV.js";
|
||||
|
||||
// node_modules/xlsx/xlsx.mjs
|
||||
var XLSX = {};
|
||||
|
||||
2
node_modules/.vite/deps/xlsx.js.map
generated
vendored
2
node_modules/.vite/deps/xlsx.js.map
generated
vendored
File diff suppressed because one or more lines are too long
10
node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts
generated
vendored
10
node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts
generated
vendored
@ -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
|
||||
|
||||
444
package-lock.json
generated
444
package-lock.json
generated
@ -1,15 +1,17 @@
|
||||
{
|
||||
"name": "untitled2",
|
||||
"version": "0.0.0",
|
||||
"version": "V.1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "untitled2",
|
||||
"version": "0.0.0",
|
||||
"version": "V.1.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.9.0",
|
||||
"d3": "^7.9.0",
|
||||
"jszip": "^3.10.1",
|
||||
"marked": "^17.0.0",
|
||||
"process": "^0.11.10",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.1",
|
||||
@ -72,6 +74,7 @@
|
||||
"resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.27.1.tgz",
|
||||
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
@ -1488,6 +1491,7 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001716",
|
||||
"electron-to-chromium": "^1.5.149",
|
||||
@ -1579,6 +1583,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 +1647,377 @@
|
||||
"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==",
|
||||
"peer": true,
|
||||
"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 +2075,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 +2494,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 +2515,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",
|
||||
@ -2304,6 +2714,18 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "17.0.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.0.tgz",
|
||||
"integrity": "sha512-KkDYEWEEiYJw/KC+DVm1zzlpMQSMIu6YRltkcCvwheCp8HWPXCk9JwOmHJKBlGfzcpzcIt6x3sMnTsRm/51oDg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@ -2556,11 +2978,17 @@
|
||||
"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",
|
||||
"integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.7"
|
||||
},
|
||||
@ -2607,11 +3035,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",
|
||||
@ -2819,6 +3257,7 @@
|
||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.3.4.tgz",
|
||||
"integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@ -2976,6 +3415,7 @@
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz",
|
||||
"integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.13",
|
||||
"@vue/compiler-sfc": "3.5.13",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled2",
|
||||
"version": "0.0.0",
|
||||
"version": "V.1.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@ -10,7 +10,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.9.0",
|
||||
"d3": "^7.9.0",
|
||||
"jszip": "^3.10.1",
|
||||
"marked": "^17.0.0",
|
||||
"process": "^0.11.10",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.1",
|
||||
|
||||
545
public/AlliedAntiAirShip.xml
Normal file
545
public/AlliedAntiAirShip.xml
Normal 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>
|
||||
1577
public/ButtonStateDataCommon.xml
Normal file
1577
public/ButtonStateDataCommon.xml
Normal file
File diff suppressed because it is too large
Load Diff
3253
public/Locomotor.xml
Normal file
3253
public/Locomotor.xml
Normal file
File diff suppressed because it is too large
Load Diff
2291
public/LogicCommand.xml
Normal file
2291
public/LogicCommand.xml
Normal file
File diff suppressed because it is too large
Load Diff
1097
public/LogicCommandSet.xml
Normal file
1097
public/LogicCommandSet.xml
Normal file
File diff suppressed because it is too large
Load Diff
1649
public/SpecialPowerTemplates.xml
Normal file
1649
public/SpecialPowerTemplates.xml
Normal file
File diff suppressed because it is too large
Load Diff
1088
public/UnitAbilityButtonTemplates.xml
Normal file
1088
public/UnitAbilityButtonTemplates.xml
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,8 @@
|
||||
import axios from 'axios';
|
||||
import { logoutUser } from '../utils/jwt'; // logoutUser会处理清除存储和重定向
|
||||
|
||||
const API_BASE_URL = 'https://api.zybdatasupport.online';
|
||||
const API_BASE_URL = 'http://zybdatasupport.online:8000'
|
||||
// const API_BASE_URL = 'http://110.42.61.148/'
|
||||
// const API_BASE_URL = 'https://api.zybdatasupport.online';
|
||||
//const API_BASE_URL = 'http://hk.zybdatasupport.online:8000/';
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
|
||||
16
src/api/info.js
Normal file
16
src/api/info.js
Normal file
@ -0,0 +1,16 @@
|
||||
import axiosInstance from './axiosConfig'
|
||||
|
||||
/**
|
||||
* 获取后端版本信息
|
||||
* GET /info
|
||||
* 基于 axiosInstance 的 baseURL:`http://zybdatasupport.online:8000`
|
||||
* 返回后端提供的原始 JSON 数据
|
||||
*/
|
||||
export const getBackendInfo = async () => {
|
||||
try {
|
||||
const { data } = await axiosInstance.get('/info')
|
||||
return data
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
64
src/api/record.js
Normal file
64
src/api/record.js
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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 - 阵营(allied、soviet、empire、ob、voice、random)
|
||||
* @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 - 阵营(allied、soviet、empire、ob、voice、random)
|
||||
* @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
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
BIN
src/assets/login_5.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
19
src/assets/version.md
Normal file
19
src/assets/version.md
Normal file
@ -0,0 +1,19 @@
|
||||
### 后端
|
||||
## V.2025.07.30.1
|
||||
|
||||
1、修改服务器地址
|
||||
|
||||
|
||||
------
|
||||
### 前端
|
||||
|
||||
## v.1.0.1
|
||||
1、谢谢你gemini3 pro,改了下赛事样式
|
||||
|
||||
|
||||
## v.1.0.0
|
||||
|
||||
1、添加版本信息页面
|
||||
|
||||
2、修改服务器地址
|
||||
|
||||
1012
src/components/DoubleEliminationBracket.vue
Normal file
1012
src/components/DoubleEliminationBracket.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -25,7 +25,7 @@ const props = defineProps({
|
||||
})
|
||||
const emit = defineEmits(['close', 'apply'])
|
||||
|
||||
function handleClose() {
|
||||
function handleClose() {
|
||||
emit('close')
|
||||
}
|
||||
function handleApply() {
|
||||
|
||||
@ -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
112
src/components/backend/AdminChangesPwd.vue
Normal file
112
src/components/backend/AdminChangesPwd.vue
Normal 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>
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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,18 @@ 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'] }
|
||||
},
|
||||
{
|
||||
path: 'version',
|
||||
name: 'VersionInfo',
|
||||
component: () => import('@/views/index/VersionInfo.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -163,7 +183,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
// 登录校验:如果页面需要登录但本地没有有效 token,则跳转登录页
|
||||
if (requiresAuth && !hasValidToken()) {
|
||||
return next({
|
||||
path: '/maps',
|
||||
path: '/',
|
||||
query: { redirect: to.fullPath }
|
||||
})
|
||||
}
|
||||
@ -174,11 +194,11 @@ 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 })
|
||||
}
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
export default router
|
||||
export default router
|
||||
@ -98,7 +98,6 @@ export const loginSuccess = (accessToken, userId) => {
|
||||
if (userId) {
|
||||
localStorage.setItem('user_id', userId);
|
||||
}
|
||||
|
||||
// 设置登录标志
|
||||
justLoggedIn.value = true;
|
||||
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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])
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
<!-- <!– 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>-->
|
||||
|
||||
<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()
|
||||
}
|
||||
@ -34,6 +34,7 @@
|
||||
</div>
|
||||
<select v-model="filterStatus" @change="handleFilter" class="filter-select">
|
||||
<option value="all">全部状态</option>
|
||||
<option value="prepare">筹备中</option>
|
||||
<option value="ongoing">进行中</option>
|
||||
<option value="finished">已结束</option>
|
||||
</select>
|
||||
@ -49,57 +50,60 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-container" :class="{ 'loading': isLoading }">
|
||||
<table class="competition-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th>赛程名称</th>
|
||||
<th>开始时间</th>
|
||||
<th>结束时间</th>
|
||||
<th>状态</th>
|
||||
<th>组织者</th>
|
||||
<th>QQ号</th>
|
||||
<th>赛制类型</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(competition, index) in filteredCompetitions"
|
||||
:key="index"
|
||||
class="competition-row"
|
||||
@click="handleView(competition)">
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td class="competition-name">{{ competition.name }}</td>
|
||||
<td>{{ formatDate(competition.start_time) }}</td>
|
||||
<td>{{ formatDate(competition.end_time) }}</td>
|
||||
<td>
|
||||
<span :class="['status-tag', competition.status]">
|
||||
{{ competition.status === 'prepare' ? '筹备中' :
|
||||
competition.status === 'starting' ? '进行中' : '已结束' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ competition.organizer }}</td>
|
||||
<td>{{ competition.qq_code }}</td>
|
||||
<td>{{ competition.format === 'single' ? '单败淘汰' :
|
||||
competition.format === 'double' ? '双败淘汰' : '积分赛' }}</td>
|
||||
<td class="action-cell">
|
||||
<button class="action-btn view" @click.stop="handleSignUp(competition)" :disabled="competition.status === 'finish'">
|
||||
报名
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="isLoading" class="loading-container">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>正在加载赛程数据...</p>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="isLoading" class="loading-overlay">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
<span>加载中...</span>
|
||||
<!-- 赛程卡片网格 -->
|
||||
<div v-else class="competition-grid">
|
||||
<div
|
||||
v-for="(competition, index) in filteredCompetitions"
|
||||
:key="index"
|
||||
class="competition-card"
|
||||
@click="handleView(competition)"
|
||||
>
|
||||
<div class="card-header">
|
||||
<div class="status-badge" :class="competition.status">
|
||||
{{ getStatusText(competition.status) }}
|
||||
</div>
|
||||
<div class="format-badge">{{ competition.format === 'single' ? '单败' : competition.format === 'double' ? '双败' : '积分' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<h3 class="competition-title">{{ competition.name }}</h3>
|
||||
|
||||
<div class="info-row">
|
||||
<i class="fas fa-user-tie"></i>
|
||||
<span>主办方:{{ competition.organizer }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<i class="fab fa-qq"></i>
|
||||
<span>QQ:{{ competition.qq_code }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row time-row">
|
||||
<i class="far fa-calendar-alt"></i>
|
||||
<span>{{ formatDate(competition.start_time) }} - {{ formatDate(competition.end_time) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<button
|
||||
class="action-btn view"
|
||||
@click.stop="handleSignUp(competition)"
|
||||
:disabled="competition.status === 'finish'"
|
||||
:class="{ 'disabled': competition.status === 'finish' }"
|
||||
>
|
||||
{{ competition.status === 'finish' ? '已结束' : '立即报名' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 空状态显示 -->
|
||||
<div v-else-if="filteredCompetitions.length === 0" class="empty-state">
|
||||
<div v-if="filteredCompetitions.length === 0" class="empty-state">
|
||||
<i class="fas fa-calendar-times"></i>
|
||||
<p>暂无赛程信息</p>
|
||||
</div>
|
||||
@ -110,7 +114,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()
|
||||
|
||||
@ -135,9 +139,13 @@ const filteredCompetitions = computed(() => {
|
||||
|
||||
// 状态过滤
|
||||
if (filterStatus.value !== 'all') {
|
||||
result = result.filter(comp =>
|
||||
filterStatus.value === 'ongoing' ? comp.status === 'starting' : comp.status === 'finish'
|
||||
)
|
||||
if (filterStatus.value === 'ongoing') {
|
||||
result = result.filter(comp => comp.status === 'starting')
|
||||
} else if (filterStatus.value === 'finished') {
|
||||
result = result.filter(comp => comp.status === 'finish')
|
||||
} else if (filterStatus.value === 'prepare') {
|
||||
result = result.filter(comp => comp.status === 'prepare')
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
@ -145,9 +153,19 @@ const filteredCompetitions = computed(() => {
|
||||
|
||||
// 方法
|
||||
const formatDate = (date) => {
|
||||
if (!date) return ''
|
||||
return date.replace(/\//g, '-')
|
||||
}
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const map = {
|
||||
'prepare': '筹备中',
|
||||
'starting': '进行中',
|
||||
'finish': '已结束'
|
||||
}
|
||||
return map[status] || status
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
// 搜索时重置过滤
|
||||
}
|
||||
@ -213,60 +231,74 @@ refreshCompetitions()
|
||||
|
||||
<style scoped>
|
||||
.competition-page {
|
||||
padding: 16px;
|
||||
padding: 24px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 22px;
|
||||
font-size: 28px;
|
||||
color: #1a237e;
|
||||
margin: 0 0 6px 0;
|
||||
margin: 0 0 8px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-subtitle {
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 24px;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
gap: 16px;
|
||||
background: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.left-actions,
|
||||
.right-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
padding: 6px 10px 6px 28px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
max-width: 220px;
|
||||
padding: 8px 12px 8px 32px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.search-box input:focus {
|
||||
border-color: #409EFF;
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #999;
|
||||
@ -274,215 +306,259 @@ refreshCompetitions()
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
background: white;
|
||||
min-width: 100px;
|
||||
min-width: 120px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.filter-select:focus {
|
||||
border-color: #409EFF;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn-common {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #b6d2ff;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-gradient {
|
||||
background: linear-gradient(90deg, #71eaeb 0%, #416bdf 100%);
|
||||
background: linear-gradient(135deg, #71eaeb 0%, #416bdf 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 4px 12px rgba(65, 107, 223, 0.2);
|
||||
}
|
||||
|
||||
.btn-gradient:hover {
|
||||
background: linear-gradient(90deg, #416bdf 0%, #71eaeb 100%);
|
||||
background: linear-gradient(135deg, #416bdf 0%, #71eaeb 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 6px 16px rgba(65, 107, 223, 0.3);
|
||||
}
|
||||
|
||||
.btn-light {
|
||||
background: white;
|
||||
background: #f0f7ff;
|
||||
color: #2563eb;
|
||||
border: 1px solid #dbeafe;
|
||||
}
|
||||
|
||||
.btn-light:hover {
|
||||
background: #f5f7fa;
|
||||
background: #dbeafe;
|
||||
border-color: #2563eb;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
/* 网格布局 */
|
||||
.competition-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.competition-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05);
|
||||
overflow-x: auto;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
min-height: 200px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
border: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.competition-table {
|
||||
width: 100%;
|
||||
min-width: 800px;
|
||||
border-collapse: collapse;
|
||||
.competition-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
|
||||
border-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.competition-table th,
|
||||
.competition-table td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
.card-header {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-badge.prepare { background-color: #fff7e6; color: #fa8c16; border: 1px solid #ffd591; }
|
||||
.status-badge.starting { background-color: #f6ffed; color: #52c41a; border: 1px solid #b7eb8f; }
|
||||
.status-badge.finish { background-color: #f5f5f5; color: #8c8c8c; border: 1px solid #d9d9d9; }
|
||||
|
||||
.format-badge {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
background: #e6f7ff;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #bae7ff;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 20px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.competition-title {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a237e;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-row i {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.time-row {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px dashed #f0f0f0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.competition-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #1a237e;
|
||||
}
|
||||
|
||||
.competition-row {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.competition-row:hover {
|
||||
background-color: #f0f7ff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.competition-name {
|
||||
font-weight: 500;
|
||||
color: #1a237e;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-tag.prepare { background-color: #e6a23c; color: #fff; }
|
||||
.status-tag.starting { background-color: #67c23a; color: #fff; }
|
||||
.status-tag.finish { background-color: #909399; color: #fff; }
|
||||
|
||||
.action-cell {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
.card-footer {
|
||||
padding: 16px;
|
||||
border-top: 1px solid #f5f5f5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
background: linear-gradient(90deg, #71eaeb 0%, #416bdf 100%);
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.action-btn.disabled {
|
||||
background: #f5f5f5;
|
||||
color: #999;
|
||||
cursor: not-allowed;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 30px;
|
||||
grid-column: 1 / -1;
|
||||
padding: 60px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
color: #d9d9d9;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #409EFF;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #fef0f0;
|
||||
color: #f56c6c;
|
||||
padding: 10px 14px;
|
||||
border-radius: 4px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #fde2e2;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
margin-left: auto;
|
||||
padding: 4px 10px;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
background: #f56c6c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
color: #409EFF;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.table-container.loading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.btn-common:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
background: #e0e0e0 !important;
|
||||
color: #b0b0b0 !important;
|
||||
cursor: not-allowed;
|
||||
border: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.competition-page {
|
||||
padding: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.left-actions, .right-actions {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin: 0 -12px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.competition-table th, .competition-table td {
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.search-box input, .filter-select {
|
||||
.search-box {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-size: 10px;
|
||||
padding: 2px 5px;
|
||||
|
||||
.filter-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
974
src/views/competition/CompetitionDetail.vue
Normal file
974
src/views/competition/CompetitionDetail.vue
Normal file
@ -0,0 +1,974 @@
|
||||
<template>
|
||||
<div class="competition-detail-container">
|
||||
<!-- 顶部横幅 -->
|
||||
<div class="detail-header">
|
||||
<div class="header-content">
|
||||
<div class="title-section">
|
||||
<button class="back-btn" @click="goBack">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</button>
|
||||
<h1>{{ competition.name }}</h1>
|
||||
<span class="status-badge" :class="competition.status">
|
||||
{{ formatStatus(competition.status) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<i class="fas fa-trophy"></i>
|
||||
<span>赛制: {{ competition.format }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<i class="fas fa-user-tie"></i>
|
||||
<span>举办方: {{ competition.organizer }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<i class="fas fa-calendar-alt"></i>
|
||||
<span>时间: {{ formatDate(competition.start_time) }} - {{ formatDate(competition.end_time) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区 -->
|
||||
<div class="main-content">
|
||||
<!-- 侧边导航/标签页 -->
|
||||
<div class="tabs-nav">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
class="tab-btn"
|
||||
:class="{ active: currentTab === tab.id }"
|
||||
@click="currentTab = tab.id"
|
||||
>
|
||||
<i :class="tab.icon"></i>
|
||||
{{ tab.name }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 标签页内容 -->
|
||||
<div class="tab-content">
|
||||
<!-- 概览 -->
|
||||
<div v-if="currentTab === 'overview'" class="tab-pane fade-in">
|
||||
<div class="overview-card">
|
||||
<h2>赛事公告</h2>
|
||||
<div class="markdown-body" v-html="renderedDescription"></div>
|
||||
</div>
|
||||
|
||||
<div class="overview-card" v-if="competition.status === 'finish'">
|
||||
<h2>最终排名</h2>
|
||||
<RankContestant :tournamentId="competition.id" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 对阵图 -->
|
||||
<div v-if="currentTab === 'bracket'" class="tab-pane fade-in">
|
||||
<div v-if="competition.status === 'prepare'" class="empty-state">
|
||||
<i class="fas fa-hourglass-start"></i>
|
||||
<p>比赛尚未开始,请等待组织者生成对阵图</p>
|
||||
<button v-if="isOrganizer" class="btn-primary" @click="startTournament">
|
||||
开始比赛并生成对阵
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="bracket-wrapper">
|
||||
<TournamentBracket
|
||||
v-if="competition.format === '单败淘汰'"
|
||||
:tournamentId="competition.id"
|
||||
@refreshPlayers="fetchRegisteredPlayers"
|
||||
/>
|
||||
<DoubleEliminationBracket
|
||||
v-else-if="competition.format === '双败淘汰'"
|
||||
:tournamentId="competition.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 参赛人员 -->
|
||||
<div v-if="currentTab === 'participants'" class="tab-pane fade-in">
|
||||
<div class="participants-header">
|
||||
<h2>参赛选手 ({{ registeredPlayers.length }})</h2>
|
||||
<div class="actions" v-if="isOrganizer && competition.status === 'prepare'">
|
||||
<button class="btn-secondary" @click="shufflePlayers" title="随机打乱顺序">
|
||||
<i class="fas fa-random"></i> 随机种子
|
||||
</button>
|
||||
<button class="btn-primary" @click="openAddPlayerModal">
|
||||
<i class="fas fa-plus"></i> 添加选手
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="participants-list">
|
||||
<div v-for="(player, index) in registeredPlayers" :key="player.id" class="participant-card">
|
||||
<div class="player-rank">种子 #{{ index + 1 }}</div>
|
||||
<div class="player-info">
|
||||
<div class="player-name">{{ player.sign_name }}</div>
|
||||
<div class="player-team" v-if="player.team_name">{{ player.team_name }}</div>
|
||||
</div>
|
||||
<div class="player-actions" v-if="isOrganizer && competition.status === 'prepare'">
|
||||
<button class="icon-btn" @click="movePlayerUp(index)" :disabled="index === 0" title="上移">
|
||||
<i class="fas fa-arrow-up"></i>
|
||||
</button>
|
||||
<button class="icon-btn" @click="movePlayerDown(index)" :disabled="index === registeredPlayers.length - 1" title="下移">
|
||||
<i class="fas fa-arrow-down"></i>
|
||||
</button>
|
||||
<button class="icon-btn delete" @click="confirmRemovePlayer(player)">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设置 (仅组织者) -->
|
||||
<div v-if="currentTab === 'settings' && isOrganizer" class="tab-pane fade-in">
|
||||
<div class="settings-card">
|
||||
<h2>赛事管理</h2>
|
||||
<form @submit.prevent="handleUpdateTournament">
|
||||
<div class="setting-group">
|
||||
<label>赛事名称</label>
|
||||
<input v-model="editingCompetition.name" type="text" required class="form-input" />
|
||||
</div>
|
||||
|
||||
<div class="setting-group">
|
||||
<label>举办方</label>
|
||||
<input v-model="editingCompetition.organizer" type="text" required class="form-input" />
|
||||
</div>
|
||||
|
||||
<div class="setting-group">
|
||||
<label>开始时间</label>
|
||||
<input v-model="editingCompetition.start_time" type="date" class="form-input" />
|
||||
</div>
|
||||
|
||||
<div class="setting-group">
|
||||
<label>结束时间</label>
|
||||
<input v-model="editingCompetition.end_time" type="date" class="form-input" />
|
||||
</div>
|
||||
|
||||
<div class="setting-group">
|
||||
<label>赛事状态</label>
|
||||
<select v-model="editingCompetition.status" class="form-select">
|
||||
<option value="prepare">筹备中</option>
|
||||
<option value="starting">进行中</option>
|
||||
<option value="finish">已结束</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="setting-actions">
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fas fa-save"></i> 保存修改
|
||||
</button>
|
||||
<button type="button" class="btn-danger" @click="confirmDeleteTournament">
|
||||
<i class="fas fa-trash-alt"></i> 删除赛事
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</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 v-if="showDeleteConfirm" class="modal-overlay">
|
||||
<div class="modal-content">
|
||||
<h3>确认删除</h3>
|
||||
<p>确定要删除该赛事吗?此操作不可恢复。</p>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-secondary" @click="showDeleteConfirm = false">取消</button>
|
||||
<button class="btn-danger" @click="handleDeleteTournament">确认删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加选手弹窗 -->
|
||||
<div v-if="showAddPlayerModal" class="modal-overlay">
|
||||
<div class="modal-content">
|
||||
<h3>添加选手</h3>
|
||||
<div class="form-group">
|
||||
<label>选手名称</label>
|
||||
<input v-model="newPlayerName" placeholder="请输入选手名称" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>QQ号 (可选)</label>
|
||||
<input v-model="newPlayerQQ" placeholder="请输入选手QQ号" class="form-input" />
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-secondary" @click="showAddPlayerModal = false">取消</button>
|
||||
<button class="btn-primary" @click="handleAddPlayer">添加</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { marked } from 'marked'
|
||||
import {
|
||||
getTournamentList,
|
||||
updateTournament,
|
||||
deleteTournament,
|
||||
getSignUpList,
|
||||
deleteSignUp,
|
||||
addSignUpResult,
|
||||
getSignUpResultList,
|
||||
updateSignUpResult,
|
||||
addSignUp
|
||||
} from '@/api/tournament'
|
||||
import { getStoredUser } from '@/utils/jwt.js'
|
||||
import TournamentBracket from '@/components/TournamentBracket.vue'
|
||||
import DoubleEliminationBracket from '@/components/DoubleEliminationBracket.vue'
|
||||
import RankContestant from '@/components/RankContestant.vue'
|
||||
import SuccessDialog from '@/components/SuccessDialog.vue'
|
||||
import ErrorDialog from '@/components/ErrorDialog.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
// 状态变量
|
||||
const competition = ref({})
|
||||
const editingCompetition = ref({}) // 用于编辑的副本
|
||||
const registeredPlayers = ref([])
|
||||
const isOrganizer = ref(false)
|
||||
const isLoading = ref(false)
|
||||
const currentTab = ref('overview')
|
||||
const showDeleteConfirm = ref(false)
|
||||
const showAddPlayerModal = ref(false)
|
||||
const newPlayerName = ref('')
|
||||
const newPlayerQQ = ref('')
|
||||
|
||||
// 弹窗状态
|
||||
const successDialog = ref({ visible: false, message: '' })
|
||||
const errorDialog = ref({ visible: false, message: '' })
|
||||
|
||||
// 标签页配置
|
||||
const tabs = [
|
||||
{ id: 'overview', name: '概览', icon: 'fas fa-info-circle' },
|
||||
{ id: 'bracket', name: '对阵图', icon: 'fas fa-sitemap' },
|
||||
{ id: 'participants', name: '参赛人员', icon: 'fas fa-users' },
|
||||
{ id: 'settings', name: '设置', icon: 'fas fa-cog' }
|
||||
]
|
||||
|
||||
// Markdown 渲染
|
||||
const renderedDescription = computed(() => {
|
||||
return competition.value.description ? marked(competition.value.description) : '暂无描述'
|
||||
})
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
await fetchTournamentDetail()
|
||||
})
|
||||
|
||||
// 监听 competition 变化,更新 editingCompetition
|
||||
watch(competition, (newVal) => {
|
||||
if (newVal) {
|
||||
editingCompetition.value = { ...newVal }
|
||||
// 格式化日期以适应 input[type="date"]
|
||||
if (editingCompetition.value.start_time) {
|
||||
editingCompetition.value.start_time = formatDateForInput(editingCompetition.value.start_time)
|
||||
}
|
||||
if (editingCompetition.value.end_time) {
|
||||
editingCompetition.value.end_time = formatDateForInput(editingCompetition.value.end_time)
|
||||
}
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
router.push('/competition')
|
||||
}
|
||||
|
||||
// 获取赛事详情
|
||||
const fetchTournamentDetail = async () => {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const tournamentId = parseInt(route.query.id)
|
||||
const data = await getTournamentList()
|
||||
const tournament = data.find(item => item.id === tournamentId)
|
||||
|
||||
if (tournament) {
|
||||
competition.value = {
|
||||
...tournament,
|
||||
format: formatType(tournament.format)
|
||||
}
|
||||
|
||||
// 权限判断
|
||||
const user = getStoredUser()
|
||||
if (user && user.qq_code && tournament.qq_code) {
|
||||
isOrganizer.value = String(user.qq_code) === String(tournament.qq_code)
|
||||
}
|
||||
|
||||
// 获取报名玩家
|
||||
await fetchRegisteredPlayers()
|
||||
} else {
|
||||
errorDialog.value = { visible: true, message: '未找到赛事信息' }
|
||||
router.push('/competition')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取赛事详情失败:', error)
|
||||
errorDialog.value = { visible: true, message: '获取赛事详情失败' }
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取报名玩家列表
|
||||
const fetchRegisteredPlayers = async () => {
|
||||
try {
|
||||
const res = await getSignUpList()
|
||||
// 过滤出当前赛事的报名者
|
||||
registeredPlayers.value = res.filter(item =>
|
||||
String(item.tournament_id) === String(competition.value.id)
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('获取报名列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化赛制
|
||||
const formatType = (type) => {
|
||||
const map = {
|
||||
'single_elimination': '单败淘汰',
|
||||
'double_elimination': '双败淘汰',
|
||||
'round_robin': '循环赛'
|
||||
}
|
||||
return map[type] || type
|
||||
}
|
||||
|
||||
// 格式化日期 (用于显示)
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return '待定'
|
||||
return new Date(dateStr).toLocaleDateString()
|
||||
}
|
||||
|
||||
// 格式化日期 (用于 input type="date")
|
||||
const formatDateForInput = (dateStr) => {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
// 格式化状态
|
||||
const formatStatus = (status) => {
|
||||
const map = {
|
||||
'prepare': '筹备中',
|
||||
'starting': '进行中',
|
||||
'finish': '已结束'
|
||||
}
|
||||
return map[status] || status
|
||||
}
|
||||
|
||||
// 随机打乱选手
|
||||
const shufflePlayers = () => {
|
||||
registeredPlayers.value.sort(() => Math.random() - 0.5)
|
||||
successDialog.value = { visible: true, message: '选手顺序已随机打乱' }
|
||||
}
|
||||
|
||||
// 上移选手
|
||||
const movePlayerUp = (index) => {
|
||||
if (index > 0) {
|
||||
const temp = registeredPlayers.value[index]
|
||||
registeredPlayers.value[index] = registeredPlayers.value[index - 1]
|
||||
registeredPlayers.value[index - 1] = temp
|
||||
}
|
||||
}
|
||||
|
||||
// 下移选手
|
||||
const movePlayerDown = (index) => {
|
||||
if (index < registeredPlayers.value.length - 1) {
|
||||
const temp = registeredPlayers.value[index]
|
||||
registeredPlayers.value[index] = registeredPlayers.value[index + 1]
|
||||
registeredPlayers.value[index + 1] = temp
|
||||
}
|
||||
}
|
||||
|
||||
// 打开添加选手弹窗
|
||||
const openAddPlayerModal = () => {
|
||||
newPlayerName.value = ''
|
||||
newPlayerQQ.value = ''
|
||||
showAddPlayerModal.value = true
|
||||
}
|
||||
|
||||
// 添加选手
|
||||
const handleAddPlayer = async () => {
|
||||
if (!newPlayerName.value.trim()) {
|
||||
errorDialog.value = { visible: true, message: '请输入选手名称' }
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await addSignUp({
|
||||
tournament_id: competition.value.id,
|
||||
tournament_name: competition.value.name,
|
||||
sign_name: newPlayerName.value,
|
||||
qq: newPlayerQQ.value || '', // 关联QQ
|
||||
contact_info: ''
|
||||
})
|
||||
await fetchRegisteredPlayers()
|
||||
showAddPlayerModal.value = false
|
||||
successDialog.value = { visible: true, message: '添加成功' }
|
||||
} catch (error) {
|
||||
console.error('添加选手失败:', error)
|
||||
errorDialog.value = { visible: true, message: '添加选手失败' }
|
||||
}
|
||||
}
|
||||
|
||||
// 确认删除选手
|
||||
const confirmRemovePlayer = async (player) => {
|
||||
if (confirm(`确定要移除选手 ${player.sign_name} 吗?`)) {
|
||||
try {
|
||||
await deleteSignUp(player.id)
|
||||
await fetchRegisteredPlayers()
|
||||
successDialog.value = { visible: true, message: '移除成功' }
|
||||
} catch (error) {
|
||||
console.error('移除选手失败:', error)
|
||||
errorDialog.value = { visible: true, message: '移除选手失败' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始比赛
|
||||
const startTournament = async () => {
|
||||
if (registeredPlayers.value.length < 2) {
|
||||
errorDialog.value = { visible: true, message: '至少需要2名选手才能开始比赛' }
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 更新赛事状态
|
||||
await updateTournament(competition.value.id, {
|
||||
...competition.value,
|
||||
status: 'starting'
|
||||
})
|
||||
competition.value.status = 'starting'
|
||||
|
||||
// 2. 生成对阵
|
||||
await generateOpponentsForAllPlayers()
|
||||
|
||||
successDialog.value = { visible: true, message: '比赛已开始,对阵图生成中...' }
|
||||
currentTab.value = 'bracket'
|
||||
} catch (error) {
|
||||
console.error('开始比赛失败:', error)
|
||||
errorDialog.value = { visible: true, message: '开始比赛失败' }
|
||||
}
|
||||
}
|
||||
|
||||
// 生成标准种子顺序
|
||||
const getStandardBracketOrder = (n) => {
|
||||
if (n <= 0) return [];
|
||||
let rounds = Math.ceil(Math.log2(n));
|
||||
let bracket = [1, 2];
|
||||
for (let i = 1; i < rounds; i++) {
|
||||
let nextBracket = [];
|
||||
let sum = Math.pow(2, i + 1) + 1;
|
||||
for (let j = 0; j < bracket.length; j++) {
|
||||
nextBracket.push(bracket[j]);
|
||||
nextBracket.push(sum - bracket[j]);
|
||||
}
|
||||
bracket = nextBracket;
|
||||
}
|
||||
return bracket;
|
||||
}
|
||||
|
||||
// 生成对阵逻辑 (基于当前列表顺序作为种子顺序)
|
||||
const generateOpponentsForAllPlayers = async () => {
|
||||
const players = registeredPlayers.value
|
||||
const totalPlayers = players.length
|
||||
|
||||
// 计算需要的总槽位数(最近的2的幂)
|
||||
const nextPowerOf2 = Math.pow(2, Math.ceil(Math.log2(totalPlayers)))
|
||||
|
||||
// 获取标准种子顺序
|
||||
const order = getStandardBracketOrder(totalPlayers)
|
||||
|
||||
// 遍历种子顺序,每两个一组生成对阵
|
||||
for (let i = 0; i < order.length; i += 2) {
|
||||
const seedA = order[i]
|
||||
const seedB = order[i+1]
|
||||
|
||||
// 注意:seed是1-based,数组是0-based
|
||||
const pA = players[seedA - 1]
|
||||
const pB = players[seedB - 1]
|
||||
|
||||
if (pA && pB) {
|
||||
// pA vs pB
|
||||
await createMatchRecord(pA, pB.sign_name)
|
||||
await createMatchRecord(pB, pA.sign_name)
|
||||
} else if (pA && !pB) {
|
||||
// pA vs 轮空
|
||||
await createMatchRecord(pA, '轮空')
|
||||
} else if (!pA && pB) {
|
||||
// pB vs 轮空 (理论上不应该发生,因为我们按顺序填充)
|
||||
await createMatchRecord(pB, '轮空')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建比赛记录辅助函数
|
||||
const createMatchRecord = async (player, rivalName) => {
|
||||
try {
|
||||
await addSignUpResult({
|
||||
tournament_id: competition.value.id,
|
||||
tournament_name: competition.value.name,
|
||||
team_name: player.team_name || null,
|
||||
sign_name: player.sign_name,
|
||||
win: '0',
|
||||
lose: '0',
|
||||
status: 'tie',
|
||||
round: '0',
|
||||
rival_name: rivalName,
|
||||
qq_code: player.qq || '' // 关联QQ
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`创建比赛记录失败 (${player.sign_name} vs ${rivalName}):`, error)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新赛事信息
|
||||
const handleUpdateTournament = async () => {
|
||||
try {
|
||||
// 反向映射 format
|
||||
const formatMap = {
|
||||
'单败淘汰': 'single_elimination',
|
||||
'双败淘汰': 'double_elimination',
|
||||
'循环赛': 'round_robin'
|
||||
}
|
||||
const rawFormat = formatMap[editingCompetition.value.format] || editingCompetition.value.format
|
||||
|
||||
const updateData = {
|
||||
...editingCompetition.value,
|
||||
format: rawFormat,
|
||||
// 确保日期格式正确 (YYYY/MM/DD)
|
||||
start_time: editingCompetition.value.start_time ? editingCompetition.value.start_time.replace(/-/g, '/') : '',
|
||||
end_time: editingCompetition.value.end_time ? editingCompetition.value.end_time.replace(/-/g, '/') : ''
|
||||
}
|
||||
|
||||
await updateTournament(competition.value.id, updateData)
|
||||
|
||||
// 更新本地状态
|
||||
competition.value = { ...editingCompetition.value }
|
||||
successDialog.value = { visible: true, message: '赛事信息已更新' }
|
||||
} catch (error) {
|
||||
console.error('更新赛事失败:', error)
|
||||
errorDialog.value = { visible: true, message: '更新赛事失败' }
|
||||
}
|
||||
}
|
||||
|
||||
// 确认删除赛事
|
||||
const confirmDeleteTournament = () => {
|
||||
showDeleteConfirm.value = true
|
||||
}
|
||||
|
||||
// 删除赛事
|
||||
const handleDeleteTournament = async () => {
|
||||
try {
|
||||
await deleteTournament(competition.value.id)
|
||||
showDeleteConfirm.value = false
|
||||
router.push('/competition')
|
||||
} catch (error) {
|
||||
console.error('删除赛事失败:', error)
|
||||
errorDialog.value = { visible: true, message: '删除赛事失败' }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.competition-detail-container {
|
||||
min-height: 100vh;
|
||||
background: #f5f7fa;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
background: white;
|
||||
padding: 20px 40px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.05);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 20px;
|
||||
color: #606266;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background: #f0f2f5;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.prepare { background: #e6a23c; color: white; }
|
||||
.status-badge.starting { background: #67c23a; color: white; }
|
||||
.status-badge.finish { background: #909399; color: white; }
|
||||
|
||||
.info-grid {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
max-width: 1200px;
|
||||
margin: 20px auto;
|
||||
width: 100%;
|
||||
gap: 20px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.tabs-nav {
|
||||
width: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
color: #606266;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
text-align: left;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
background: #f5f7fa;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
background: #409EFF;
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
flex: 1;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
min-height: 600px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.overview-card {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.overview-card h2 {
|
||||
font-size: 18px;
|
||||
color: #303133;
|
||||
margin-bottom: 15px;
|
||||
border-left: 4px solid #409EFF;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 400px;
|
||||
color: #909399;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 48px;
|
||||
color: #dcdfe6;
|
||||
}
|
||||
|
||||
.bracket-wrapper {
|
||||
height: 100%;
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.participants-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.participants-header h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.participants-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.participant-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
background: #f9fafc;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.participant-card:hover {
|
||||
border-color: #c6e2ff;
|
||||
background: #ecf5ff;
|
||||
}
|
||||
|
||||
.player-rank {
|
||||
width: 80px;
|
||||
font-weight: bold;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.player-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.player-name {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.player-team {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.player-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: #606266;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s;
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.icon-btn:hover:not(:disabled) {
|
||||
color: #409EFF;
|
||||
border-color: #c6e2ff;
|
||||
}
|
||||
|
||||
.icon-btn:disabled {
|
||||
color: #c0c4cc;
|
||||
cursor: not-allowed;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.icon-btn.delete:hover {
|
||||
color: #F56C6C;
|
||||
border-color: #fbc4c4;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.setting-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.setting-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input, .form-select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.form-input:focus, .form-select:focus {
|
||||
border-color: #409EFF;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.setting-actions {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.btn-primary, .btn-secondary, .btn-danger {
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #409EFF;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #66b1ff;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
border: 1px solid #dcdfe6;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
border-color: #409EFF;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #F56C6C;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #f78989;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
width: 400px;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.modal-content h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
@ -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 || '报名失败,请稍后重试' }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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">-->
|
||||
<!-- <!– 在线工具 一级菜单 –>-->
|
||||
<!-- <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>-->
|
||||
<!-- <!– 赛事信息 一级菜单 –>-->
|
||||
<!-- <div class="nav-dropdown">-->
|
||||
<!-- <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">赛程信息</router-link>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <!– 公共信息区 一级菜单 –>-->
|
||||
<!-- <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>
|
||||
<!-- 公共信息区 一级菜单 -->
|
||||
@ -396,8 +373,12 @@ function handlePasswordChangeError(errorMessage) {
|
||||
<router-link to="/demands" class="nav-link" @click.prevent="handleNavClick('/demands')">办事大厅</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 版本更新 -->
|
||||
<div class="nav-dropdown">
|
||||
<router-link to="/version" class="nav-link">版本信息</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nav-right" :class="{ active: showMobileMenu }">
|
||||
<router-link v-if="!isLoggedIn" to="/backend/login" class="nav-link login-btn">
|
||||
<i class="fas fa-user"></i>
|
||||
@ -437,11 +418,17 @@ function handlePasswordChangeError(errorMessage) {
|
||||
<div class="footer-top">
|
||||
<div class="footer-brand">
|
||||
<img src="../assets/logo.png" class="footer-logo">
|
||||
<span class="footer-title">红色警戒3数据分析中心</span>
|
||||
<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 +440,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
1783
src/views/index/ConfigEditor.vue
Normal file
1783
src/views/index/ConfigEditor.vue
Normal file
File diff suppressed because it is too large
Load Diff
1093
src/views/index/Home.vue
Normal file
1093
src/views/index/Home.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -74,7 +74,7 @@
|
||||
error: null,
|
||||
currentPage: 1,
|
||||
itemsPerPage: 100,
|
||||
apiBaseUrl: 'https://api.zybdatasupport.online',
|
||||
apiBaseUrl: 'http://zybdatasupport.online:8000',
|
||||
categoryList: [],
|
||||
selectedCategory: '全部',
|
||||
};
|
||||
|
||||
103
src/views/index/VersionInfo.vue
Normal file
103
src/views/index/VersionInfo.vue
Normal file
@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div class="version-page">
|
||||
<div class="page-header">
|
||||
<h1>版本信息</h1>
|
||||
<div class="header-subtitle">
|
||||
<span>当前前端应用版本:{{ appVersion }}</span>
|
||||
<br />
|
||||
<span>当前后端应用版本:V.{{ backendVersion }}</span>
|
||||
<h3>联系邮箱</h3>
|
||||
<ul>
|
||||
<li v-for="(email, name) in contactEmails" :key="name">
|
||||
{{ name }}: {{ email }}
|
||||
</li>
|
||||
<li>kuangisa: 1549184870@qq.com</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<h2>更新日志</h2>
|
||||
<div class="md-content" v-html="mdHtml"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import pkg from '../../../package.json'
|
||||
import { marked } from 'marked'
|
||||
import versionMd from '@/assets/version.md?raw'
|
||||
import { getBackendInfo } from '@/api/info'
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const backendVersion = ref('')
|
||||
const contactEmails = ref({})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const info = await getBackendInfo()
|
||||
backendVersion.value = info.version || '未知'
|
||||
if (info.contact && info.contact.email) {
|
||||
try {
|
||||
const emailString = info.contact.email.replace(/'/g, '"')
|
||||
contactEmails.value = JSON.parse(emailString)
|
||||
} catch (e) {
|
||||
contactEmails.value = {}
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
backendVersion.value = '获取失败'
|
||||
}
|
||||
})
|
||||
|
||||
const appVersion = pkg.version || '未知'
|
||||
const mdHtml = marked.parse(versionMd || '')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.header-subtitle {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.header-subtitle span {
|
||||
display: inline-block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.header-subtitle h3 {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-subtitle ul {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.header-subtitle li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.md-content {
|
||||
line-height: 1.8;
|
||||
}
|
||||
.md-content h1, .md-content h2, .md-content h3 {
|
||||
margin: 12px 0 8px;
|
||||
}
|
||||
.md-content p {
|
||||
margin: 8px 0;
|
||||
}
|
||||
.md-content ul, .md-content ol {
|
||||
padding-left: 20px;
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user