Compare commits
6 Commits
server-fea
...
server
| Author | SHA1 | Date | |
|---|---|---|---|
| 04f8bd7e88 | |||
| 41268e5a62 | |||
| ddef52e6ce | |||
| 2d417cd631 | |||
| 5f6d43e66b | |||
| 5fad2f97f8 |
@@ -864,9 +864,10 @@
|
||||
"role": "manager"
|
||||
}
|
||||
],
|
||||
"musicConfig": {
|
||||
"music": {
|
||||
"enabled": true,
|
||||
"filePath": "/assets/music/1765778401201.mp3"
|
||||
"filePath": "",
|
||||
"volume": "1"
|
||||
},
|
||||
"displayConfig": {
|
||||
"showBonusModule": true,
|
||||
@@ -954,9 +955,5 @@
|
||||
"battleEndTime": {
|
||||
"date": "2026-02-08",
|
||||
"time": "00:00:00"
|
||||
},
|
||||
"music": {
|
||||
"enabled": true,
|
||||
"filePath": "/assets/music/1765778401201.mp3"
|
||||
}
|
||||
}
|
||||
379
package-lock.json
generated
379
package-lock.json
generated
@@ -9,14 +9,13 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.2.1",
|
||||
"express": "^5.1.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"vue": "^3.5.24",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"nodemon": "^3.1.11",
|
||||
"sass": "^1.94.0",
|
||||
"vite": "^7.2.2"
|
||||
}
|
||||
@@ -1283,92 +1282,30 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/append-field": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/append-field/-/append-field-1.0.0.tgz",
|
||||
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.1.tgz",
|
||||
"integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.0.tgz",
|
||||
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "^3.1.2",
|
||||
"content-type": "^1.0.5",
|
||||
"debug": "^4.4.3",
|
||||
"debug": "^4.4.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"iconv-lite": "^0.7.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"qs": "^6.14.0",
|
||||
"raw-body": "^3.0.1",
|
||||
"type-is": "^2.0.1"
|
||||
"raw-body": "^3.0.0",
|
||||
"type-is": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
@@ -1377,6 +1314,7 @@
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
@@ -1455,13 +1393,6 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concat-stream": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-1.6.2.tgz",
|
||||
@@ -1716,19 +1647,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/express/-/express-5.2.1.tgz",
|
||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/express/-/express-5.1.0.tgz",
|
||||
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.2.1",
|
||||
"body-parser": "^2.2.0",
|
||||
"content-disposition": "^1.0.0",
|
||||
"content-type": "^1.0.5",
|
||||
"cookie": "^0.7.1",
|
||||
"cookie-signature": "^1.2.1",
|
||||
"debug": "^4.4.0",
|
||||
"depd": "^2.0.0",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"etag": "^1.8.1",
|
||||
@@ -1782,6 +1712,7 @@
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@@ -1885,19 +1816,6 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
|
||||
@@ -1910,16 +1828,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
@@ -1945,48 +1853,42 @@
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz",
|
||||
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "~2.0.0",
|
||||
"inherits": "~2.0.4",
|
||||
"setprototypeof": "~1.2.0",
|
||||
"statuses": "~2.0.2",
|
||||
"toidentifier": "~1.0.1"
|
||||
"depd": "2.0.0",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors/node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.1.tgz",
|
||||
"integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==",
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore-by-default": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
|
||||
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.4.tgz",
|
||||
@@ -2009,25 +1911,13 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -2038,6 +1928,7 @@
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
@@ -2051,6 +1942,7 @@
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
@@ -2156,19 +2048,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz",
|
||||
@@ -2293,96 +2172,6 @@
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/nodemon": {
|
||||
"version": "3.1.11",
|
||||
"resolved": "https://registry.npmmirror.com/nodemon/-/nodemon-3.1.11.tgz",
|
||||
"integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.2",
|
||||
"debug": "^4",
|
||||
"ignore-by-default": "^1.0.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"pstree.remy": "^1.1.8",
|
||||
"semver": "^7.5.3",
|
||||
"simple-update-notifier": "^2.0.0",
|
||||
"supports-color": "^5.5.0",
|
||||
"touch": "^3.1.0",
|
||||
"undefsafe": "^2.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"nodemon": "bin/nodemon.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/nodemon"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon/node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon/node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -2510,13 +2299,6 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/pstree.remy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmmirror.com/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.0.tgz",
|
||||
@@ -2542,20 +2324,36 @@
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.2.tgz",
|
||||
"integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.1.tgz",
|
||||
"integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "~3.1.2",
|
||||
"http-errors": "~2.0.1",
|
||||
"iconv-lite": "~0.7.0",
|
||||
"unpipe": "~1.0.0"
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.7.0",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body/node_modules/iconv-lite": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.0.tgz",
|
||||
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
@@ -2696,19 +2494,6 @@
|
||||
"@parcel/watcher": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/send/-/send-1.2.0.tgz",
|
||||
@@ -2824,19 +2609,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
||||
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -2878,19 +2650,6 @@
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
@@ -2914,6 +2673,7 @@
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
@@ -2930,16 +2690,6 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/touch": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/touch/-/touch-3.1.1.tgz",
|
||||
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"nodetouch": "bin/nodetouch.js"
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/type-is/-/type-is-2.0.1.tgz",
|
||||
@@ -2960,13 +2710,6 @@
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undefsafe": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz",
|
||||
|
||||
@@ -12,14 +12,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.2.1",
|
||||
"express": "^5.1.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"vue": "^3.5.24",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"nodemon": "^3.1.11",
|
||||
"sass": "^1.94.0",
|
||||
"vite": "^7.2.2"
|
||||
}
|
||||
|
||||
Binary file not shown.
256
server.js
256
server.js
@@ -13,15 +13,14 @@ const app = express();
|
||||
const PORT = 3000;
|
||||
const CONFIG_FILE_PATH = path.join(__dirname, 'data', 'config.json');
|
||||
|
||||
// ===================== 原有图片上传配置(保留) =====================
|
||||
// 创建图片上传目录
|
||||
// 创建上传目录
|
||||
const uploadDir = path.join(__dirname, 'uploads');
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 图片上传multer配置
|
||||
const imageStorage = multer.diskStorage({
|
||||
// 配置multer
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
cb(null, uploadDir);
|
||||
},
|
||||
@@ -33,8 +32,9 @@ const imageStorage = multer.diskStorage({
|
||||
}
|
||||
});
|
||||
|
||||
const imageUpload = multer({
|
||||
storage: imageStorage,
|
||||
// 文件上传
|
||||
const upload = multer({
|
||||
storage,
|
||||
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB限制
|
||||
fileFilter: (req, file, cb) => {
|
||||
const allowedTypes = /jpeg|jpg|png|gif/;
|
||||
@@ -48,30 +48,9 @@ const imageUpload = multer({
|
||||
}
|
||||
});
|
||||
|
||||
// ===================== 新增:音乐上传配置 =====================
|
||||
// 创建音乐上传目录(对应前端访问路径)
|
||||
const musicUploadDir = path.join(__dirname, 'public', 'assets', 'music');
|
||||
// 确保目录存在(不存在则创建)
|
||||
if (!fs.existsSync(musicUploadDir)) {
|
||||
fs.mkdirSync(musicUploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 音乐上传multer配置
|
||||
const musicStorage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
cb(null, musicUploadDir); // 保存到public/assets/music
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
// 保留原文件名+时间戳,避免重复
|
||||
const timestamp = Date.now();
|
||||
const ext = path.extname(file.originalname);
|
||||
const filename = `${timestamp}${ext}`;
|
||||
cb(null, filename);
|
||||
}
|
||||
});
|
||||
|
||||
// 音乐上传
|
||||
const musicUpload = multer({
|
||||
storage: musicStorage,
|
||||
storage,
|
||||
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB音乐文件限制
|
||||
fileFilter: (req, file, cb) => {
|
||||
// 仅允许MP3格式
|
||||
@@ -86,17 +65,15 @@ const musicUpload = multer({
|
||||
}
|
||||
});
|
||||
|
||||
// ===================== 中间件(保留+优化) =====================
|
||||
// 中间件
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// 静态文件服务(Vue应用、上传的图片、音乐文件)
|
||||
// 静态文件服务(Vue应用和上传的图片)
|
||||
app.use(express.static(path.join(__dirname, 'dist')));
|
||||
app.use('/uploads', express.static(uploadDir));
|
||||
app.use('/assets/music', express.static(musicUploadDir)); // 新增:音乐文件静态访问
|
||||
|
||||
// ===================== 原有API(保留) =====================
|
||||
// API: 获取整体配置数据
|
||||
// API: 获取配置数据
|
||||
app.get('/api/config', (req, res) => {
|
||||
try {
|
||||
const configData = fs.readFileSync(CONFIG_FILE_PATH, 'utf8');
|
||||
@@ -107,7 +84,7 @@ app.get('/api/config', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// API: 保存整体配置数据
|
||||
// API: 保存配置数据
|
||||
app.post('/api/config', (req, res) => {
|
||||
try {
|
||||
fs.writeFileSync(CONFIG_FILE_PATH, JSON.stringify(req.body, null, 2), 'utf8');
|
||||
@@ -118,140 +95,9 @@ app.post('/api/config', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ===================== 新增:音乐专属API =====================
|
||||
// API: 获取音乐配置(单独返回musicConfig)
|
||||
app.get('/api/musicConfig', (req, res) => {
|
||||
try {
|
||||
const configData = fs.readFileSync(CONFIG_FILE_PATH, 'utf8');
|
||||
const config = JSON.parse(configData);
|
||||
// 兜底:如果没有musicConfig,返回默认值
|
||||
res.json(config.musicConfig || {
|
||||
enabled: false,
|
||||
filePath: '/assets/music/background.mp3'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('读取音乐配置失败:', error);
|
||||
res.status(500).json({
|
||||
enabled: false,
|
||||
filePath: '/assets/music/background.mp3'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ===================== 新增:获取已上传音乐列表API =====================
|
||||
app.get('/api/music/list', (req, res) => {
|
||||
try {
|
||||
// 音乐文件存储目录(和之前配置的一致)
|
||||
const musicDir = path.join(__dirname, 'public', 'assets', 'music');
|
||||
// 先判断目录是否存在,避免报错
|
||||
if (!fs.existsSync(musicDir)) {
|
||||
return res.json({ success: true, data: [] });
|
||||
}
|
||||
// 读取目录下的所有MP3文件
|
||||
const files = fs.readdirSync(musicDir).filter(file => file.endsWith('.mp3'));
|
||||
// 返回文件名+访问路径
|
||||
const musicList = files.map(file => ({
|
||||
filename: file,
|
||||
filePath: `/assets/music/${file}`
|
||||
}));
|
||||
res.json({ success: true, data: musicList });
|
||||
} catch (error) {
|
||||
console.error('读取音乐列表失败:', error);
|
||||
res.status(500).json({ success: false, error: '读取音乐列表失败' });
|
||||
}
|
||||
});
|
||||
// ===================== 新增结束 =====================
|
||||
|
||||
// API: 更新音乐配置(仅更新musicConfig节点,不影响其他配置)
|
||||
app.post('/api/musicConfig', (req, res) => {
|
||||
try {
|
||||
// 1. 读取原有配置
|
||||
const configData = fs.readFileSync(CONFIG_FILE_PATH, 'utf8');
|
||||
const config = JSON.parse(configData);
|
||||
|
||||
// 2. 校验参数
|
||||
const { enabled, filePath } = req.body;
|
||||
if (typeof enabled !== 'boolean' || !filePath) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '参数错误:enabled必须为布尔值,filePath不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 更新musicConfig节点(保留其他配置不变)
|
||||
config.musicConfig = { enabled, filePath };
|
||||
|
||||
// 4. 写入配置文件
|
||||
fs.writeFileSync(CONFIG_FILE_PATH, JSON.stringify(config, null, 2), 'utf8');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: config.musicConfig
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新音乐配置失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '更新音乐配置失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// API: 上传音乐文件
|
||||
app.post('/api/upload/music', musicUpload.single('musicFile'), (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '没有选择要上传的音乐文件'
|
||||
});
|
||||
}
|
||||
|
||||
// 返回前端可访问的音乐路径(对应public/assets/music)
|
||||
const relativePath = `/assets/music/${req.file.filename}`;
|
||||
res.json({
|
||||
success: true,
|
||||
filePath: relativePath, // 音乐访问路径
|
||||
filename: req.file.filename,
|
||||
originalName: req.file.originalname
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('音乐文件上传失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message || '音乐文件上传失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ===================== 兼容前端旧路径 /upload-music =====================
|
||||
app.post('/upload-music', (req, res) => {
|
||||
musicUpload.single('musicFile')(req, res, (err) => {
|
||||
if (err) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: err.message || '音乐文件上传失败'
|
||||
});
|
||||
}
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '没有选择要上传的音乐文件'
|
||||
});
|
||||
}
|
||||
const relativePath = `/assets/music/${req.file.filename}`;
|
||||
res.json({
|
||||
success: true,
|
||||
filePath: relativePath,
|
||||
filename: req.file.filename,
|
||||
originalName: req.file.originalname
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ===================== 原有图片上传/删除API(保留) =====================
|
||||
// API: 上传图片
|
||||
app.post('/api/upload', imageUpload.single('image'), (req, res) => {
|
||||
app.post('/api/upload', upload.single('image'), (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: '没有文件上传' });
|
||||
@@ -288,23 +134,83 @@ app.delete('/api/upload/:filename', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ===================== 前端路由兼容(保留) =====================
|
||||
// API: 上传音乐
|
||||
app.post('/api/music', musicUpload.single('music'), (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: '没有文件上传' });
|
||||
}
|
||||
|
||||
// 返回文件的相对路径和完整信息
|
||||
const relativePath = `/uploads/${req.file.filename}`;
|
||||
res.json({
|
||||
success: true,
|
||||
filePath: relativePath,
|
||||
filename: req.file.filename,
|
||||
originalName: req.file.originalname,
|
||||
size: req.file.size
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('音乐上传失败:', error);
|
||||
res.status(500).json({ error: error.message || '音乐上传失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// API: 删除音乐
|
||||
app.delete('/api/music/:filename', (req, res) => {
|
||||
try {
|
||||
const filename = req.params.filename;
|
||||
const filePath = path.join(uploadDir, filename);
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
res.json({ success: true });
|
||||
} else {
|
||||
res.status(404).json({ error: '音乐文件不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('音乐删除失败:', error);
|
||||
res.status(500).json({ error: '音乐删除失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// API: 获取音乐文件列表
|
||||
app.get('/api/music', (req, res) => {
|
||||
try {
|
||||
const files = fs.readdirSync(uploadDir)
|
||||
.filter(file => {
|
||||
const ext = path.extname(file).toLowerCase();
|
||||
return ['.mp3', '.wav', '.ogg', '.m4a'].includes(ext);
|
||||
})
|
||||
.map(file => {
|
||||
const filePath = path.join(uploadDir, file);
|
||||
const stats = fs.statSync(filePath);
|
||||
return {
|
||||
filename: file,
|
||||
filePath: `/uploads/${file}`,
|
||||
size: stats.size,
|
||||
createdAt: stats.birthtime.toISOString(),
|
||||
modifiedAt: stats.mtime.toISOString()
|
||||
};
|
||||
});
|
||||
|
||||
res.json({ success: true, files });
|
||||
} catch (error) {
|
||||
console.error('获取音乐列表失败:', error);
|
||||
res.status(500).json({ error: '获取音乐列表失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 处理Vue Router历史模式 - 使用正则表达式代替通配符
|
||||
app.get(/^((?!\/api).)*$/, (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
|
||||
});
|
||||
|
||||
// ===================== 服务器启动(保留+优化) =====================
|
||||
// 启动服务器并监听错误
|
||||
const server = app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`服务器运行在 http://localhost:${PORT}`);
|
||||
console.log('服务器已成功启动,可以访问 http://localhost:3000');
|
||||
console.log('API端点:');
|
||||
console.log(' - 整体配置: GET/POST /api/config');
|
||||
console.log(' - 音乐配置: GET/POST /api/musicConfig');
|
||||
console.log(' - 图片上传: POST /api/upload');
|
||||
console.log(' - 音乐上传: POST /api/upload/music');
|
||||
console.log(' - 图片删除: DELETE /api/upload/:filename');
|
||||
console.log('API端点: GET/POST /api/config');
|
||||
});
|
||||
|
||||
// 监听服务器错误
|
||||
|
||||
@@ -13,7 +13,9 @@ import {
|
||||
getBattleEndTime,
|
||||
saveBattleEndTime as saveBattleEndTimeToConfig,
|
||||
getBonusRules,
|
||||
saveBonusRules as saveBonusRulesToConfig
|
||||
saveBonusRules as saveBonusRulesToConfig,
|
||||
getMusicConfig,
|
||||
saveMusicConfig as saveMusicConfigToConfig
|
||||
} from '../services/configService';
|
||||
|
||||
// 初始化空数据占位符,将在initializeData中正确加载
|
||||
@@ -27,6 +29,7 @@ export let bonusRules = [
|
||||
export let systemUsers = [];
|
||||
export let displayConfig = null;
|
||||
export let battleEndTime = { date: new Date().toISOString().split('T')[0], time: '00:00:00' };
|
||||
export let musicConfig = { enabled: false, filePath: '' };
|
||||
|
||||
|
||||
// 保存结束时间
|
||||
@@ -93,6 +96,7 @@ export const refreshData = async () => {
|
||||
systemUsers = await getSystemUsers();
|
||||
displayConfig = await getDisplayConfig();
|
||||
battleEndTime = await getBattleEndTime();
|
||||
musicConfig = await getMusicConfig();
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import BattleRanking from '../views/BattleRanking.vue'; // 首页组件
|
||||
import AdminPanel from '../views/AdminPanel.vue'; // 管理员面板组件
|
||||
import { musicPlayer } from '@/utils/musicPlayer'; // 音乐播放器实例
|
||||
import { getMusicConfig } from '@/services/configService'; // 音乐配置读取服务
|
||||
import { musicPlayer } from '../utils/musicPlayer'; // 音乐播放器实例
|
||||
import { getMusicConfig } from '../services/configService'; // 音乐配置读取服务
|
||||
|
||||
// 路由配置
|
||||
const routes = [
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
// 配置文件API路径
|
||||
// 修复后(绝对路径,直接请求后端3000端口)
|
||||
const CONFIG_API_URL = 'http://localhost:3000/api/config';
|
||||
// 新增:音乐相关API地址(和后端接口对应)
|
||||
const MUSIC_API_URL = 'http://localhost:3000/api/musicConfig';
|
||||
const MUSIC_UPLOAD_API_URL = 'http://localhost:3000/api/upload/music';
|
||||
const CONFIG_API_URL = '/api/config';
|
||||
|
||||
/**
|
||||
* 读取配置文件
|
||||
@@ -109,9 +105,9 @@ const getDefaultConfig = () => ({
|
||||
// ========== 音乐配置默认值(和displayConfig同级) ==========
|
||||
music: {
|
||||
enabled: false,
|
||||
filePath: '/assets/music/background.mp3'
|
||||
filePath: '',
|
||||
volume: 0.5 // 默认音量50%
|
||||
},
|
||||
// ==========================================================
|
||||
battleEndTime: {
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
time: '00:00:00'
|
||||
@@ -348,102 +344,99 @@ export const saveBackgroundConfig = async (backgroundConfig) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取音乐配置(优先读后端接口,失败则兜底本地配置)
|
||||
* @returns {Object} 音乐配置 { enabled, filePath }
|
||||
* 获取音乐配置
|
||||
* @returns {Object} 音乐配置
|
||||
*/
|
||||
export const getMusicConfig = async () => {
|
||||
try {
|
||||
// 优先调用后端音乐配置接口
|
||||
const response = await fetch(MUSIC_API_URL);
|
||||
if (response.ok) {
|
||||
const musicConfig = await response.json();
|
||||
// 基础格式校验
|
||||
if (typeof musicConfig.enabled !== 'boolean' || !musicConfig.filePath) {
|
||||
throw new Error('后端音乐配置返回格式异常');
|
||||
}
|
||||
return musicConfig;
|
||||
}
|
||||
throw new Error(`获取音乐配置失败: ${response.status}`);
|
||||
} catch (error) {
|
||||
console.error('读取音乐配置接口失败,兜底读取本地配置:', error);
|
||||
// 兜底逻辑:读取本地config中的music字段
|
||||
const localConfig = await readConfig();
|
||||
return localConfig.music || { enabled: false, filePath: '/assets/music/background.mp3' };
|
||||
}
|
||||
const config = await readConfig();
|
||||
return config.music || getDefaultConfig().music;
|
||||
};
|
||||
|
||||
/**
|
||||
* 上传音乐文件(带返回值格式校验)
|
||||
* @param {File} file 音乐文件(MP3)
|
||||
* @returns {Object} { success: boolean, filePath?: string, error?: string }
|
||||
* 保存音乐配置
|
||||
* @param {Object} musicConfig 音乐配置
|
||||
* @returns {boolean} 是否保存成功
|
||||
*/
|
||||
export const uploadMusicFile = async (file) => {
|
||||
export const saveMusicConfig = async (musicConfig) => {
|
||||
const config = await readConfig();
|
||||
// 确保音量是数字类型
|
||||
const normalizedMusicConfig = {
|
||||
...musicConfig,
|
||||
volume: typeof musicConfig.volume === 'string' ? parseFloat(musicConfig.volume) : musicConfig.volume
|
||||
};
|
||||
config.music = normalizedMusicConfig;
|
||||
return await writeConfig(config);
|
||||
};
|
||||
|
||||
/**
|
||||
* 上传音乐
|
||||
* @param {File} file 音乐文件
|
||||
* @returns {Object} 上传结果 { success: boolean, filePath?: string, filename?: string, originalName?: string, size?: number, error?: string }
|
||||
*/
|
||||
export const uploadMusic = async (file) => {
|
||||
try {
|
||||
// 前置校验:文件类型
|
||||
if (!file.type.includes('audio/mpeg') && !file.name.endsWith('.mp3')) {
|
||||
throw new Error('仅支持MP3格式的音乐文件');
|
||||
}
|
||||
|
||||
// 构建FormData(适配后端multer.single('musicFile'))
|
||||
const formData = new FormData();
|
||||
formData.append('musicFile', file);
|
||||
|
||||
const response = await fetch(MUSIC_UPLOAD_API_URL, {
|
||||
formData.append('music', file);
|
||||
|
||||
const response = await fetch('/api/music', {
|
||||
method: 'POST',
|
||||
body: formData // FormData格式无需设置Content-Type,浏览器自动处理
|
||||
body: formData
|
||||
});
|
||||
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
// 严格校验后端返回格式
|
||||
if (typeof result.success !== 'boolean') {
|
||||
throw new Error('音乐上传接口返回格式异常(缺失success字段)');
|
||||
}
|
||||
if (result.success && !result.filePath) {
|
||||
throw new Error('音乐上传成功但未返回文件路径');
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(result.error || '音乐上传失败');
|
||||
}
|
||||
|
||||
throw new Error(`音乐上传失败: ${response.status}`);
|
||||
} catch (error) {
|
||||
console.error('上传音乐文件失败:', error);
|
||||
console.error('上传音乐失败:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新音乐配置(同步更新后端+本地配置)
|
||||
* @param {boolean} enabled 是否开启播放
|
||||
* @param {string} filePath 音乐文件路径
|
||||
* @returns {boolean} 是否更新成功
|
||||
* 删除音乐
|
||||
* @param {string} filename 文件名
|
||||
* @returns {Object} 删除结果 { success: boolean, error?: string }
|
||||
*/
|
||||
export const updateMusicConfig = async (enabled, filePath) => {
|
||||
export const deleteMusic = async (filename) => {
|
||||
try {
|
||||
// 1. 调用后端接口更新音乐配置
|
||||
const response = await fetch(MUSIC_API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ enabled, filePath })
|
||||
const response = await fetch(`/api/music/${filename}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
// 2. 同步更新本地config中的music字段
|
||||
const localConfig = await readConfig();
|
||||
localConfig.music = { enabled, filePath };
|
||||
await writeConfig(localConfig);
|
||||
console.log('音乐配置已同步更新到本地');
|
||||
}
|
||||
return result.success;
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(result.error || '音乐删除失败');
|
||||
}
|
||||
|
||||
throw new Error(`保存音乐配置失败: ${response.status}`);
|
||||
} catch (error) {
|
||||
console.error('更新音乐配置失败:', error);
|
||||
return false;
|
||||
console.error('删除音乐失败:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取音乐文件列表
|
||||
* @returns {Object} 列表结果 { success: boolean, files?: Array, error?: string }
|
||||
*/
|
||||
export const getMusicList = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/music');
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(result.error || '获取音乐列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取音乐列表失败:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
};
|
||||
@@ -1,10 +1,11 @@
|
||||
// src/utils/musicPlayer.js
|
||||
class MusicPlayer {
|
||||
constructor() {
|
||||
this.audio = null;
|
||||
this.isPlaying = false;
|
||||
this.defaultPath = "/assets/music/background.mp3";
|
||||
this.enabled = false;
|
||||
this.audio = null;
|
||||
this.isPlaying = false;
|
||||
this.defaultPath = "";
|
||||
this.enabled = false;
|
||||
this.volume = 0.5; // 默认音量50%
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -13,7 +14,7 @@ class MusicPlayer {
|
||||
*/
|
||||
init(path) {
|
||||
// 组件调用 init 时,复用已有的 initMusicConfig,开关状态先传 this.enabled(后续组件会通过配置更新)
|
||||
this.initMusicConfig(path, this.enabled);
|
||||
this.initMusicConfig(path, this.enabled, this.volume);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,35 +22,56 @@ class MusicPlayer {
|
||||
* @param {string} filePath 音乐路径
|
||||
* @param {boolean} enabled 播放开关
|
||||
*/
|
||||
initMusicConfig(filePath, enabled) {
|
||||
initMusicConfig(filePath, enabled, volume = 0.5) {
|
||||
console.log("初始化音乐配置:", { filePath, enabled, volume });
|
||||
|
||||
this.enabled = enabled;
|
||||
// 确保音量是数字类型
|
||||
this.volume = typeof volume === 'string' ? parseFloat(volume) : volume;
|
||||
console.log("处理后的音量值:", this.volume, "类型:", typeof this.volume);
|
||||
|
||||
let validPath = this.defaultPath;
|
||||
if (filePath && filePath.endsWith('.mp3')) {
|
||||
validPath = filePath;
|
||||
} else if (filePath) {
|
||||
console.warn(`音乐路径无效(非MP3格式):${filePath},使用兜底路径`);
|
||||
}
|
||||
|
||||
console.log("使用的音乐路径:", validPath);
|
||||
|
||||
if (this.audio) {
|
||||
this.audio.pause();
|
||||
this.audio = null;
|
||||
}
|
||||
|
||||
this.audio = new Audio(validPath);
|
||||
this.audio.loop = true;
|
||||
this.audio.volume = this.volume;
|
||||
|
||||
console.log("音频对象创建完成,音量设置为:", this.audio.volume);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放音乐(保留原有逻辑,适配开关)
|
||||
*/
|
||||
play() {
|
||||
console.log("调用 play 方法,当前状态:", { enabled: this.enabled, hasAudio: !!this.audio, isPlaying: this.isPlaying });
|
||||
|
||||
if (!this.enabled) {
|
||||
console.log("首页播放开关未开启,跳过音乐播放");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.audio) {
|
||||
console.warn("音频对象未初始化");
|
||||
this.initMusicConfig(this.defaultPath, false);
|
||||
console.warn("未初始化音乐配置,使用兜底路径且关闭播放开关");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("音频源路径:", this.audio.src);
|
||||
console.log("音频就绪状态:", this.audio.readyState);
|
||||
|
||||
if (!this.isPlaying) {
|
||||
this.audio.play()
|
||||
.then(() => {
|
||||
@@ -58,8 +80,15 @@ class MusicPlayer {
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("音乐播放失败(浏览器自动播放限制/路径错误):", err);
|
||||
console.error("错误详情:", {
|
||||
name: err.name,
|
||||
message: err.message,
|
||||
code: err.code
|
||||
});
|
||||
this.isPlaying = false;
|
||||
});
|
||||
} else {
|
||||
console.log("音乐已在播放中");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,16 +102,32 @@ class MusicPlayer {
|
||||
console.log("音乐已暂停");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 新增:设置静音/取消静音(适配管理员/首页场景)
|
||||
* @param {boolean} muted 是否静音
|
||||
*/
|
||||
setMuted(muted) {
|
||||
if (this.audio) {
|
||||
this.audio.muted = muted;
|
||||
console.log(muted ? "音乐已静音" : "音乐已取消静音");
|
||||
/**
|
||||
* 新增:设置静音/取消静音(适配管理员/首页场景)
|
||||
* @param {boolean} muted 是否静音
|
||||
*/
|
||||
setMuted(muted) {
|
||||
if (this.audio) {
|
||||
this.audio.muted = muted;
|
||||
console.log(muted ? "音乐已静音" : "音乐已取消静音");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置音量
|
||||
* @param {number} volume 音量值 (0.0 到 1.0)
|
||||
*/
|
||||
setVolume(volume) {
|
||||
if (this.audio) {
|
||||
// 确保音量是数字类型
|
||||
const numericVolume = typeof volume === 'string' ? parseFloat(volume) : volume;
|
||||
// 限制音量范围在0.0到1.0之间
|
||||
this.volume = Math.max(0, Math.min(1, numericVolume));
|
||||
this.audio.volume = this.volume;
|
||||
console.log(`音乐音量已设置为: ${Math.round(this.volume * 100)}%`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增:stop 方法(组件 onUnmounted 调用,暂停+重置进度)
|
||||
*/
|
||||
@@ -124,7 +169,7 @@ setMuted(muted) {
|
||||
console.error("更新的音乐路径无效(非MP3格式):", newPath);
|
||||
return;
|
||||
}
|
||||
this.initMusicConfig(newPath, this.enabled);
|
||||
this.initMusicConfig(newPath, this.enabled, this.volume);
|
||||
console.log("音乐路径已更新为:", newPath);
|
||||
if (this.enabled && this.isPlaying) {
|
||||
this.pause();
|
||||
|
||||
@@ -103,8 +103,17 @@
|
||||
:class="{ active: music.filePath === currentMusicPath }"
|
||||
@click="switchToMusic(music.filePath)"
|
||||
>
|
||||
{{ music.filename }}
|
||||
<span v-if="music.filePath === currentMusicPath" style="color: #667eea; margin-left: 8px;">✅ 当前使用</span>
|
||||
<span class="music-name">{{ music.filename }}</span>
|
||||
<div class="music-actions">
|
||||
<span v-if="music.filePath === currentMusicPath" class="current-indicator">✅ 当前使用</span>
|
||||
<button
|
||||
@click="deleteMusicFile(music.filename, $event)"
|
||||
class="btn-delete-music"
|
||||
title="删除音乐文件"
|
||||
>
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="upload-hint">暂无已上传的音乐文件</p>
|
||||
@@ -118,6 +127,32 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- 音乐测试播放控件 -->
|
||||
<div class="config-item" style="margin: 15px 0;">
|
||||
<label class="checkbox-label">
|
||||
<span class="text-gold">音乐测试播放:</span>
|
||||
<button
|
||||
@click="testMusicPlay"
|
||||
:disabled="!currentMusicPath"
|
||||
class="btn-game"
|
||||
style="margin-right: 10px; padding: 8px 16px; font-size: 14px;"
|
||||
>
|
||||
🎵 测试播放
|
||||
</button>
|
||||
<button
|
||||
@click="testMusicPause"
|
||||
:disabled="!currentMusicPath"
|
||||
class="btn-game-secondary"
|
||||
style="padding: 8px 16px; font-size: 14px;"
|
||||
>
|
||||
⏸️ 停止播放
|
||||
</button>
|
||||
</label>
|
||||
<div v-if="testPlayMsg" class="upload-hint" :style="testPlayMsg.includes('失败') ? 'color: red;' : 'color: #667eea;'">
|
||||
{{ testPlayMsg }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第2行:首页播放开关(对齐Logo配置的复选框样式) -->
|
||||
<div class="config-item" style="margin: 20px 0;">
|
||||
<label class="checkbox-label">
|
||||
@@ -125,7 +160,15 @@
|
||||
<span class="text-gold">开启首页背景音乐播放</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 音量控制滑块 -->
|
||||
<div class="config-item" style="margin: 15px 0;">
|
||||
<label class="checkbox-label">
|
||||
<span class="text-gold">音乐音量调节:</span>
|
||||
<input type="range" min="0" max="1" step="0.01" v-model="musicVolume" @input="handleMusicVolumeChange" class="volume-slider">
|
||||
<span class="volume-value">{{ Math.round(musicVolume * 100) }}%</span>
|
||||
</label>
|
||||
</div>
|
||||
<!-- 提示文本(复用Logo上传的hint样式) -->
|
||||
<p class="upload-hint">
|
||||
仅支持MP3格式音频文件,建议文件大小不超过10MB,上传后立即生效
|
||||
@@ -867,9 +910,16 @@ import {
|
||||
refreshData,
|
||||
initializeData
|
||||
} from '../data/mockData.js';
|
||||
// 新增音乐相关依赖引入
|
||||
import { uploadMusicFile, updateMusicConfig, getMusicConfig } from '../services/configService.js';
|
||||
import { musicPlayer } from '@/utils/musicPlayer';
|
||||
|
||||
import {
|
||||
getMusicConfig,
|
||||
saveMusicConfig,
|
||||
uploadMusic,
|
||||
deleteMusic,
|
||||
getMusicList
|
||||
} from '../services/configService.js';
|
||||
|
||||
import { musicPlayer } from '../utils/musicPlayer.js';
|
||||
const router = useRouter();
|
||||
// 新增音乐配置变量
|
||||
const selectedMusicFile = ref(null); // 选中的MP3文件
|
||||
@@ -877,6 +927,7 @@ const uploadMsg = ref(''); // 上传提示信息
|
||||
const musicEnabled = ref(false); // 首页播放开关状态
|
||||
const currentMusicPath = ref(''); // 当前音乐路径
|
||||
const musicList = ref([]); // 新增:已上传音乐列表
|
||||
const musicVolume = ref(0.5); // 音乐音量控制(0.0-1.0,默认50%)
|
||||
// 返回首页
|
||||
const goToHome = () => {
|
||||
router.push('/');
|
||||
@@ -910,7 +961,26 @@ const championLogos = ref({
|
||||
individualChampionSize: 60 // 默认60px
|
||||
});
|
||||
// ========== 增强版:音乐相关核心方法 ==========
|
||||
// 1. 选择音乐文件(校验格式+友好提示)
|
||||
// 1. 初始化音乐配置
|
||||
const initMusicConfig = async () => {
|
||||
try {
|
||||
// 获取音乐配置
|
||||
const musicConfig = await getMusicConfig();
|
||||
musicEnabled.value = musicConfig.enabled || false;
|
||||
currentMusicPath.value = musicConfig.filePath || '';
|
||||
musicVolume.value = musicConfig.volume !== undefined ? musicConfig.volume : 0.5; // 默认50%音量
|
||||
|
||||
// 获取音乐文件列表
|
||||
const musicListResult = await getMusicList();
|
||||
if (musicListResult.success) {
|
||||
musicList.value = musicListResult.files;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化音乐配置失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 2. 选择音乐文件(校验格式+友好提示)
|
||||
const handleMusicFileChange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
@@ -935,121 +1005,206 @@ const handleMusicFileChange = (e) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 新增:获取已上传音乐列表(调用后端/api/music/list接口)
|
||||
const fetchMusicList = async () => {
|
||||
try {
|
||||
const res = await fetch('http://localhost:3000/api/music/list');
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
musicList.value = data.data;
|
||||
} else {
|
||||
uploadMsg.value = `⚠️ 获取音乐列表失败:${data.error}`;
|
||||
}
|
||||
} catch (err) {
|
||||
uploadMsg.value = `⚠️ 获取音乐列表异常:${err.message}`;
|
||||
}
|
||||
};
|
||||
|
||||
// 新增:点击列表项切换音乐
|
||||
const switchToMusic = async (filePath) => {
|
||||
try {
|
||||
uploadMsg.value = '⏳ 正在切换音乐...';
|
||||
// 调用后端接口更新config.json的音乐路径
|
||||
await updateMusicConfig(musicEnabled.value, filePath);
|
||||
// 回显新路径到页面
|
||||
currentMusicPath.value = filePath;
|
||||
// 实时切换播放器音乐(和原有上传逻辑保持一致)
|
||||
musicPlayer.updateMusicPath(filePath);
|
||||
// 若开关开启,立即播放新音乐
|
||||
if (musicEnabled.value) {
|
||||
musicPlayer.play();
|
||||
}
|
||||
uploadMsg.value = '✅ 音乐切换成功!';
|
||||
} catch (err) {
|
||||
uploadMsg.value = `❌ 切换音乐失败:${err.message}`;
|
||||
}
|
||||
};
|
||||
|
||||
// 修改原handleMusicUpload方法,在上传成功后添加fetchMusicList()
|
||||
// 3. 上传音乐文件
|
||||
const handleMusicUpload = async () => {
|
||||
if (!selectedMusicFile.value) {
|
||||
uploadMsg.value = '❌ 请先选择MP3文件!';
|
||||
uploadMsg.value = '❌ 请先选择音乐文件!';
|
||||
return;
|
||||
}
|
||||
|
||||
uploadMsg.value = '⏳ 正在上传音乐文件...';
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('musicFile', selectedMusicFile.value);
|
||||
const response = await fetch('http://localhost:3000/upload-music', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const uploadResult = await response.json();
|
||||
|
||||
uploadMsg.value = '📤 正在上传...';
|
||||
|
||||
// 上传音乐文件
|
||||
const uploadResult = await uploadMusic(selectedMusicFile.value);
|
||||
|
||||
if (uploadResult.success) {
|
||||
await updateMusicConfig(musicEnabled.value, uploadResult.filePath);
|
||||
uploadMsg.value = `✅ 上传成功:${uploadResult.filename}`;
|
||||
|
||||
// 更新当前音乐路径
|
||||
currentMusicPath.value = uploadResult.filePath;
|
||||
musicPlayer.updateMusicPath(uploadResult.filePath);
|
||||
if (musicEnabled.value) {
|
||||
musicPlayer.play();
|
||||
|
||||
// 刷新音乐列表
|
||||
const musicListResult = await getMusicList();
|
||||
if (musicListResult.success) {
|
||||
musicList.value = musicListResult.files;
|
||||
}
|
||||
// 新增:上传成功后刷新列表
|
||||
await fetchMusicList();
|
||||
uploadMsg.value = '✅ 音乐上传成功!已自动应用到首页';
|
||||
|
||||
// 自动启用音乐
|
||||
musicEnabled.value = true;
|
||||
await saveMusicConfig({
|
||||
enabled: true,
|
||||
filePath: uploadResult.filePath
|
||||
});
|
||||
|
||||
// 清空选择
|
||||
selectedMusicFile.value = null;
|
||||
|
||||
// 清空文件输入
|
||||
const fileInput = document.querySelector('input[type="file"][accept=".mp3"]');
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
uploadMsg.value = '';
|
||||
}, 3000);
|
||||
} else {
|
||||
uploadMsg.value = `❌ 上传失败:${uploadResult.error}`;
|
||||
}
|
||||
} catch (err) {
|
||||
uploadMsg.value = `❌ 上传异常:${err.message}`;
|
||||
} catch (error) {
|
||||
console.error('音乐上传失败:', error);
|
||||
uploadMsg.value = '❌ 上传失败,请重试';
|
||||
}
|
||||
};
|
||||
|
||||
// 3. 切换播放开关(更新config.json+实时控制)
|
||||
// 4. 切换音乐文件
|
||||
const switchToMusic = async (filePath) => {
|
||||
try {
|
||||
currentMusicPath.value = filePath;
|
||||
await saveMusicConfig({
|
||||
enabled: musicEnabled.value,
|
||||
filePath: filePath
|
||||
});
|
||||
|
||||
uploadMsg.value = '✅ 已切换音乐文件';
|
||||
setTimeout(() => {
|
||||
uploadMsg.value = '';
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
console.error('切换音乐失败:', error);
|
||||
uploadMsg.value = '❌ 切换失败,请重试';
|
||||
}
|
||||
};
|
||||
|
||||
// 5. 删除音乐文件
|
||||
const deleteMusicFile = async (filename, event) => {
|
||||
event.stopPropagation(); // 阻止触发点击切换音乐
|
||||
|
||||
if (!confirm('确定要删除这个音乐文件吗?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const deleteResult = await deleteMusic(filename);
|
||||
|
||||
if (deleteResult.success) {
|
||||
// 如果删除的是当前使用的音乐,清空配置
|
||||
const deletedFilePath = `/uploads/${filename}`;
|
||||
if (currentMusicPath.value === deletedFilePath) {
|
||||
currentMusicPath.value = '';
|
||||
musicEnabled.value = false;
|
||||
await saveMusicConfig({
|
||||
enabled: false,
|
||||
filePath: ''
|
||||
});
|
||||
}
|
||||
|
||||
// 刷新音乐列表
|
||||
const musicListResult = await getMusicList();
|
||||
if (musicListResult.success) {
|
||||
musicList.value = musicListResult.files;
|
||||
}
|
||||
|
||||
uploadMsg.value = '✅ 音乐文件已删除';
|
||||
setTimeout(() => {
|
||||
uploadMsg.value = '';
|
||||
}, 2000);
|
||||
} else {
|
||||
uploadMsg.value = `❌ 删除失败:${deleteResult.error}`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除音乐失败:', error);
|
||||
uploadMsg.value = '❌ 删除失败,请重试';
|
||||
}
|
||||
};
|
||||
|
||||
// 6. 处理音乐开关切换
|
||||
const handleMusicSwitchChange = async () => {
|
||||
try {
|
||||
// 更新config.json的enabled状态
|
||||
const updateResult = await updateMusicConfig(musicEnabled.value, currentMusicPath.value);
|
||||
if (updateResult) {
|
||||
uploadMsg.value = `✅ 开关已${musicEnabled.value ? '开启' : '关闭'}!`;
|
||||
// 实时控制播放器
|
||||
if (musicEnabled.value) {
|
||||
musicPlayer.play();
|
||||
} else {
|
||||
musicPlayer.pause();
|
||||
}
|
||||
} else {
|
||||
// 失败时回滚开关状态
|
||||
musicEnabled.value = !musicEnabled.value;
|
||||
uploadMsg.value = '❌ 开关更新失败!';
|
||||
}
|
||||
} catch (err) {
|
||||
await saveMusicConfig({
|
||||
enabled: musicEnabled.value,
|
||||
filePath: currentMusicPath.value,
|
||||
volume: musicVolume.value
|
||||
});
|
||||
|
||||
uploadMsg.value = musicEnabled.value ? '✅ 背景音乐已开启' : '⏸️ 背景音乐已关闭';
|
||||
setTimeout(() => {
|
||||
uploadMsg.value = '';
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
console.error('保存音乐配置失败:', error);
|
||||
uploadMsg.value = '❌ 操作失败,请重试';
|
||||
// 回滚开关状态
|
||||
musicEnabled.value = !musicEnabled.value;
|
||||
uploadMsg.value = `❌ 开关更新异常:${err.message}`;
|
||||
}
|
||||
};
|
||||
|
||||
// 4. 初始化音乐配置(页面加载时回显状态)
|
||||
const initMusicConfig = async () => {
|
||||
// 6.1 处理音量变化
|
||||
const handleMusicVolumeChange = async () => {
|
||||
try {
|
||||
const config = await getMusicConfig();
|
||||
musicEnabled.value = config.enabled ?? false; // 兼容默认值
|
||||
currentMusicPath.value = config.filePath || '/assets/music/background.mp3'; // 默认路径
|
||||
await fetchMusicList();// 新增:初始化时加载已上传列表
|
||||
} catch (err) {
|
||||
uploadMsg.value = `⚠️ 音乐配置初始化失败:${err.message}`;
|
||||
// 初始化默认值
|
||||
musicEnabled.value = false;
|
||||
currentMusicPath.value = '/assets/music/background.mp3';
|
||||
await saveMusicConfig({
|
||||
enabled: musicEnabled.value,
|
||||
filePath: currentMusicPath.value,
|
||||
volume: musicVolume.value
|
||||
});
|
||||
|
||||
// 更新播放器音量
|
||||
musicPlayer.setVolume(musicVolume.value);
|
||||
|
||||
uploadMsg.value = `✅ 音量已设置为 ${Math.round(musicVolume.value * 100)}%`;
|
||||
setTimeout(() => {
|
||||
uploadMsg.value = '';
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
console.error('保存音乐配置失败:', error);
|
||||
uploadMsg.value = '❌ 音量设置失败,请重试';
|
||||
}
|
||||
};
|
||||
// ========== 音乐方法结束 ==========
|
||||
|
||||
// 7. 测试播放音乐
|
||||
const testPlayMsg = ref('');
|
||||
|
||||
const testMusicPlay = () => {
|
||||
if (!currentMusicPath.value) {
|
||||
testPlayMsg.value = '❌ 请先选择或上传音乐文件';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 初始化音乐播放器(管理员页面强制启用播放)
|
||||
musicPlayer.initMusicConfig(currentMusicPath.value, true, musicVolume.value);
|
||||
musicPlayer.play();
|
||||
testPlayMsg.value = `🎵 开始播放音乐(音量:${Math.round(musicVolume.value * 100)}%)`;
|
||||
|
||||
setTimeout(() => {
|
||||
testPlayMsg.value = '';
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
console.error('测试播放失败:', error);
|
||||
testPlayMsg.value = '❌ 播放失败,请检查音乐文件';
|
||||
}
|
||||
};
|
||||
|
||||
const testMusicPause = () => {
|
||||
try {
|
||||
musicPlayer.pause();
|
||||
testPlayMsg.value = '⏸️ 已停止播放';
|
||||
|
||||
setTimeout(() => {
|
||||
testPlayMsg.value = '';
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
console.error('停止播放失败:', error);
|
||||
testPlayMsg.value = '❌ 停止失败';
|
||||
}
|
||||
};
|
||||
|
||||
// 组件挂载时初始化冠军Logo配置
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await initializeData();
|
||||
// 重新加载本地数据副本(合并第二个onMounted的逻辑)
|
||||
// 重新加载本地数据副本
|
||||
localIndividualRankings.value = [...individualRankings];
|
||||
localTeamRankings.value = [...teamRankings];
|
||||
localBonusRules.value = [...bonusRules];
|
||||
@@ -1329,6 +1484,12 @@ const saveData = async () => {
|
||||
// 保存冠军Logo配置
|
||||
currentConfig.displayConfig.championLogos = championLogos.value;
|
||||
currentConfig.battleEndTime = localBattleEndTime.value;
|
||||
// 保存音乐配置
|
||||
currentConfig.music = {
|
||||
enabled: musicEnabled.value,
|
||||
filePath: currentMusicPath.value,
|
||||
volume: parseFloat(musicVolume.value) // 确保音量是数字类型
|
||||
};
|
||||
|
||||
// 一次性保存所有配置
|
||||
const result = await writeConfig(currentConfig);
|
||||
@@ -2264,6 +2425,9 @@ const deleteBonusRule = (index) => {
|
||||
border: 1px solid #eee;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.music-item:hover {
|
||||
background: #f0f7ff;
|
||||
@@ -2275,4 +2439,51 @@ const deleteBonusRule = (index) => {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.music-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.music-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.current-indicator {
|
||||
font-size: 0.9rem;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.btn-delete-music {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.btn-delete-music:hover {
|
||||
background-color: #ffebee;
|
||||
}
|
||||
|
||||
/* 音量控制滑块样式 */
|
||||
.volume-slider {
|
||||
width: 200px;
|
||||
margin: 0 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.volume-value {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
color: #667eea;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div @click="handleAnyInteraction" @scroll="handleAnyInteraction" @touchstart="handleAnyInteraction">
|
||||
<!-- 第一部分:百日大战主题 - 使用banner0.png图片 -->
|
||||
<section class="theme-section card-game">
|
||||
<div class="theme-container">
|
||||
@@ -212,13 +212,11 @@ import {
|
||||
bonusRules,
|
||||
displayConfig,
|
||||
battleEndTime,
|
||||
initializeData
|
||||
initializeData,
|
||||
musicConfig as importedMusicConfig
|
||||
} from '../data/mockData.js';
|
||||
import { readConfig, getMusicConfig } from '../services/configService.js';
|
||||
import { musicPlayer } from '@/utils/musicPlayer';
|
||||
// 新增:选项卡激活状态(控制音乐面板显示/隐藏)
|
||||
const activeTab = ref('');
|
||||
|
||||
import { readConfig } from '../services/configService.js';
|
||||
import { musicPlayer } from '../utils/musicPlayer';
|
||||
|
||||
// 创建默认显示配置的函数
|
||||
function createDefaultDisplayConfig() {
|
||||
@@ -401,10 +399,37 @@ const localDisplayConfig = ref(() => {
|
||||
return defaultDisplayConfig;
|
||||
});
|
||||
const taskSettings = ref({
|
||||
mainTitle: '3000万',
|
||||
mainTitle: '2300万',
|
||||
subtitle: '时间: 2025-11-12 - 2026-02-08'
|
||||
});
|
||||
|
||||
|
||||
const localMusicConfig = ref({
|
||||
enabled: true,
|
||||
filePath: '',
|
||||
volume: 1
|
||||
});
|
||||
|
||||
// 添加首次交互处理函数
|
||||
const handleFirstInteraction = () => {
|
||||
console.log("用户首次交互触发");
|
||||
if (localMusicConfig.value.enabled) {
|
||||
console.log("尝试播放音乐...");
|
||||
musicPlayer.play();
|
||||
} else {
|
||||
console.log("音乐未启用,跳过播放");
|
||||
}
|
||||
};
|
||||
|
||||
// 添加任意交互处理函数(点击、滚动等都会触发)
|
||||
const handleAnyInteraction = () => {
|
||||
// 只有在音乐启用且当前未播放时才尝试播放
|
||||
if (localMusicConfig.value.enabled && !musicPlayer.isPlaying) {
|
||||
console.log("检测到用户交互,尝试播放音乐...");
|
||||
musicPlayer.play();
|
||||
}
|
||||
};
|
||||
|
||||
// 加载任务设置和初始化所有数据
|
||||
onBeforeMount(async () => {
|
||||
try {
|
||||
@@ -433,6 +458,17 @@ onBeforeMount(async () => {
|
||||
const configCopy = JSON.parse(JSON.stringify(config.displayConfig));
|
||||
localDisplayConfig.value = mergeConfig(defaultDisplayConfig, configCopy);
|
||||
}
|
||||
|
||||
if (config.music) {
|
||||
localMusicConfig.value = config.music;
|
||||
console.log("从服务器加载的音乐配置:", config.music);
|
||||
console.log("音乐文件路径:", config.music.filePath);
|
||||
console.log("音乐是否启用:", config.music.enabled);
|
||||
console.log("音乐音量:", config.music.volume);
|
||||
console.log("音量类型:", typeof config.music.volume);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
@@ -767,6 +803,10 @@ const endTouch = (e) => {
|
||||
|
||||
// 优化的拖动函数 - 使用节流减少更新频率(鼠标事件)
|
||||
const drag = throttle((e) => {
|
||||
if (isDragging) {
|
||||
drumsPosition.value.x = e.clientX - dragOffset.x;
|
||||
drumsPosition.value.y = e.clientY - dragOffset.y;
|
||||
}
|
||||
if (isBonusDragging) {
|
||||
// 计算新的位置
|
||||
const newX = e.clientX - bonusDragOffset.x;
|
||||
@@ -874,12 +914,42 @@ onMounted(async () => {
|
||||
try {
|
||||
// 异步初始化数据
|
||||
await initializeData();
|
||||
// 新增:加入音乐控制逻辑
|
||||
const musicConfig = await getMusicConfig();
|
||||
if (musicConfig.enabled) {
|
||||
musicPlayer.play();
|
||||
|
||||
localMusicConfig.value = await readConfig().music;
|
||||
|
||||
if (localMusicConfig.value.enabled) {
|
||||
// 获取音量设置,如果没有则使用默认值0.5
|
||||
const volume = localMusicConfig.value.volume !== undefined ? localMusicConfig.value.volume : 0.5;
|
||||
console.log("音乐配置信息:", localMusicConfig.value);
|
||||
console.log("音乐文件路径:", localMusicConfig.value.filePath);
|
||||
console.log("音乐是否启用:", localMusicConfig.value.enabled);
|
||||
console.log("音乐音量:", volume);
|
||||
console.log("音量类型:", typeof volume);
|
||||
|
||||
// 检查音乐文件是否存在
|
||||
if (localMusicConfig.value.filePath) {
|
||||
console.log("正在检查音乐文件是否存在...");
|
||||
fetch(localMusicConfig.value.filePath)
|
||||
.then(response => {
|
||||
console.log("音乐文件状态:", response.status, response.statusText);
|
||||
if (response.ok) {
|
||||
console.log("音乐文件存在,可以正常访问");
|
||||
} else {
|
||||
console.error("音乐文件无法访问,状态码:", response.status);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("检查音乐文件时出错:", error);
|
||||
});
|
||||
}
|
||||
|
||||
musicPlayer.initMusicConfig(localMusicConfig.value.filePath, localMusicConfig.value.enabled, volume);
|
||||
// 注意:由于浏览器自动播放策略限制,这里不直接调用play()
|
||||
// 而是等待用户的第一次交互(点击)后再播放
|
||||
console.log("音乐已准备就绪,等待用户首次交互后播放...");
|
||||
} else {
|
||||
musicPlayer.pause();
|
||||
console.log("音乐未启用,已暂停");
|
||||
}
|
||||
// 更新本地显示配置,确保columnAlignments属性存在
|
||||
if (displayConfig) {
|
||||
@@ -936,27 +1006,18 @@ const handleDisplayConfigChange = () => {
|
||||
// 在实际项目中,可能需要通过WebSocket或轮询来更新配置
|
||||
|
||||
onUnmounted(() => {
|
||||
// 1. 强制暂停+静音(双重保险)
|
||||
// 强制暂停音乐
|
||||
musicPlayer.pause();
|
||||
musicPlayer.setMuted(true);
|
||||
// 2. 销毁实例(防止内存泄漏)
|
||||
if (isMusicInitiated.value) {
|
||||
musicPlayer.stop();
|
||||
musicPlayer.destroy();
|
||||
isMusicInitiated.value = false; // 重置初始化状态
|
||||
}
|
||||
// 3. 重置响应式变量
|
||||
isMusicEnabled.value = false;
|
||||
musicPath.value = '';
|
||||
|
||||
document.removeEventListener('click', unlockMusicPlay);
|
||||
document.removeEventListener('touchstart', unlockMusicPlay);
|
||||
|
||||
if (countdownInterval) clearInterval(countdownInterval);
|
||||
window.removeEventListener('resize', handleResize);
|
||||
|
||||
// 移除拖放相关的事件监听
|
||||
document.removeEventListener('mousemove', drag);
|
||||
document.removeEventListener('mouseup', endDrag);
|
||||
|
||||
// 移除触摸事件监听
|
||||
document.removeEventListener('touchmove', touchMove);
|
||||
document.removeEventListener('touchend', endTouch);
|
||||
document.removeEventListener('touchcancel', endTouch);
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import path from 'path' // 新增:引入path模块
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
resolve: { // 新增:配置路径别名
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src') // 让@代表项目根目录下的src文件夹
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user