Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad8bc1f152 |
@@ -26,8 +26,6 @@
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-ui": "^1.5.11",
|
||||
"pinia": "2.0.36",
|
||||
"pinia-plugin-persistedstate": "3.2.1",
|
||||
"protobufjs": "^8.7.0",
|
||||
"vue": "^3.4.21",
|
||||
"vue-i18n": "^9.1.9"
|
||||
},
|
||||
@@ -2424,70 +2422,6 @@
|
||||
"vite": "^5.2.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
||||
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
||||
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
||||
@@ -2504,294 +2438,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
||||
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
||||
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
||||
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
||||
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
||||
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
||||
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
||||
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
||||
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "9.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.1.9.tgz",
|
||||
@@ -3778,32 +3424,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz",
|
||||
"integrity": "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.39.0.tgz",
|
||||
"integrity": "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz",
|
||||
@@ -3817,227 +3437,6 @@
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.39.0.tgz",
|
||||
"integrity": "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.39.0.tgz",
|
||||
"integrity": "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.39.0.tgz",
|
||||
"integrity": "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.39.0.tgz",
|
||||
"integrity": "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.39.0.tgz",
|
||||
"integrity": "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz",
|
||||
"integrity": "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.39.0.tgz",
|
||||
"integrity": "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.39.0.tgz",
|
||||
"integrity": "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.39.0.tgz",
|
||||
"integrity": "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.39.0.tgz",
|
||||
"integrity": "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.39.0.tgz",
|
||||
"integrity": "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.39.0.tgz",
|
||||
"integrity": "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz",
|
||||
"integrity": "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.39.0.tgz",
|
||||
"integrity": "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.39.0.tgz",
|
||||
"integrity": "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.39.0.tgz",
|
||||
"integrity": "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.39.0.tgz",
|
||||
"integrity": "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@sinonjs/commons": {
|
||||
"version": "1.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz",
|
||||
@@ -8136,12 +7535,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
@@ -8829,15 +8222,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pinia-plugin-persistedstate": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-3.2.1.tgz",
|
||||
"integrity": "sha512-MK++8LRUsGF7r45PjBFES82ISnPzyO6IZx3CH5vyPseFLZCk1g2kgx6l/nW8pEBKxxd4do0P6bJw+mUSZIEZUQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"pinia": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pirates": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
||||
@@ -9146,18 +8530,6 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/protobufjs": {
|
||||
"version": "8.7.0",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.7.0.tgz",
|
||||
"integrity": "sha512-uu52JNxLh3vsL7tXU/h0gDaywufvuUCTbGSi0NKQKBZ2ZopkmrWQJSQO/EFqzu/5YhiwgVM8rq/a/iVpx4eZ0g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"long": "^5.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"@dcloudio/uni-ui": "^1.5.11",
|
||||
"pinia": "2.0.36",
|
||||
"pinia-plugin-persistedstate": "3.2.1",
|
||||
"protobufjs": "^8.7.0",
|
||||
"vue": "^3.4.21",
|
||||
"vue-i18n": "^9.1.9"
|
||||
},
|
||||
|
||||
@@ -7,13 +7,9 @@
|
||||
onHide
|
||||
} from "@dcloudio/uni-app";
|
||||
import websocket from "@/websocket";
|
||||
import matchWebsocket from "@/matchWebsocket";
|
||||
import {
|
||||
getDeviceBatteryAPI
|
||||
} from "@/apis";
|
||||
import {
|
||||
MESSAGETYPES
|
||||
} from "@/constants";
|
||||
import useStore from "@/store";
|
||||
import {
|
||||
storeToRefs
|
||||
@@ -40,9 +36,6 @@
|
||||
websocket.createWebSocket(token, onShootWsMsg);
|
||||
}
|
||||
if (!newVal) {
|
||||
matchWebsocket.closeMatchWebSocket({
|
||||
reason: "logout"
|
||||
});
|
||||
websocket.closeWebSocket();
|
||||
}
|
||||
}, {
|
||||
@@ -80,26 +73,10 @@
|
||||
// audioManager.play("射箭声音")
|
||||
}
|
||||
|
||||
function connectMatchServerFromMessage(content) {
|
||||
const messages = Array.isArray(content) ? content : [content];
|
||||
messages.forEach((message) => {
|
||||
if (
|
||||
message?.constructor ===
|
||||
MESSAGETYPES.ShootSyncBattleCreateMatchLinkID
|
||||
) {
|
||||
matchWebsocket.connectMatchWebSocketFromNotice(
|
||||
message,
|
||||
user.value.id
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onShootWsMsg(content) {
|
||||
if(content.type === 'shoot-trigger'){
|
||||
onDeviceShoot()
|
||||
}
|
||||
connectMatchServerFromMessage(content);
|
||||
uni.$emit("socket-inbox", content);
|
||||
}
|
||||
|
||||
@@ -122,9 +99,6 @@
|
||||
uni.$off("update-online", emitUpdateOnline);
|
||||
uni.$off("session-kicked-out", onSessionKickedOut);
|
||||
uni.$off("device-bind-invalid", onDeviceBindInvalid);
|
||||
matchWebsocket.closeMatchWebSocket({
|
||||
reason: "app-hide"
|
||||
});
|
||||
websocket.closeWebSocket();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { normalizeBattleApiResult } from "@/utils/matchAdapter";
|
||||
|
||||
let BASE_URL = "https://api.shelingxingqiu.com/api/shoot"; // 默认正式版
|
||||
|
||||
try {
|
||||
@@ -8,7 +6,7 @@ try {
|
||||
|
||||
switch (envVersion) {
|
||||
case "develop": // 开发版
|
||||
// BASE_URL = "http://192.168.1.2:8000/api/shoot";
|
||||
// BASE_URL = "http://192.168.1.30:8000/api/shoot";
|
||||
BASE_URL = "https://apitest.shelingxingqiu.com/api/shoot";
|
||||
break;
|
||||
case "trial": // 体验版
|
||||
@@ -555,10 +553,9 @@ export const getReadyAPI = (roomId) => {
|
||||
};
|
||||
|
||||
export const getBattleAPI = async (battleId) => {
|
||||
const result = await request("POST", "/user/match/info", {
|
||||
return request("POST", "/user/match/info", {
|
||||
id: battleId,
|
||||
});
|
||||
return normalizeBattleApiResult(result);
|
||||
};
|
||||
|
||||
export const kickPlayerAPI = (number, userId) => {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { onShow } from "@dcloudio/uni-app";
|
||||
|
||||
import { getBattleAPI, getUserGameState } from "@/apis";
|
||||
import { debounce } from "@/util";
|
||||
import { returnToBattle } from "@/utils/matchReturn";
|
||||
|
||||
import useStore from "@/store";
|
||||
import { storeToRefs } from "pinia";
|
||||
@@ -68,7 +67,11 @@ const onClick = debounce(async () => {
|
||||
const result = await getBattleAPI();
|
||||
if (result && result.matchId) {
|
||||
await uni.$checkAudio();
|
||||
await returnToBattle(result, navigateOnce);
|
||||
if (result.mode <= 3) {
|
||||
await navigateOnce(`/pages/team-battle/index?battleId=${result.matchId}`);
|
||||
} else {
|
||||
await navigateOnce(`/pages/melee-battle?battleId=${result.matchId}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (game.value.roomID) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import ScreenHint from "@/components/ScreenHint.vue";
|
||||
import BackToGame from "@/components/BackToGame.vue";
|
||||
import {laserAimAPI, getBattleAPI, matchGameAPI} from "@/apis";
|
||||
import { capsuleHeight, debounce } from "@/util";
|
||||
import { returnToBattle } from "@/utils/matchReturn";
|
||||
import AudioManager from "@/audioManager";
|
||||
const props = defineProps({
|
||||
title: {
|
||||
@@ -111,15 +110,6 @@ onShow(() => {
|
||||
showHint.value = false;
|
||||
});
|
||||
|
||||
const navigateTo = (url) =>
|
||||
new Promise((resolve, reject) => {
|
||||
uni.navigateTo({
|
||||
url,
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
|
||||
const backToGame = debounce(async () => {
|
||||
if (isLoading.value) return; // 防止重复点击
|
||||
|
||||
@@ -128,7 +118,15 @@ const backToGame = debounce(async () => {
|
||||
const result = await getBattleAPI();
|
||||
if (result && result.matchId) {
|
||||
await checkAudioProgress();
|
||||
await returnToBattle(result, navigateTo);
|
||||
if (result.mode <= 3) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/team-battle/index?battleId=${result.matchId}`,
|
||||
});
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: `/pages/melee-battle?battleId=${result.matchId}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取当前游戏失败:", error);
|
||||
|
||||
@@ -36,7 +36,6 @@ const createRoom = debounce(async () => {
|
||||
}
|
||||
if (loading.value === true) return;
|
||||
loading.value = true;
|
||||
let keepLoading = false;
|
||||
let size = 2;
|
||||
if (battleMode.value === 2) size = 10;
|
||||
if (battleMode.value === 3) size = 4;
|
||||
@@ -50,18 +49,14 @@ const createRoom = debounce(async () => {
|
||||
if (result.number) {
|
||||
props.onConfirm();
|
||||
await joinRoomAPI(result.number);
|
||||
keepLoading = true;
|
||||
uni.navigateTo({
|
||||
url: "/pages/battle-room?roomNumber=" + result.number + "&target=" + targetMode.value,
|
||||
fail: () => {
|
||||
loading.value = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
if (!keepLoading) loading.value = false;
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -117,7 +112,7 @@ const createRoom = debounce(async () => {
|
||||
<text>40厘米全环靶</text>
|
||||
</view>
|
||||
</view>
|
||||
<SButton :disabled="loading" :onClick="() => { $clickSound(); return createRoom(); }">创建房间</SButton>
|
||||
<SButton :onClick="() => $clickSound(createRoom)">创建房间</SButton>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -85,25 +85,25 @@ const handleUpdateClick = async () => {
|
||||
<!-- 悬浮图标(溢出卡片顶部) -->
|
||||
<image
|
||||
v-if="isNewVersion"
|
||||
src="../static/ota/ota-mascot.png"
|
||||
src="https://static.shelingxingqiu.com/shootmini/static/ota/ota-mascot.png"
|
||||
mode="aspectFit"
|
||||
class="float-icon float-mascot"
|
||||
/>
|
||||
<image
|
||||
v-else-if="isSuccess"
|
||||
src="../static/ota/check-char.png"
|
||||
src="https://static.shelingxingqiu.com/shootmini/static/ota/check-char.png"
|
||||
mode="aspectFit"
|
||||
class="float-icon float-check"
|
||||
/>
|
||||
<image
|
||||
v-else-if="isFailure"
|
||||
src="../static/ota/close-char.png"
|
||||
src="https://static.shelingxingqiu.com/shootmini/static/ota/close-char.png"
|
||||
mode="aspectFit"
|
||||
class="float-icon float-close"
|
||||
/>
|
||||
<image
|
||||
v-else-if="isProgress"
|
||||
src="../static/ota/target-char.png"
|
||||
src="https://static.shelingxingqiu.com/shootmini/static/ota/target-char.png"
|
||||
mode="aspectFit"
|
||||
class="float-icon float-target"
|
||||
/>
|
||||
@@ -111,7 +111,7 @@ const handleUpdateClick = async () => {
|
||||
<!-- 弹窗卡片:overflow:visible 允许按钮溢出底部,背景图通过 ota-bg-clip 独立裁剪保持圆角 -->
|
||||
<view class="ota-dialog">
|
||||
<view class="ota-bg-clip">
|
||||
<image src="../static/ota/ota-bg.png" mode="aspectFill" class="ota-bg" />
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/ota/ota-bg.png" mode="aspectFill" class="ota-bg" />
|
||||
</view>
|
||||
<view
|
||||
class="ota-content"
|
||||
@@ -120,9 +120,9 @@ const handleUpdateClick = async () => {
|
||||
|
||||
<!-- 发现新版本:new-ver.png 已包含标题图,不再重复文字;版本号使用 ota-ver.png 胶囊背景 -->
|
||||
<block v-if="isNewVersion">
|
||||
<image src="../static/ota/new-ver.png" mode="aspectFit" class="new-ver-img" />
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/ota/new-ver.png" mode="aspectFit" class="new-ver-img" />
|
||||
<view v-if="version" class="version-tag-wrap">
|
||||
<image src="../static/ota/ota-ver.png" mode="aspectFit" class="version-tag-bg-img" />
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/ota/ota-ver.png" mode="aspectFit" class="version-tag-bg-img" />
|
||||
<text class="version-tag">{{ version }}</text>
|
||||
</view>
|
||||
<!-- 副标题:如“新版本将优化智能弓体验”,离下方详情 12rpx -->
|
||||
@@ -139,7 +139,7 @@ const handleUpdateClick = async () => {
|
||||
|
||||
<!-- 更新成功:图片左边距 34rpx,文案左边距 44rpx,按钮浮动底部居中 -->
|
||||
<block v-else-if="isSuccess">
|
||||
<image src="../static/ota/update-ok.png" mode="aspectFit" class="result-title-img" style="width: 220rpx; height: 62rpx;" />
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/ota/update-ok.png" mode="aspectFit" class="result-title-img" style="width: 220rpx; height: 62rpx;" />
|
||||
<text class="dialog-desc">请关机并重启智能弓</text>
|
||||
<view class="btn-group-result">
|
||||
<view class="primary-btn" @click="emit('done')">
|
||||
@@ -150,7 +150,7 @@ const handleUpdateClick = async () => {
|
||||
|
||||
<!-- 更新中:复用成功标题图,正文区域展示进度条,无底部按钮 -->
|
||||
<block v-else-if="isProgress">
|
||||
<image src="../static/ota/update_progress.png" mode="aspectFit" class="result-title-img" style="width: 220rpx; height: 62rpx;" />
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/ota/update_progress.png" mode="aspectFit" class="result-title-img" style="width: 220rpx; height: 62rpx;" />
|
||||
<view class="progress-wrap">
|
||||
<view class="progress-track">
|
||||
<view class="progress-fill" :style="{ width: `${progressValue}%` }"></view>
|
||||
@@ -160,7 +160,7 @@ const handleUpdateClick = async () => {
|
||||
|
||||
<!-- 更新失败:图片左边距 34rpx,文案左对齐 44rpx,按钮浮动底部居中 -->
|
||||
<block v-else-if="isFailure">
|
||||
<image src="../static/ota/update-fail.png" mode="aspectFit" class="result-title-img" style="width: 222rpx; height: 62rpx;" />
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/ota/update-fail.png" mode="aspectFit" class="result-title-img" style="width: 222rpx; height: 62rpx;" />
|
||||
<text class="dialog-desc">请确保:</text>
|
||||
<text class="dialog-desc">1、智能弓已开启</text>
|
||||
<text class="dialog-desc">2、网路连接稳定</text>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
export const MESSAGETYPES = {
|
||||
ShootSyncMeArrowID: parseInt("0x789b6b0d"), // 2023451405
|
||||
ShootSyncMePracticeID: parseInt("0xD88AE05E"), // 3632980062
|
||||
ShootSyncBattleCreateMatchLinkID: parseInt("0xF86FF466"), // 2538869862
|
||||
WaitForAllReady: parseInt("0x615C13BE"), // 1633424318
|
||||
AllReady: parseInt("0x1CCB49FD"), // 483084797
|
||||
MeleeAllReady: parseInt("0x37132BD5"), // 924003285
|
||||
|
||||
@@ -1,607 +0,0 @@
|
||||
import {
|
||||
ClientMessageType,
|
||||
ServerMessageType,
|
||||
createAckMessage,
|
||||
createHeartbeatAckMessage,
|
||||
createLeaveMessage,
|
||||
decodeServerMessage,
|
||||
getServerMessageTypeName,
|
||||
} from "@/utils/matchProtocol";
|
||||
import { MESSAGETYPESV2 } from "@/constants";
|
||||
import { getDirectionText } from "@/util";
|
||||
import {
|
||||
normalizeId,
|
||||
normalizeMatchInfo,
|
||||
normalizePlainObject,
|
||||
pickField,
|
||||
} from "@/utils/matchAdapter";
|
||||
|
||||
// 比赛服 websocket 独立管理器:
|
||||
// 负责连接、解码、ACK/LEAVE,并把比赛服消息适配成项目现有 socket-inbox 业务事件。
|
||||
let socket = null;
|
||||
let currentContext = null;
|
||||
let isConnecting = false;
|
||||
let manualClose = false;
|
||||
let audioAckListenerReady = false;
|
||||
let lastReadyRouteKey = "";
|
||||
|
||||
// 后端要求非心跳消息必须等前端语音播报完成后再 ACK。
|
||||
const pendingAcks = [];
|
||||
const ACK_AUDIO_TIMEOUT_MS = 9000;
|
||||
const READY_ROUTE_FALLBACK_DELAY_MS = 300;
|
||||
const ROUND_AUDIO_NAMES = ["一", "二", "三", "四", "五"];
|
||||
|
||||
// 比赛服消息类型先映射成项目里已有的 V2 业务消息,页面仍然复用原来的 socket-inbox 流程。
|
||||
const BUSINESS_TYPE_BY_SERVER_TYPE = {
|
||||
[ServerMessageType.SERVER_MSG_MATCH_READY]: MESSAGETYPESV2.AboutToStart,
|
||||
[ServerMessageType.SERVER_MSG_MATCH_START]: MESSAGETYPESV2.BattleStart,
|
||||
[ServerMessageType.SERVER_MSG_NOW_YOU]: MESSAGETYPESV2.ToSomeoneShoot,
|
||||
[ServerMessageType.SERVER_MSG_SHOT]: MESSAGETYPESV2.ShootResult,
|
||||
[ServerMessageType.SERVER_MSG_NEW_ROUND]: MESSAGETYPESV2.NewRound,
|
||||
[ServerMessageType.SERVER_MSG_MATCH_END]: MESSAGETYPESV2.BattleEnd,
|
||||
[ServerMessageType.SERVER_MSG_TIMEOUT]: MESSAGETYPESV2.BattleEnd,
|
||||
[ServerMessageType.SERVER_MSG_CHECK]: MESSAGETYPESV2.TestDistance,
|
||||
[ServerMessageType.SERVER_MSG_NOT_ENOUGH_DISTANCE]: MESSAGETYPESV2.InvalidShot,
|
||||
};
|
||||
|
||||
const MATCH_READY_SNAPSHOT_PREFIX = "match-ready-snapshot:";
|
||||
export const MATCH_WS_AUDIO_ACK_EVENT = "match-ws-audio-ack";
|
||||
|
||||
function normalizeShootData(shootData) {
|
||||
if (!shootData || typeof shootData !== "object") return shootData;
|
||||
const normalized = { ...shootData };
|
||||
if (normalized.distance === undefined && normalized.dst !== undefined) {
|
||||
normalized.distance = normalized.dst;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
// 兼容普通接口通知的 camelCase 和 protobuf 解码后的 snake_case。
|
||||
function buildBusinessMessage(message) {
|
||||
const businessType = BUSINESS_TYPE_BY_SERVER_TYPE[message.type];
|
||||
if (!businessType) return null;
|
||||
|
||||
const matchInfo = normalizeMatchInfo(message.match_info);
|
||||
const matchId = normalizeId(
|
||||
pickField(message, "matchId", "match_id") ||
|
||||
matchInfo.matchId ||
|
||||
currentContext?.matchId
|
||||
);
|
||||
const shootData = normalizeShootData(
|
||||
matchInfo.shootData ||
|
||||
(message.shoot_data ? normalizePlainObject(message.shoot_data) : undefined)
|
||||
);
|
||||
|
||||
return {
|
||||
...matchInfo,
|
||||
type: businessType,
|
||||
id: matchId,
|
||||
matchId,
|
||||
shootData,
|
||||
sequence: message.sequence,
|
||||
timestamp: message.timestamp,
|
||||
matchWsType: message.type,
|
||||
matchWsTypeName: getServerMessageTypeName(message.type),
|
||||
};
|
||||
}
|
||||
|
||||
function getReadySnapshotKey(matchId) {
|
||||
return `${MATCH_READY_SNAPSHOT_PREFIX}${normalizeId(matchId)}`;
|
||||
}
|
||||
|
||||
function persistReadyTeams(message) {
|
||||
const teams = message?.teams || {};
|
||||
if (Number(message?.mode) > 3) return;
|
||||
|
||||
const bluePlayers = teams[1]?.players || teams["1"]?.players;
|
||||
const redPlayers = teams[2]?.players || teams["2"]?.players;
|
||||
if (Array.isArray(bluePlayers)) uni.setStorageSync("blue-team", bluePlayers);
|
||||
if (Array.isArray(redPlayers)) uni.setStorageSync("red-team", redPlayers);
|
||||
}
|
||||
|
||||
function persistReadySnapshot(message) {
|
||||
const matchId = normalizeId(message?.matchId || message?.id);
|
||||
if (!matchId) return;
|
||||
uni.setStorageSync(getReadySnapshotKey(matchId), {
|
||||
...message,
|
||||
savedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
function getBattlePageUrl(message) {
|
||||
const matchId = normalizeId(message?.matchId || message?.id);
|
||||
const mode = Number(message?.mode);
|
||||
if (!matchId) return "";
|
||||
if (!Number.isFinite(mode)) return "";
|
||||
return mode <= 3
|
||||
? `/pages/team-battle/index?battleId=${matchId}`
|
||||
: `/pages/melee-battle?battleId=${matchId}`;
|
||||
}
|
||||
|
||||
function getCurrentPageInfo() {
|
||||
if (typeof getCurrentPages !== "function") return null;
|
||||
const pages = getCurrentPages();
|
||||
return pages[pages.length - 1] || null;
|
||||
}
|
||||
|
||||
function normalizeRoute(route) {
|
||||
return String(route || "").replace(/^\//, "");
|
||||
}
|
||||
|
||||
function isCurrentBattlePage(matchId) {
|
||||
const page = getCurrentPageInfo();
|
||||
const route = normalizeRoute(page?.route);
|
||||
if (route !== "pages/team-battle/index" && route !== "pages/melee-battle") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const options = page?.options || page?.$page?.options || {};
|
||||
return !matchId || normalizeId(options.battleId) === normalizeId(matchId);
|
||||
}
|
||||
|
||||
function scheduleReadyRouteFallback(message) {
|
||||
const url = getBattlePageUrl(message);
|
||||
const matchId = normalizeId(message?.matchId || message?.id);
|
||||
const routeKey = `${matchId}:${url}`;
|
||||
if (!url || lastReadyRouteKey === routeKey) return;
|
||||
|
||||
lastReadyRouteKey = routeKey;
|
||||
setTimeout(() => {
|
||||
if (isCurrentBattlePage(matchId)) return;
|
||||
|
||||
const currentRoute = normalizeRoute(getCurrentPageInfo()?.route);
|
||||
// battle-room 和 match-page 本身会消费 AboutToStart;这里不绕过页面内状态,避免好友房误触退房逻辑。
|
||||
if (currentRoute === "pages/battle-room" || currentRoute === "pages/match-page") return;
|
||||
|
||||
uni.redirectTo({
|
||||
url,
|
||||
fail: (err) => {
|
||||
console.log("[match-ws] ready route fallback failed", err, { url });
|
||||
},
|
||||
});
|
||||
}, READY_ROUTE_FALLBACK_DELAY_MS);
|
||||
}
|
||||
|
||||
function emitBusinessMessage(message) {
|
||||
if (!message) return;
|
||||
if (message.type === MESSAGETYPESV2.AboutToStart) {
|
||||
persistReadyTeams(message);
|
||||
persistReadySnapshot(message);
|
||||
scheduleReadyRouteFallback(message);
|
||||
}
|
||||
|
||||
uni.$emit("socket-inbox", message);
|
||||
}
|
||||
|
||||
function resolvePlayerTeam(message, playerId) {
|
||||
const teams = message?.teams || {};
|
||||
const redPlayers = teams[2]?.players || teams["2"]?.players || [];
|
||||
return redPlayers.some((item) => String(item.id) === String(playerId))
|
||||
? "red"
|
||||
: "blue";
|
||||
}
|
||||
|
||||
function getNowYouAudioKeys(message) {
|
||||
const current = message?.current || {};
|
||||
const keys = [];
|
||||
|
||||
if (current.goldRound) {
|
||||
keys.push("决金箭轮");
|
||||
} else if (current.round) {
|
||||
const roundIndex = Math.max(0, Number(current.round) - 1);
|
||||
const roundName = ROUND_AUDIO_NAMES[roundIndex] || current.round;
|
||||
keys.push(`第${roundName}轮`);
|
||||
}
|
||||
|
||||
if (String(current.playerId) === String(currentContext?.userId)) {
|
||||
keys.push("轮到你了");
|
||||
return keys;
|
||||
}
|
||||
|
||||
const team = resolvePlayerTeam(message, current.playerId);
|
||||
keys.push(team === "red" ? "请红方射箭" : "请蓝方射箭");
|
||||
return keys;
|
||||
}
|
||||
|
||||
function getShootResultAudioKeys(shootData) {
|
||||
if (!shootData) return [];
|
||||
const keys = [
|
||||
shootData.ring ? `${shootData.ringX ? "X" : shootData.ring}环` : "未上靶",
|
||||
];
|
||||
if (shootData.angle !== null && shootData.angle !== undefined) {
|
||||
keys.push(`向${getDirectionText(shootData.angle)}调整`);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
function getTestDistanceAudioKeys(shootData) {
|
||||
const distance = Number(shootData?.distance ?? shootData?.dst);
|
||||
if (Number.isNaN(distance)) return [];
|
||||
return [distance / 100 >= 5 ? "\u8ddd\u79bb\u5408\u683c" : "\u8ddd\u79bb\u4e0d\u8db3"];
|
||||
}
|
||||
|
||||
function getAckAudioKeys(message, businessMessage) {
|
||||
switch (message.type) {
|
||||
case ServerMessageType.SERVER_MSG_MATCH_START:
|
||||
return ["比赛开始"];
|
||||
case ServerMessageType.SERVER_MSG_NOW_YOU:
|
||||
return getNowYouAudioKeys(businessMessage);
|
||||
case ServerMessageType.SERVER_MSG_SHOT:
|
||||
return getShootResultAudioKeys(businessMessage?.shootData);
|
||||
case ServerMessageType.SERVER_MSG_MATCH_END:
|
||||
case ServerMessageType.SERVER_MSG_TIMEOUT:
|
||||
return ["比赛结束"];
|
||||
case ServerMessageType.SERVER_MSG_CHECK:
|
||||
return getTestDistanceAudioKeys(businessMessage?.shootData);
|
||||
case ServerMessageType.SERVER_MSG_NOT_ENOUGH_DISTANCE:
|
||||
return ["射击无效"];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function shouldCloseAfterAck(message) {
|
||||
return (
|
||||
message.type === ServerMessageType.SERVER_MSG_MATCH_END ||
|
||||
message.type === ServerMessageType.SERVER_MSG_TIMEOUT
|
||||
);
|
||||
}
|
||||
|
||||
// 追加 token 查询参数;如果后端地址已经带 token,则保持原样。
|
||||
function appendQuery(url, key, value) {
|
||||
if (!value || new RegExp(`[?&]${key}=`).test(url)) return url;
|
||||
return `${url}${url.includes("?") ? "&" : "?"}${key}=${encodeURIComponent(
|
||||
value
|
||||
)}`;
|
||||
}
|
||||
|
||||
// 后端可能下发完整 ws 地址,也可能只给 host:port,这里统一转成可连接地址。
|
||||
function normalizeServerUrl(serverAddr, token) {
|
||||
if (!serverAddr || typeof serverAddr !== "string") return "";
|
||||
|
||||
let url = serverAddr.trim();
|
||||
if (!url) return "";
|
||||
|
||||
if (!/^wss?:\/\//i.test(url)) {
|
||||
const [address, query = ""] = url.split("?");
|
||||
const hasPath = address.includes("/");
|
||||
const host = hasPath
|
||||
? address
|
||||
: `${address}${address.includes(":") ? "" : ":8011"}/ws`;
|
||||
url = `ws://${host}${query ? `?${query}` : ""}`;
|
||||
}
|
||||
|
||||
return appendQuery(url, "token", token);
|
||||
}
|
||||
|
||||
// 统一发送二进制 protobuf 消息,并在日志里保留发送类型。
|
||||
function sendBuffer(buffer, label, clientMessage) {
|
||||
if (!socket || !buffer) return;
|
||||
|
||||
const currentSocket = socket;
|
||||
currentSocket.send({
|
||||
data: buffer,
|
||||
success: () => {
|
||||
console.log("发送比赛服 WebSocket 消息", label, clientMessage);
|
||||
},
|
||||
fail: (err) => {
|
||||
if (socket !== currentSocket) return;
|
||||
console.log("比赛服 WebSocket 消息发送失败", label, clientMessage, err);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function sendHeartbeatAck() {
|
||||
// 心跳 ACK 不进入 pendingAcks,收到后立即回复。
|
||||
const clientMessage = {
|
||||
type: ClientMessageType.CLIENT_MSG_HEARTBEAT_ACK,
|
||||
};
|
||||
sendBuffer(
|
||||
createHeartbeatAckMessage(),
|
||||
"CLIENT_MSG_HEARTBEAT_ACK",
|
||||
clientMessage
|
||||
);
|
||||
}
|
||||
|
||||
function sendAck({ matchId, sequence }) {
|
||||
// sequence 由后端处理,前端只原样带回,不做重排和断线补发。
|
||||
if (sequence === undefined || sequence === null || sequence === "") return;
|
||||
const clientMessage = {
|
||||
type: ClientMessageType.CLIENT_MSG_ACK,
|
||||
match_id: matchId,
|
||||
sequence,
|
||||
};
|
||||
sendBuffer(
|
||||
createAckMessage({ matchId, sequence }),
|
||||
"CLIENT_MSG_ACK",
|
||||
clientMessage
|
||||
);
|
||||
}
|
||||
|
||||
function completeAckTask(task) {
|
||||
if (task?.timer) clearTimeout(task.timer);
|
||||
sendAck(task);
|
||||
if (task.leaveAfterAck) {
|
||||
setTimeout(() => {
|
||||
closeMatchWebSocket({ reason: "match-end" });
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function isSameAckTask(task, payload = {}) {
|
||||
if (String(task?.sequence) !== String(payload.sequence)) return false;
|
||||
if (!payload.matchId) return true;
|
||||
return String(task?.matchId) === String(payload.matchId);
|
||||
}
|
||||
|
||||
function completePendingAckByMessage(payload = {}) {
|
||||
if (payload.sequence === undefined || payload.sequence === null || payload.sequence === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = pendingAcks.findIndex((task) => isSameAckTask(task, payload));
|
||||
if (index === -1) return;
|
||||
|
||||
const [task] = pendingAcks.splice(index, 1);
|
||||
console.log("[match-ws] ack after page audio", task.sequence, payload);
|
||||
completeAckTask(task);
|
||||
}
|
||||
|
||||
function flushPendingAcks() {
|
||||
// 每次语音播报完成,只确认队首那条等待相同语音 key 的服务端消息。
|
||||
if (!pendingAcks.length) return;
|
||||
const endedKey = arguments[0];
|
||||
const task = pendingAcks[0];
|
||||
if (task.expectedAudioKey && endedKey && task.expectedAudioKey !== endedKey) {
|
||||
return;
|
||||
}
|
||||
completeAckTask(pendingAcks.shift());
|
||||
}
|
||||
|
||||
function ensureAudioAckListener() {
|
||||
// 复用现有全局 audioEnded 事件,确保 ACK 时机落在播报结束之后。
|
||||
if (audioAckListenerReady) return;
|
||||
uni.$on("audioEnded", flushPendingAcks);
|
||||
uni.$on(MATCH_WS_AUDIO_ACK_EVENT, completePendingAckByMessage);
|
||||
audioAckListenerReady = true;
|
||||
}
|
||||
|
||||
function removeAudioAckListener() {
|
||||
if (!audioAckListenerReady) return;
|
||||
uni.$off("audioEnded", flushPendingAcks);
|
||||
uni.$off(MATCH_WS_AUDIO_ACK_EVENT, completePendingAckByMessage);
|
||||
audioAckListenerReady = false;
|
||||
}
|
||||
|
||||
function queueAckAfterAudio(message, businessMessage) {
|
||||
// 除心跳外,只要服务端带了 sequence,都需要走 ACK;没有语音的消息立即 ACK。
|
||||
if (
|
||||
message.sequence === undefined ||
|
||||
message.sequence === null ||
|
||||
message.sequence === ""
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const task = {
|
||||
matchId: normalizeId(
|
||||
pickField(message, "matchId", "match_id") || currentContext?.matchId
|
||||
),
|
||||
sequence: message.sequence,
|
||||
leaveAfterAck: shouldCloseAfterAck(message),
|
||||
};
|
||||
|
||||
const audioKeys = getAckAudioKeys(message, businessMessage).filter(Boolean);
|
||||
if (!audioKeys.length) {
|
||||
console.log(
|
||||
"[match-ws] ack immediately without audio",
|
||||
getServerMessageTypeName(message.type),
|
||||
message.sequence
|
||||
);
|
||||
completeAckTask(task);
|
||||
return;
|
||||
}
|
||||
|
||||
task.expectedAudioKey = audioKeys[audioKeys.length - 1];
|
||||
task.timer = setTimeout(() => {
|
||||
const index = pendingAcks.indexOf(task);
|
||||
if (index === -1) return;
|
||||
pendingAcks.splice(index, 1);
|
||||
console.log(
|
||||
"[match-ws] ack audio wait timeout",
|
||||
getServerMessageTypeName(message.type),
|
||||
message.sequence,
|
||||
task.expectedAudioKey
|
||||
);
|
||||
completeAckTask(task);
|
||||
}, ACK_AUDIO_TIMEOUT_MS);
|
||||
pendingAcks.push(task);
|
||||
console.log(
|
||||
"[match-ws] ack queued until audioEnded",
|
||||
getServerMessageTypeName(message.type),
|
||||
message.sequence,
|
||||
task.expectedAudioKey
|
||||
);
|
||||
}
|
||||
|
||||
function handleMessage(data) {
|
||||
// 比赛服下发 raw protobuf frame,先解码,再根据消息类型决定是否 ACK。
|
||||
let message;
|
||||
try {
|
||||
message = decodeServerMessage(data);
|
||||
} catch (err) {
|
||||
console.log("[match-ws] decode failed", err, data);
|
||||
return;
|
||||
}
|
||||
|
||||
const decodedMatchId = normalizeId(pickField(message, "matchId", "match_id"));
|
||||
if (decodedMatchId && currentContext) {
|
||||
currentContext.matchId = decodedMatchId;
|
||||
}
|
||||
|
||||
if (message.type === ServerMessageType.SERVER_MSG_HEARTBEAT) {
|
||||
sendHeartbeatAck();
|
||||
return;
|
||||
}
|
||||
|
||||
const typeName = getServerMessageTypeName(message.type);
|
||||
console.log("收到比赛服 WebSocket 消息", typeName, message);
|
||||
|
||||
const businessMessage = buildBusinessMessage(message);
|
||||
if (businessMessage?.matchId && currentContext) {
|
||||
currentContext.matchId = businessMessage.matchId;
|
||||
}
|
||||
queueAckAfterAudio(message, businessMessage);
|
||||
emitBusinessMessage(businessMessage);
|
||||
}
|
||||
|
||||
function sendLeave() {
|
||||
// 主动关闭、比赛结束、页面离开时发 CLIENT_MSG_LEAVE。
|
||||
if (!socket || !currentContext?.matchId) return;
|
||||
const clientMessage = {
|
||||
type: ClientMessageType.CLIENT_MSG_LEAVE,
|
||||
match_id: currentContext.matchId,
|
||||
user_id: currentContext.userId,
|
||||
};
|
||||
sendBuffer(
|
||||
createLeaveMessage({
|
||||
matchId: clientMessage.match_id,
|
||||
userId: clientMessage.user_id,
|
||||
}),
|
||||
"CLIENT_MSG_LEAVE",
|
||||
clientMessage
|
||||
);
|
||||
}
|
||||
|
||||
export function connectMatchWebSocket(options = {}) {
|
||||
// 入口参数来自原 websocket 的比赛服地址通知,不影响原有 websocket 连接。
|
||||
const { serverAddr, matchId, userId, token } = options;
|
||||
const url = normalizeServerUrl(serverAddr, token);
|
||||
const normalizedMatchId = normalizeId(matchId);
|
||||
const normalizedUserId = normalizeId(userId);
|
||||
|
||||
if (!url || !normalizedMatchId) {
|
||||
console.log("[match-ws] missing serverAddr or matchId", options);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
socket &&
|
||||
currentContext?.url === url &&
|
||||
currentContext?.matchId === normalizedMatchId
|
||||
) {
|
||||
// 同一场比赛同一地址重复通知时不重复建连。
|
||||
console.log("[match-ws] already connected or connecting", {
|
||||
matchId: normalizedMatchId,
|
||||
url,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 切换比赛服地址时先关闭旧连接,不发送 LEAVE,避免误通知旧比赛离场。
|
||||
closeMatchWebSocket({ sendLeave: false, reason: "switch" });
|
||||
|
||||
manualClose = false;
|
||||
isConnecting = true;
|
||||
currentContext = {
|
||||
serverAddr,
|
||||
matchId: normalizedMatchId,
|
||||
userId: normalizedUserId,
|
||||
url,
|
||||
};
|
||||
ensureAudioAckListener();
|
||||
|
||||
const socketTask = uni.connectSocket({
|
||||
url,
|
||||
success: () => {
|
||||
console.log("[match-ws] connect requested", {
|
||||
matchId: normalizedMatchId,
|
||||
url,
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
if (socket !== socketTask) return;
|
||||
socket = null;
|
||||
isConnecting = false;
|
||||
console.log("[match-ws] connect failed", err);
|
||||
},
|
||||
});
|
||||
|
||||
socket = socketTask;
|
||||
|
||||
socketTask.onOpen(() => {
|
||||
if (socket !== socketTask) return;
|
||||
isConnecting = false;
|
||||
console.log("[match-ws] connected", {
|
||||
matchId: normalizedMatchId,
|
||||
url,
|
||||
});
|
||||
});
|
||||
|
||||
socketTask.onMessage((res) => {
|
||||
if (socket !== socketTask) return;
|
||||
handleMessage(res.data);
|
||||
});
|
||||
|
||||
socketTask.onError((err) => {
|
||||
if (socket !== socketTask) return;
|
||||
console.log("[match-ws] socket error", err);
|
||||
});
|
||||
|
||||
socketTask.onClose((result) => {
|
||||
if (socket !== socketTask) return;
|
||||
socket = null;
|
||||
isConnecting = false;
|
||||
console.log("[match-ws] closed", {
|
||||
manualClose,
|
||||
result,
|
||||
context: currentContext,
|
||||
});
|
||||
if (!manualClose) removeAudioAckListener();
|
||||
});
|
||||
}
|
||||
|
||||
export function connectMatchWebSocketFromNotice(notice, fallbackUserId) {
|
||||
// 原 websocket 下发的比赛服通知统一从这里转成连接参数。
|
||||
if (!notice) return;
|
||||
connectMatchWebSocket({
|
||||
serverAddr: pickField(notice, "serverAddr", "server_addr"),
|
||||
matchId: pickField(notice, "matchId", "match_id"),
|
||||
userId: pickField(notice, "userId", "user_id") || fallbackUserId,
|
||||
token: notice.token || notice.wsToken || notice.ws_token,
|
||||
});
|
||||
}
|
||||
|
||||
export function closeMatchWebSocket(options = {}) {
|
||||
// 默认关闭时会发送 LEAVE;内部切换连接可通过 sendLeave:false 跳过。
|
||||
const { sendLeave: shouldSendLeave = true, reason = "manual" } = options;
|
||||
|
||||
manualClose = true;
|
||||
pendingAcks.forEach((task) => {
|
||||
if (task?.timer) clearTimeout(task.timer);
|
||||
});
|
||||
pendingAcks.length = 0;
|
||||
|
||||
if (socket) {
|
||||
const currentSocket = socket;
|
||||
if (shouldSendLeave) sendLeave();
|
||||
socket = null;
|
||||
try {
|
||||
currentSocket.close();
|
||||
} catch (err) {
|
||||
console.log("[match-ws] close failed", err);
|
||||
}
|
||||
}
|
||||
|
||||
isConnecting = false;
|
||||
removeAudioAckListener();
|
||||
console.log("[match-ws] close requested", { reason, currentContext });
|
||||
currentContext = null;
|
||||
}
|
||||
|
||||
export default {
|
||||
ClientMessageType,
|
||||
ServerMessageType,
|
||||
connectMatchWebSocket,
|
||||
connectMatchWebSocketFromNotice,
|
||||
closeMatchWebSocket,
|
||||
};
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
getReadyAPI,
|
||||
kickPlayerAPI,
|
||||
} from "@/apis";
|
||||
import { debounce, isLimitError } from "@/util";
|
||||
import { isLimitError } from "@/util";
|
||||
import { MESSAGETYPES, MESSAGETYPESV2 } from "@/constants";
|
||||
import useStore from "@/store";
|
||||
import { storeToRefs } from "pinia";
|
||||
@@ -59,7 +59,6 @@ const allReady = ref(false);
|
||||
const timer = ref(null);
|
||||
const goBattle = ref(false);
|
||||
const showLimitModal = ref(false);
|
||||
const readySubmitting = ref(false);
|
||||
/** 从结算页返回时为 true,跳过进场靶纸语音 */
|
||||
const skipTargetAudio = ref(false);
|
||||
|
||||
@@ -140,22 +139,17 @@ async function refreshRoomData() {
|
||||
// timer.value = setTimeout(refreshRoomData, 2000);
|
||||
}
|
||||
|
||||
const getReady = debounce(async () => {
|
||||
if (readySubmitting.value) return;
|
||||
readySubmitting.value = true;
|
||||
const getReady = async () => {
|
||||
try {
|
||||
await getReadyAPI(roomNumber.value);
|
||||
ready.value = true;
|
||||
} catch (error) {
|
||||
if (isLimitError(error)) {
|
||||
showLimitModal.value = true;
|
||||
return;
|
||||
}
|
||||
console.log("room ready error", error);
|
||||
} finally {
|
||||
readySubmitting.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const closeLimitModal = () => {
|
||||
showLimitModal.value = false;
|
||||
@@ -471,10 +465,7 @@ onBeforeUnmount(() => {
|
||||
<PlayerSeats v-if="room.battleType === 2" :total="room.count || 10" :players="players"
|
||||
:removePlayer="removePlayer" :isOwner="owner.id === user.id" />
|
||||
<view>
|
||||
<SButton
|
||||
:disabled="!canClick || readySubmitting"
|
||||
:onClick="() => { $clickSound(); return getReady(); }"
|
||||
>
|
||||
<SButton :disabled="!canClick" :onClick="() => $clickSound(getReady)">
|
||||
{{
|
||||
allReady.value
|
||||
? "即将进入对局..."
|
||||
|
||||
@@ -27,7 +27,6 @@ const data = ref({});
|
||||
const roomID = ref("");
|
||||
const loading = ref(false);
|
||||
const showLimitModal = ref(false);
|
||||
const createRoomLoading = ref(false);
|
||||
const isSVip = computed(() => user.value.sVip === true);
|
||||
const isVip = computed(() => user.value.vip === true && !isSVip.value);
|
||||
const challengeLimitText = computed(() =>
|
||||
@@ -46,8 +45,6 @@ const enterRoom = debounce(async (number) => {
|
||||
showModal.value = true;
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
let keepLoading = false;
|
||||
try {
|
||||
const room = await getRoomAPI(number);
|
||||
if (!room.number) {
|
||||
@@ -66,22 +63,16 @@ const enterRoom = debounce(async (number) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
keepLoading = true;
|
||||
loading.value = true;
|
||||
uni.navigateTo({
|
||||
url: "/pages/battle-room?roomNumber=" + number,
|
||||
fail: () => {
|
||||
loading.value = false;
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
if (!keepLoading) loading.value = false;
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
const onCreateRoom = debounce(async () => {
|
||||
if (createRoomLoading.value) return;
|
||||
const onCreateRoom = async () => {
|
||||
if (!canEenter(user.value, device.value, online.value)) return;
|
||||
createRoomLoading.value = true;
|
||||
try {
|
||||
const countData = await loadDailyCount();
|
||||
if (isLimitReached(countData.challenge)) {
|
||||
showLimitModal.value = true;
|
||||
@@ -89,10 +80,7 @@ const onCreateRoom = debounce(async () => {
|
||||
}
|
||||
warnning.value = "";
|
||||
showModal.value = true;
|
||||
} finally {
|
||||
createRoomLoading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const closeLimitModal = () => {
|
||||
showLimitModal.value = false;
|
||||
@@ -128,7 +116,6 @@ const goMyRecord = () => {
|
||||
});
|
||||
};
|
||||
onShow(async () => {
|
||||
loading.value = false;
|
||||
if (user.value.id) {
|
||||
const [result] = await Promise.all([
|
||||
getBattleDataAPI(),
|
||||
@@ -221,12 +208,7 @@ onLoad(async (options) => {
|
||||
<view v-if="challengeLimitText" class="pp-text">
|
||||
{{ challengeLimitText }}
|
||||
</view>
|
||||
<SButton
|
||||
width="80%"
|
||||
:rounded="30"
|
||||
:disabled="createRoomLoading"
|
||||
:onClick="() => { $clickSound(); return onCreateRoom(); }"
|
||||
>
|
||||
<SButton width="80%" :rounded="30" :onClick="() => $clickSound(onCreateRoom)">
|
||||
创建约战房
|
||||
</SButton>
|
||||
</view>
|
||||
|
||||
@@ -13,13 +13,7 @@ import TestDistance from "@/components/TestDistance.vue";
|
||||
import SModal from "@/components/SModal.vue";
|
||||
import audioManager from "@/audioManager";
|
||||
import { getBattleAPI, laserCloseAPI } from "@/apis";
|
||||
import {
|
||||
closeMatchWebSocket,
|
||||
connectMatchWebSocket,
|
||||
MATCH_WS_AUDIO_ACK_EVENT,
|
||||
} from "@/matchWebsocket";
|
||||
import { MESSAGETYPESV2 } from "@/constants";
|
||||
import { takeMatchReturnSnapshot } from "@/utils/matchReturn";
|
||||
import useStore from "@/store";
|
||||
import { storeToRefs } from "pinia";
|
||||
const store = useStore();
|
||||
@@ -37,22 +31,12 @@ const playersScores = ref([]);
|
||||
const halfTimeTip = ref(false);
|
||||
const halfRest = ref(false);
|
||||
const HALF_REST_SECONDS = 20;
|
||||
const MATCH_READY_SNAPSHOT_PREFIX = "match-ready-snapshot:";
|
||||
const MATCH_STATUS_BY_TEXT = {
|
||||
MATCH_STATUS_READY: 0,
|
||||
MATCH_STATUS_STARTED: 1,
|
||||
MATCH_STATUS_END: 2,
|
||||
MATCH_STATUS_TIMEOUT: 3,
|
||||
MATCH_STATUS_UNEXPECTEDLY: 4,
|
||||
};
|
||||
const halfRestRemain = ref(HALF_REST_SECONDS);
|
||||
let halfRestTimer = null;
|
||||
/** 控制设备离线提示弹窗的显示状态 */
|
||||
const showOfflineModal = ref(false);
|
||||
/** 记录每位玩家当前半场连续 10 环及以上次数,key 为 playerId,用于触发 tententen 音效 */
|
||||
const xRingStreaks = ref({});
|
||||
let battleEnded = false;
|
||||
let skipNextRestoreOnShow = false;
|
||||
|
||||
function clearHalfRestCountdown() {
|
||||
if (halfRestTimer) {
|
||||
@@ -109,90 +93,6 @@ const currentPlayer = computed(() =>
|
||||
);
|
||||
const isCurrentUserSvip = computed(() => currentPlayer.value?.sVip === true);
|
||||
|
||||
function getReadySnapshotKey(matchId) {
|
||||
return `${MATCH_READY_SNAPSHOT_PREFIX}${String(matchId || "")}`;
|
||||
}
|
||||
|
||||
function normalizeBattleInfo(battleInfo) {
|
||||
if (!battleInfo || typeof battleInfo !== "object") return battleInfo;
|
||||
|
||||
const statusText = battleInfo.statusText || battleInfo.status_text;
|
||||
if (
|
||||
(battleInfo.status === undefined ||
|
||||
battleInfo.status === null ||
|
||||
battleInfo.status === "") &&
|
||||
MATCH_STATUS_BY_TEXT[statusText] !== undefined
|
||||
) {
|
||||
return {
|
||||
...battleInfo,
|
||||
status: MATCH_STATUS_BY_TEXT[statusText],
|
||||
};
|
||||
}
|
||||
|
||||
return battleInfo;
|
||||
}
|
||||
|
||||
function hasKnownStatus(battleInfo) {
|
||||
return !(
|
||||
battleInfo?.status === undefined ||
|
||||
battleInfo?.status === null ||
|
||||
battleInfo?.status === ""
|
||||
);
|
||||
}
|
||||
|
||||
function hasStartedSnapshot(battleInfo) {
|
||||
if (hasKnownStatus(battleInfo)) return Number(battleInfo.status) !== 0;
|
||||
|
||||
const current = battleInfo?.current || {};
|
||||
return !!(
|
||||
current.playerId ||
|
||||
current.startTime ||
|
||||
current.round ||
|
||||
(Array.isArray(battleInfo?.rounds) && battleInfo.rounds.length)
|
||||
);
|
||||
}
|
||||
|
||||
function takeReadySnapshot(matchId) {
|
||||
const key = getReadySnapshotKey(matchId);
|
||||
const snapshot = uni.getStorageSync(key);
|
||||
if (snapshot) uni.removeStorageSync(key);
|
||||
return normalizeBattleInfo(snapshot);
|
||||
}
|
||||
|
||||
function reconnectMatchServer(battleInfo) {
|
||||
const status = Number(battleInfo?.status);
|
||||
if ([2, 3, 4].includes(status)) return;
|
||||
if (!battleInfo?.serverAddr) return;
|
||||
|
||||
connectMatchWebSocket({
|
||||
serverAddr: battleInfo.serverAddr,
|
||||
matchId: battleInfo.matchId || battleId.value,
|
||||
userId: user.value.id,
|
||||
});
|
||||
}
|
||||
|
||||
function closeBattleServer(reason) {
|
||||
if (battleEnded) return;
|
||||
closeMatchWebSocket({ reason });
|
||||
}
|
||||
|
||||
function notifyMatchAudioAck(message) {
|
||||
if (
|
||||
message?.sequence === undefined ||
|
||||
message?.sequence === null ||
|
||||
message?.sequence === ""
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
uni.$emit(MATCH_WS_AUDIO_ACK_EVENT, {
|
||||
matchId: message.matchId || battleId.value,
|
||||
sequence: message.sequence,
|
||||
matchWsType: message.matchWsType,
|
||||
matchWsTypeName: message.matchWsTypeName,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听设备在线状态,大乱斗比赛进行中设备离线时弹窗提示用户
|
||||
*/
|
||||
@@ -203,11 +103,7 @@ watch(online, (newVal, oldVal) => {
|
||||
});
|
||||
|
||||
function recoverData(battleInfo, { force = false } = {}) {
|
||||
battleInfo = normalizeBattleInfo(battleInfo);
|
||||
if (!battleInfo) return;
|
||||
if (battleInfo.status !== undefined) {
|
||||
battleEnded = [2, 4].includes(Number(battleInfo.status));
|
||||
}
|
||||
try {
|
||||
if (battleInfo.way === 1) title.value = "好友约战 - 大乱斗";
|
||||
if (battleInfo.way === 2) title.value = "排位赛 - 大乱斗";
|
||||
@@ -225,27 +121,19 @@ function recoverData(battleInfo, { force = false } = {}) {
|
||||
players.value = [];
|
||||
}
|
||||
|
||||
if (hasKnownStatus(battleInfo)) {
|
||||
start.value = battleInfo.status !== 0;
|
||||
} else if (!hasStartedSnapshot(battleInfo)) {
|
||||
if (start.value !== true) start.value = false;
|
||||
return;
|
||||
} else {
|
||||
start.value = true;
|
||||
}
|
||||
|
||||
if (battleInfo.status === 0) {
|
||||
const readyRemain = (Date.now() - (battleInfo.serverTime || Date.now())) / 1000;
|
||||
const readyRemain = (Date.now() - (battleInfo.createTime || Date.now())) / 1000;
|
||||
if (readyRemain > 0 && readyRemain < 15) {
|
||||
setTimeout(() => uni.$emit("update-timer", 15 - readyRemain - 0.2), 200);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const rounds = Array.isArray(battleInfo.rounds) ? battleInfo.rounds : [];
|
||||
tips.value =
|
||||
(rounds.length !== 2 ? "上" : "下") + "半场:请先射6箭";
|
||||
playersScores.value = rounds.map((r) => ({ ...r.shoots }));
|
||||
(battleInfo.rounds.length !== 2 ? "上" : "下") + "半场:请先射6箭";
|
||||
playersScores.value = battleInfo.rounds.map((r) => ({ ...r.shoots }));
|
||||
const totals = {};
|
||||
players.value.forEach((p) => {
|
||||
const total = playersScores.value.reduce((acc, round) => {
|
||||
@@ -282,18 +170,7 @@ function recoverData(battleInfo, { force = false } = {}) {
|
||||
}
|
||||
|
||||
onLoad(async (options) => {
|
||||
const returnSnapshot = options.fromReturn ? takeMatchReturnSnapshot() : null;
|
||||
skipNextRestoreOnShow = false;
|
||||
if (returnSnapshot?.matchId) battleId.value = returnSnapshot.matchId;
|
||||
else if (options.battleId) battleId.value = options.battleId;
|
||||
if (returnSnapshot) {
|
||||
skipNextRestoreOnShow = true;
|
||||
reconnectMatchServer(returnSnapshot);
|
||||
recoverData(returnSnapshot, { force: true });
|
||||
return;
|
||||
}
|
||||
const readySnapshot = takeReadySnapshot(battleId.value);
|
||||
if (readySnapshot?.status === 0) recoverData(readySnapshot);
|
||||
if (options.battleId) battleId.value = options.battleId;
|
||||
// uni.enableAlertBeforeUnload({
|
||||
// message: "离开比赛可能导致比赛失败,是否继续?",
|
||||
// success: (res) => {
|
||||
@@ -330,9 +207,7 @@ function checkAndPlayTententen(playerId, isTenPlusRingShot) {
|
||||
|
||||
async function onReceiveMessage(msg) {
|
||||
if (Array.isArray(msg)) return;
|
||||
if (msg.type === MESSAGETYPESV2.AboutToStart) {
|
||||
recoverData(msg);
|
||||
} else if (msg.type === MESSAGETYPESV2.BattleStart) {
|
||||
if (msg.type === MESSAGETYPESV2.BattleStart) {
|
||||
clearHalfRestCountdown();
|
||||
halfTimeTip.value = false;
|
||||
halfRest.value = false;
|
||||
@@ -366,8 +241,6 @@ async function onReceiveMessage(msg) {
|
||||
tips.value = "准备下半场";
|
||||
startHalfRestCountdown();
|
||||
} else if (msg.type === MESSAGETYPESV2.BattleEnd) {
|
||||
battleEnded = true;
|
||||
notifyMatchAudioAck(msg);
|
||||
setTimeout(() => {
|
||||
// 全部跳转到新结算页
|
||||
uni.redirectTo({
|
||||
@@ -389,24 +262,14 @@ onBeforeUnmount(() => {
|
||||
});
|
||||
clearHalfRestCountdown();
|
||||
uni.$off("socket-inbox", onReceiveMessage);
|
||||
closeBattleServer("melee-battle-unmount");
|
||||
audioManager.stopAll();
|
||||
});
|
||||
|
||||
onHide(() => {
|
||||
closeBattleServer("melee-battle-hide");
|
||||
});
|
||||
|
||||
onShow(async () => {
|
||||
if (skipNextRestoreOnShow) {
|
||||
skipNextRestoreOnShow = false;
|
||||
return;
|
||||
}
|
||||
if (battleId.value) {
|
||||
const result = normalizeBattleInfo(await getBattleAPI(battleId.value));
|
||||
const result = await getBattleAPI(battleId.value);
|
||||
if (!result) return;
|
||||
if (result.status === 2) {
|
||||
battleEnded = true;
|
||||
uni.showToast({
|
||||
title: "比赛已结束",
|
||||
icon: "none",
|
||||
@@ -415,7 +278,6 @@ onShow(async () => {
|
||||
delta: 2,
|
||||
});
|
||||
} else {
|
||||
reconnectMatchServer(result);
|
||||
recoverData(result, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -632,7 +632,7 @@ onUnmounted(() => {
|
||||
>
|
||||
<!-- Hero: wifi1.png 单图(已内含吉祥物) -->
|
||||
<view class="hero-area">
|
||||
<image src="../static/ota/wifi1.png" mode="aspectFit" style="width: 194rpx; height: 164rpx;" />
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/ota/wifi1.png" mode="aspectFit" style="width: 194rpx; height: 164rpx;" />
|
||||
</view>
|
||||
|
||||
<text class="page-title" :class="{ 'connected-page-title': currentState === 'CONNECTED' }">
|
||||
@@ -721,7 +721,7 @@ onUnmounted(() => {
|
||||
|
||||
<!-- UPDATING -->
|
||||
<view v-else-if="currentState === 'UPDATING'" class="center-page">
|
||||
<image src="../static/ota/target-char.png" mode="aspectFit" style="width: 194rpx; height: 164rpx;" />
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/ota/target-char.png" mode="aspectFit" style="width: 194rpx; height: 164rpx;" />
|
||||
<text class="page-title" style="margin-top: 24rpx;">更新中,请稍等片刻...</text>
|
||||
<view class="progress-wrap">
|
||||
<view class="progress-track">
|
||||
@@ -733,7 +733,7 @@ onUnmounted(() => {
|
||||
|
||||
<!-- DONE -->
|
||||
<view v-else-if="currentState === 'DONE'" class="center-page">
|
||||
<image src="../static/ota/check-char.png" mode="aspectFit" style="width: 194rpx; height: 166rpx;" />
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/ota/check-char.png" mode="aspectFit" style="width: 194rpx; height: 166rpx;" />
|
||||
<text class="page-title" style="margin-top: 24rpx;">更新完成</text>
|
||||
<text class="page-desc-white">请关机并重启智能弓</text>
|
||||
<view class="primary-btn done-btn" style="margin-top:20px" @click="handleDone">
|
||||
@@ -743,7 +743,7 @@ onUnmounted(() => {
|
||||
|
||||
<!-- FAILED -->
|
||||
<view v-else-if="currentState === 'FAILED'" class="center-page">
|
||||
<image src="../static/ota/close-char.png" mode="aspectFit" style="width: 194rpx; height: 164rpx;" />
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/ota/close-char.png" mode="aspectFit" style="width: 194rpx; height: 164rpx;" />
|
||||
<text class="page-title fail-title" style="margin-top: 24rpx;">更新失败</text>
|
||||
<text class="page-desc-white">请确保:</text>
|
||||
<text class="page-desc-white">1、智能弓已开启</text>
|
||||
@@ -756,7 +756,7 @@ onUnmounted(() => {
|
||||
<!-- CONNECTING 底部弹窗 -->
|
||||
<view v-if="currentState === 'CONNECTING'" class="sheet-mask">
|
||||
<view class="sheet" :style="{ marginBottom: keyboardHeight + 'px' }" @click.stop="">
|
||||
<image src="../static/ota/ota-bg.png" mode="aspectFill" class="sheet-bg" />
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/ota/ota-bg.png" mode="aspectFill" class="sheet-bg" />
|
||||
<view class="sheet-inner">
|
||||
|
||||
<!-- 有密码网络 -->
|
||||
|
||||
@@ -86,7 +86,7 @@ const recoverData = (battleInfo, {force = false, arrowOnly = false} = {}) => {
|
||||
start.value = battleInfo.status !== 0;
|
||||
|
||||
if (battleInfo.status === 0) {
|
||||
const readyRemain = (Date.now() - (battleInfo.serverTime || Date.now())) / 1000;
|
||||
const readyRemain = (Date.now() - (battleInfo.createTime || Date.now())) / 1000;
|
||||
if (readyRemain > 0 && readyRemain < 15) {
|
||||
setTimeout(() => {
|
||||
uni.$emit("update-timer", 15 - readyRemain - 0.2);
|
||||
|
||||
@@ -4,7 +4,6 @@ import { onShow } from "@dcloudio/uni-app";
|
||||
|
||||
import { getBattleAPI, getUserGameState } from "@/apis";
|
||||
import { debounce } from "@/util";
|
||||
import { returnToBattle } from "@/utils/matchReturn";
|
||||
|
||||
import useStore from "@/store";
|
||||
import { storeToRefs } from "pinia";
|
||||
@@ -68,7 +67,11 @@ const onClick = debounce(async () => {
|
||||
const result = await getBattleAPI();
|
||||
if (result && result.matchId) {
|
||||
await uni.$checkAudio();
|
||||
await returnToBattle(result, navigateOnce);
|
||||
if (result.mode <= 3) {
|
||||
await navigateOnce(`/pages/team-battle/index?battleId=${result.matchId}`);
|
||||
} else {
|
||||
await navigateOnce(`/pages/melee-battle?battleId=${result.matchId}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (game.value.roomID) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import ScreenHint from "./ScreenHint.vue";
|
||||
import BackToGame from "./BackToGame.vue";
|
||||
import {laserAimAPI, getBattleAPI, matchGameAPI} from "@/apis";
|
||||
import { capsuleHeight, debounce } from "@/util";
|
||||
import { returnToBattle } from "@/utils/matchReturn";
|
||||
import AudioManager from "@/audioManager";
|
||||
const props = defineProps({
|
||||
title: {
|
||||
@@ -121,15 +120,6 @@ onShow(() => {
|
||||
showHint.value = false;
|
||||
});
|
||||
|
||||
const navigateTo = (url) =>
|
||||
new Promise((resolve, reject) => {
|
||||
uni.navigateTo({
|
||||
url,
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
|
||||
const backToGame = debounce(async () => {
|
||||
if (isLoading.value) return; // 防止重复点击
|
||||
|
||||
@@ -138,7 +128,15 @@ const backToGame = debounce(async () => {
|
||||
const result = await getBattleAPI();
|
||||
if (result && result.matchId) {
|
||||
await checkAudioProgress();
|
||||
await returnToBattle(result, navigateTo);
|
||||
if (result.mode <= 3) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/team-battle/index?battleId=${result.matchId}`,
|
||||
});
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: `/pages/melee-battle?battleId=${result.matchId}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取当前游戏失败:", error);
|
||||
|
||||
@@ -13,14 +13,8 @@ import TeamAvatars from "./components/TeamAvatars.vue";
|
||||
import ShootProgress2 from "./components/ShootProgress2.vue";
|
||||
import SModal from "./components/SModal.vue";
|
||||
import { laserCloseAPI, getBattleAPI } from "@/apis";
|
||||
import {
|
||||
closeMatchWebSocket,
|
||||
connectMatchWebSocket,
|
||||
MATCH_WS_AUDIO_ACK_EVENT,
|
||||
} from "@/matchWebsocket";
|
||||
import { MESSAGETYPESV2 } from "@/constants";
|
||||
import { getDirectionText } from "@/util";
|
||||
import { takeMatchReturnSnapshot } from "@/utils/matchReturn";
|
||||
import audioManager, {
|
||||
AUDIO_INTERRUPTION_BEGIN_EVENT,
|
||||
AUDIO_INTERRUPTION_END_EVENT,
|
||||
@@ -48,14 +42,6 @@ const AUDIO_TIMEOUT_MAX = 12000;
|
||||
const BATTLE_CANCEL_RETURN_DELAY = 2000;
|
||||
const ROUND_AUDIO_NAMES = ["一", "二", "三", "四", "五"];
|
||||
const X_RING_STREAKS_KEY = "team-battle-x-ring-streaks";
|
||||
const MATCH_READY_SNAPSHOT_PREFIX = "match-ready-snapshot:";
|
||||
const MATCH_STATUS_BY_TEXT = {
|
||||
MATCH_STATUS_READY: 0,
|
||||
MATCH_STATUS_STARTED: 1,
|
||||
MATCH_STATUS_END: 2,
|
||||
MATCH_STATUS_TIMEOUT: 3,
|
||||
MATCH_STATUS_UNEXPECTEDLY: 4,
|
||||
};
|
||||
const PROGRESS_ZERO_EVENT = "team-battle-progress-zero";
|
||||
const COUNTDOWN_READY_EVENT = "team-battle-countdown-ready";
|
||||
|
||||
@@ -103,7 +89,6 @@ let restoreLoadingTimer = null;
|
||||
let pendingRoundAudio = false;
|
||||
// 一旦收到 BattleEnd,后续普通消息就不再进入队列。
|
||||
let battleEnded = false;
|
||||
let skipNextRestoreOnShow = false;
|
||||
const handledMessageKeys = new Set();
|
||||
const handledMessageKeyOrder = [];
|
||||
const queuedMessageKeys = new Set();
|
||||
@@ -144,68 +129,6 @@ function normalizeTimestamp(value) {
|
||||
return numberValue < 1000000000000 ? numberValue * 1000 : numberValue;
|
||||
}
|
||||
|
||||
function getReadySnapshotKey(matchId) {
|
||||
return `${MATCH_READY_SNAPSHOT_PREFIX}${String(matchId || "")}`;
|
||||
}
|
||||
|
||||
function normalizeBattleInfo(battleInfo) {
|
||||
if (!battleInfo || typeof battleInfo !== "object") return battleInfo;
|
||||
|
||||
const statusText = battleInfo.statusText || battleInfo.status_text;
|
||||
if (
|
||||
(battleInfo.status === undefined ||
|
||||
battleInfo.status === null ||
|
||||
battleInfo.status === "") &&
|
||||
MATCH_STATUS_BY_TEXT[statusText] !== undefined
|
||||
) {
|
||||
return {
|
||||
...battleInfo,
|
||||
status: MATCH_STATUS_BY_TEXT[statusText],
|
||||
};
|
||||
}
|
||||
|
||||
return battleInfo;
|
||||
}
|
||||
|
||||
function hasKnownStatus(battleInfo) {
|
||||
return !(
|
||||
battleInfo?.status === undefined ||
|
||||
battleInfo?.status === null ||
|
||||
battleInfo?.status === ""
|
||||
);
|
||||
}
|
||||
|
||||
function hasStartedSnapshot(battleInfo) {
|
||||
if (hasKnownStatus(battleInfo)) return Number(battleInfo.status) !== 0;
|
||||
|
||||
const current = battleInfo?.current || {};
|
||||
return !!(
|
||||
current.playerId ||
|
||||
current.startTime ||
|
||||
current.round ||
|
||||
(Array.isArray(battleInfo?.rounds) && battleInfo.rounds.length)
|
||||
);
|
||||
}
|
||||
|
||||
function takeReadySnapshot(matchId) {
|
||||
const key = getReadySnapshotKey(matchId);
|
||||
const snapshot = uni.getStorageSync(key);
|
||||
if (snapshot) uni.removeStorageSync(key);
|
||||
return normalizeBattleInfo(snapshot);
|
||||
}
|
||||
|
||||
function reconnectMatchServer(battleInfo) {
|
||||
const status = Number(battleInfo?.status);
|
||||
if ([2, 3, 4].includes(status)) return;
|
||||
if (!battleInfo?.serverAddr) return;
|
||||
|
||||
connectMatchWebSocket({
|
||||
serverAddr: battleInfo.serverAddr,
|
||||
matchId: battleInfo.matchId || battleId.value,
|
||||
userId: user.value.id,
|
||||
});
|
||||
}
|
||||
|
||||
// 从不同消息结构中提取服务端时间,作为恢复和去重的时间基准。
|
||||
function getServerTime(message) {
|
||||
return normalizeTimestamp(
|
||||
@@ -366,10 +289,6 @@ function hideRestoreLoading() {
|
||||
}
|
||||
|
||||
function showRestoreLoading() {
|
||||
if (start.value === false) {
|
||||
hideRestoreLoading();
|
||||
return;
|
||||
}
|
||||
clearRestoreLoadingTimer();
|
||||
restoreLoading.value = true;
|
||||
restoreLoadingTimer = setTimeout(() => {
|
||||
@@ -472,9 +391,7 @@ async function executeBattleTask(task, runId) {
|
||||
if (!task || !isQueueAlive(runId)) return;
|
||||
const type = task.type;
|
||||
|
||||
if (type === MESSAGETYPESV2.AboutToStart) {
|
||||
applyBattleSnapshot(task.message, { restore: true });
|
||||
} else if (type === MESSAGETYPESV2.BattleStart) {
|
||||
if (type === MESSAGETYPESV2.BattleStart) {
|
||||
await runBattleStartTask(task, runId);
|
||||
} else if (type === MESSAGETYPESV2.ToSomeoneShoot) {
|
||||
await runToSomeoneShootTask(task, runId);
|
||||
@@ -485,7 +402,7 @@ async function executeBattleTask(task, runId) {
|
||||
} else if (type === MESSAGETYPESV2.BattleEnd) {
|
||||
await runBattleEndTask(task, runId);
|
||||
} else if (type === MESSAGETYPESV2.InvalidShot) {
|
||||
await runInvalidShotTask(task, runId);
|
||||
await runInvalidShotTask(runId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,24 +448,6 @@ function onAudioEnded(key) {
|
||||
});
|
||||
}
|
||||
|
||||
function notifyMatchAudioAck(task) {
|
||||
const message = task?.message;
|
||||
if (
|
||||
message?.sequence === undefined ||
|
||||
message?.sequence === null ||
|
||||
message?.sequence === ""
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
uni.$emit(MATCH_WS_AUDIO_ACK_EVENT, {
|
||||
matchId: message.matchId || battleId.value,
|
||||
sequence: message.sequence,
|
||||
matchWsType: message.matchWsType,
|
||||
matchWsTypeName: message.matchWsTypeName,
|
||||
});
|
||||
}
|
||||
|
||||
function handleBattleCovered() {
|
||||
if (pendingRestoreTimer) {
|
||||
clearTimeout(pendingRestoreTimer);
|
||||
@@ -563,11 +462,6 @@ function handleBattleRecovered() {
|
||||
scheduleRestoreLatestBattle();
|
||||
}
|
||||
|
||||
function closeBattleServer(reason) {
|
||||
if (battleEnded) return;
|
||||
closeMatchWebSocket({ reason });
|
||||
}
|
||||
|
||||
// 队伍信息优先用接口返回值;接口缺失时使用本地缓存,避免重进页面时头像为空。
|
||||
function loadTeamPlayers(teamInfo, storageKey) {
|
||||
if (Array.isArray(teamInfo?.players)) return [...teamInfo.players];
|
||||
@@ -632,7 +526,6 @@ function applyRestoreNewRoundSnapshot(battleInfo) {
|
||||
|
||||
// 回填比赛基础信息:队伍、比分、轮次、金箭状态等公共字段都在这里统一处理。
|
||||
function applyBattleBase(battleInfo) {
|
||||
battleInfo = normalizeBattleInfo(battleInfo);
|
||||
if (!battleInfo) return;
|
||||
if (battleInfo.matchId) battleId.value = battleInfo.matchId;
|
||||
if (battleInfo.status !== undefined) {
|
||||
@@ -841,8 +734,8 @@ function applyReadyState(battleInfo) {
|
||||
clearProgressZeroWaiters();
|
||||
cancelRoundTipDisplay();
|
||||
|
||||
const serverTime = normalizeTimestamp(battleInfo?.serverTime || Date.now());
|
||||
const readyElapsed = (Date.now() - serverTime) / 1000;
|
||||
const createTime = normalizeTimestamp(battleInfo?.createTime || Date.now());
|
||||
const readyElapsed = (Date.now() - createTime) / 1000;
|
||||
if (readyElapsed > 0 && readyElapsed < READY_SECONDS) {
|
||||
setTimeout(() => {
|
||||
uni.$emit("update-timer", READY_SECONDS - readyElapsed - 0.2);
|
||||
@@ -852,8 +745,6 @@ function applyReadyState(battleInfo) {
|
||||
|
||||
// 快照恢复入口:只把页面拉到服务端最新状态,不重放已经发生过的语音。
|
||||
function applyBattleSnapshot(battleInfo, { restore = false, restoreEventType = 0 } = {}) {
|
||||
battleInfo = normalizeBattleInfo(battleInfo);
|
||||
if (!battleInfo) return;
|
||||
// 快照恢复只负责“把页面拉回最新状态”,不重放历史语音。
|
||||
applyBattleBase(battleInfo);
|
||||
if (battleInfo.status === 0) {
|
||||
@@ -861,12 +752,6 @@ function applyBattleSnapshot(battleInfo, { restore = false, restoreEventType = 0
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasStartedSnapshot(battleInfo)) {
|
||||
hideRestoreLoading();
|
||||
if (start.value === false) applyReadyState(battleInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
start.value = true;
|
||||
showRoundTip.value = false;
|
||||
|
||||
@@ -908,7 +793,6 @@ async function runBattleStartTask(task, runId) {
|
||||
pendingRoundAudio = true;
|
||||
updateShotInfo(task.message);
|
||||
await playAudioKeys("比赛开始", { interrupt: false });
|
||||
notifyMatchAudioAck(task);
|
||||
if (!isQueueAlive(runId)) return;
|
||||
}
|
||||
|
||||
@@ -962,7 +846,6 @@ async function runToSomeoneShootTask(task, runId) {
|
||||
});
|
||||
|
||||
await audioPromise;
|
||||
notifyMatchAudioAck(task);
|
||||
if (!isQueueAlive(runId)) return;
|
||||
|
||||
const remainingSeconds = getRemainingSeconds(battleInfo, task, {
|
||||
@@ -1035,7 +918,6 @@ async function runShootResultTask(task) {
|
||||
const audioKeys = buildShootResultAudioKeys(battleInfo.shootData);
|
||||
if (isTententen) audioKeys.push("tententen");
|
||||
await playAudioKeys(audioKeys, { interrupt: false });
|
||||
notifyMatchAudioAck(task);
|
||||
}
|
||||
|
||||
// 新回合任务:展示上一回合结算弹窗,弹窗关闭后再允许下一轮继续。
|
||||
@@ -1095,7 +977,6 @@ async function runBattleEndTask(task, runId) {
|
||||
|
||||
// 终局语音必须完整播完,再决定跳转或返回。
|
||||
await playAudioKeys("比赛结束", { interrupt: false, timeout: AUDIO_TIMEOUT_MAX });
|
||||
notifyMatchAudioAck(task);
|
||||
if (!isQueueAlive(runId)) return;
|
||||
|
||||
if (matchStatus.value === 2) {
|
||||
@@ -1103,28 +984,20 @@ async function runBattleEndTask(task, runId) {
|
||||
url: `/pages/friend-battle-result?battleId=${battleId.value}`,
|
||||
});
|
||||
} else if (matchStatus.value === 4) {
|
||||
const roomNumber = battleInfo.roomId || store.game.roomNumber || store.game.roomID;
|
||||
setTimeout(() => {
|
||||
if (roomNumber) {
|
||||
uni.redirectTo({
|
||||
url: `/pages/battle-room?roomNumber=${encodeURIComponent(roomNumber)}&fromResult=1`,
|
||||
});
|
||||
} else {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}, BATTLE_CANCEL_RETURN_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
// 无效射击任务:弹 toast 并播报无效射击,不修改比赛主状态。
|
||||
async function runInvalidShotTask(task, runId) {
|
||||
async function runInvalidShotTask(runId) {
|
||||
if (!isQueueAlive(runId)) return;
|
||||
uni.showToast({
|
||||
title: "距离不足,无效",
|
||||
icon: "none",
|
||||
});
|
||||
await playAudioKeys("射击无效", { interrupt: false });
|
||||
notifyMatchAudioAck(task);
|
||||
}
|
||||
|
||||
// 回前台恢复入口:拉取服务端快照,处理结束态,然后继续消费增量队列。
|
||||
@@ -1143,7 +1016,6 @@ async function restoreLatestBattle() {
|
||||
console.log("restore latest battle failed:", err);
|
||||
}
|
||||
if (currentRestoreId !== restoreGeneration) return;
|
||||
result = normalizeBattleInfo(result);
|
||||
if (!result) {
|
||||
hideRestoreLoading();
|
||||
runBattleQueue();
|
||||
@@ -1169,7 +1041,6 @@ async function restoreLatestBattle() {
|
||||
if (result.status === 4) {
|
||||
clearXRingStreaks();
|
||||
}
|
||||
reconnectMatchServer(result);
|
||||
|
||||
if (restoreEventType === MESSAGETYPESV2.NewRound) {
|
||||
const prevRound = getRestorePrevRound(result);
|
||||
@@ -1223,12 +1094,10 @@ function onReceiveMessage(message) {
|
||||
// 新对局入口:彻底清空上一局残留的状态、队列和缓存。
|
||||
onLoad((options) => {
|
||||
console.log('重新进入了')
|
||||
const returnSnapshot = options.fromReturn ? takeMatchReturnSnapshot() : null;
|
||||
skipNextRestoreOnShow = false;
|
||||
// 新对局入口:把所有会串场的状态、队列、时间戳和缓存一次性清空。
|
||||
start.value = null;
|
||||
tips.value = "";
|
||||
battleId.value = returnSnapshot?.matchId || options.battleId || "";
|
||||
battleId.value = options.battleId || "";
|
||||
currentRound.value = 0;
|
||||
roundTipRound.value = 0;
|
||||
goldenRound.value = 0;
|
||||
@@ -1266,16 +1135,6 @@ onLoad((options) => {
|
||||
store.updateShotInfo(0, 0);
|
||||
store.updateTips("");
|
||||
latestShotFlash.value = null;
|
||||
if (returnSnapshot) {
|
||||
skipNextRestoreOnShow = true;
|
||||
applyBattleSnapshot(returnSnapshot, { restore: true });
|
||||
reconnectMatchServer(returnSnapshot);
|
||||
return;
|
||||
}
|
||||
const readySnapshot = takeReadySnapshot(battleId.value);
|
||||
if (readySnapshot?.status === 0) {
|
||||
applyBattleSnapshot(readySnapshot, { restore: true });
|
||||
}
|
||||
scheduleRestoreLatestBattle();
|
||||
});
|
||||
|
||||
@@ -1308,7 +1167,6 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
hideRestoreLoading();
|
||||
invalidateBattleQueue({ stopAudio: true, stopProgress: true });
|
||||
closeBattleServer("team-battle-unmount");
|
||||
console.log('onBeforeUnmount', '页面卸载前')
|
||||
audioManager.stopAll();
|
||||
uni.$off(AUDIO_INTERRUPTION_BEGIN_EVENT, handleBattleCovered);
|
||||
@@ -1318,16 +1176,11 @@ onBeforeUnmount(() => {
|
||||
onHide(()=>{
|
||||
console.log('onHide', '页面大退')
|
||||
handleBattleCovered();
|
||||
closeBattleServer("team-battle-hide");
|
||||
})
|
||||
|
||||
// 每次回到前台都重新拉最新比赛快照,确保画面与后端一致。
|
||||
onShow(() => {
|
||||
console.log('onshow')
|
||||
if (skipNextRestoreOnShow) {
|
||||
skipNextRestoreOnShow = false;
|
||||
return;
|
||||
}
|
||||
scheduleRestoreLatestBattle();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 237 B |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,4 +0,0 @@
|
||||
/* eslint-disable */
|
||||
import * as $protobuf from "protobufjs";
|
||||
const $root=$protobuf.Root.create({nested:{rpc:{ServerMessageType:{SERVER_MSG_UNKNOWN:0,SERVER_MSG_MATCH_READY:1,SERVER_MSG_MATCH_START:2,SERVER_MSG_NOW_YOU:3,SERVER_MSG_SHOT:4,SERVER_MSG_NEW_ROUND:5,SERVER_MSG_MATCH_END:6,SERVER_MSG_TIMEOUT:7,SERVER_MSG_CHECK:8,SERVER_MSG_RAND:9,SERVER_MSG_NOT_ENOUGH_DISTANCE:10,SERVER_MSG_PLAYER_LEFT:11,SERVER_MSG_HEARTBEAT:12,SERVER_MSG_PRACTICE_END:13},ClientMessageType:{CLIENT_MSG_UNKNOWN:0,CLIENT_MSG_HEARTBEAT_ACK:1,CLIENT_MSG_SHOOT_DATA:2,CLIENT_MSG_ACK:3,CLIENT_MSG_LEAVE:4},MatchStatus:{MATCH_STATUS_READY:0,MATCH_STATUS_STARTED:1,MATCH_STATUS_END:2,MATCH_STATUS_TIMEOUT:3,MATCH_STATUS_UNEXPECTEDLY:4},MatchPlayerStatus:{MATCH_PLAYER_STATUS_READY:0,MATCH_PLAYER_STATUS_STARTED:1,MATCH_PLAYER_STATUS_END:2},MatchShoot:{fields:{player_id:{type:"int64",id:1},status:{type:"int32",id:2},x:{type:"float",id:3},y:{type:"float",id:4},ring:{type:"int32",id:5},ring_x:{type:"bool",id:6},angle:{type:"float",id:7},distance:{type:"float",id:8}}},MatchShootList:{fields:{items:{rule:"repeated",type:"MatchShoot",id:1}}},RoundScore:{fields:{total_ring:{type:"int32",id:1},score:{type:"int32",id:2},if_win:{type:"bool",id:3}}},MatchRound:{fields:{shoots:{rule:"map",type:"MatchShootList",id:1,keytype:"int64"},scores:{rule:"map",type:"RoundScore",id:2,keytype:"int32"},round:{type:"int32",id:3},if_gold:{type:"bool",id:4},status:{type:"int32",id:5},gold_round:{type:"int32",id:6}}},PlayerMatchResult:{fields:{total_ring:{type:"int32",id:1},user_id:{type:"int64",id:2},ten_ring_count:{type:"int32",id:3},average_ring:{type:"float",id:4}}},PlayerFull:{fields:{id:{type:"int64",id:1},name:{type:"string",id:2},avatar:{type:"string",id:3},exp:{type:"int32",id:4},score:{type:"int32",id:5},level:{type:"int32",id:6},before_level:{type:"int32",id:7},before_exp:{type:"int32",id:8},current_exp:{type:"int32",id:9},upgrade_exp:{type:"int32",id:10},active:{type:"int32",id:11},device_id:{type:"string",id:12},s_vip:{type:"bool",id:13},vip:{type:"bool",id:14},player_match_result:{type:"PlayerMatchResult",id:15}}},TeamInfo:{fields:{players:{rule:"repeated",type:"PlayerFull",id:1},id:{type:"int32",id:2},name:{type:"string",id:3},score:{type:"int32",id:4}}},CurrentShoot:{fields:{round:{type:"int32",id:1},round_id:{type:"int64",id:2},index:{type:"int32",id:3},start_time:{type:"int64",id:4},player_id:{type:"int64",id:5},gold_round:{type:"bool",id:6},start_time_text:{type:"string",id:7},my_index:{type:"int32",id:8},index_map:{rule:"map",type:"int32",id:9,keytype:"int64"}}},ShootData:{fields:{x:{type:"float",id:1},y:{type:"float",id:2},r:{type:"float",id:3},dst:{type:"float",id:4},m:{type:"string",id:5},adc:{type:"float",id:6},device_id:{type:"string",id:7},shoot_id:{type:"string",id:8}}},PracticeInfo:{fields:{id:{type:"int64",id:1},user_id:{type:"int64",id:2},status:{type:"int32",id:3},status_text:{type:"string",id:4},start_time:{type:"int64",id:5},target_type:{type:"int32",id:6},vip:{type:"bool",id:7},s_vip:{type:"bool",id:8},device_id:{type:"string",id:9},shoot_data:{type:"MatchShoot",id:10},details:{rule:"repeated",type:"MatchShoot",id:11}}},MatchInfo:{fields:{match_id:{type:"int64",id:1},create_time:{type:"int64",id:2},start_time:{type:"int64",id:3},server_time:{type:"int64",id:4},shoot_time:{type:"int32",id:5},shoot_number:{type:"int32",id:6},ready_time:{type:"int32",id:7},way:{type:"int32",id:8},mode:{type:"int32",id:9},status:{type:"MatchStatus",id:10},status_text:{type:"string",id:11},rounds:{rule:"repeated",type:"MatchRound",id:12},teams:{rule:"map",type:"TeamInfo",id:13,keytype:"int32"},current:{type:"CurrentShoot",id:14},next:{type:"CurrentShoot",id:15},shoot_data:{type:"MatchShoot",id:16},win_team:{type:"int32",id:17},mvp:{type:"PlayerFull",id:18},room_id:{type:"string",id:19},result_list:{rule:"repeated",type:"PlayerMatchResult",id:20},timeout_time:{type:"int64",id:21},target_type:{type:"int32",id:22},event_type:{type:"int32",id:23},timeout:{type:"int32",id:24},server_addr:{type:"string",id:25}}},ServerMessage:{fields:{type:{type:"ServerMessageType",id:1},match_id:{type:"int64",id:2},timestamp:{type:"int64",id:3},match_info:{type:"MatchInfo",id:4,oneof:"payload"},shoot_data:{type:"ShootData",id:5,oneof:"payload"},practice_info:{type:"PracticeInfo",id:6,oneof:"payload"},sequence:{type:"int64",id:7}}},ClientMessage:{fields:{type:{type:"ClientMessageType",id:1},match_id:{type:"int64",id:2},user_id:{type:"int64",id:3},sequence:{type:"int64",id:4},data:{type:"bytes",id:5}}}}}});
|
||||
export default $root;
|
||||
@@ -1,121 +0,0 @@
|
||||
// 比赛数据适配器:统一接口返回和比赛服 websocket 的字段结构。
|
||||
// 页面继续使用原来的 camelCase 数据,后端可以返回 snake_case 或新的 matchInfo 包装结构。
|
||||
export const MATCH_STATUS_BY_TEXT = {
|
||||
MATCH_STATUS_READY: 0,
|
||||
MATCH_STATUS_STARTED: 1,
|
||||
MATCH_STATUS_END: 2,
|
||||
MATCH_STATUS_TIMEOUT: 3,
|
||||
MATCH_STATUS_UNEXPECTEDLY: 4,
|
||||
};
|
||||
|
||||
export function pickField(source, camelKey, snakeKey) {
|
||||
return source?.[camelKey] ?? source?.[snakeKey];
|
||||
}
|
||||
|
||||
export function normalizeId(value) {
|
||||
if (value === undefined || value === null || value === "") return "";
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function toCamelKey(key) {
|
||||
return key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||
}
|
||||
|
||||
export function normalizePlainObject(value) {
|
||||
if (Array.isArray(value)) return value.map(normalizePlainObject);
|
||||
if (!value || typeof value !== "object") return value;
|
||||
|
||||
return Object.keys(value).reduce((result, key) => {
|
||||
result[toCamelKey(key)] = normalizePlainObject(value[key]);
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function normalizeMapValues(map = {}) {
|
||||
return Object.keys(map || {}).reduce((result, key) => {
|
||||
result[key] = normalizePlainObject(map[key]);
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function normalizeShootListMap(map = {}) {
|
||||
return Object.keys(map || {}).reduce((result, key) => {
|
||||
const value = map[key];
|
||||
const items = Array.isArray(value)
|
||||
? value
|
||||
: Array.isArray(value?.items)
|
||||
? value.items
|
||||
: [];
|
||||
result[key] = items.map(normalizePlainObject);
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function normalizeRound(round = {}) {
|
||||
const normalized = normalizePlainObject(round);
|
||||
normalized.shoots = normalizeShootListMap(round.shoots || normalized.shoots);
|
||||
normalized.scores = normalizeMapValues(round.scores || normalized.scores);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function normalizeMatchInfo(matchInfo = {}) {
|
||||
if (!matchInfo || typeof matchInfo !== "object") return matchInfo;
|
||||
|
||||
const normalized = normalizePlainObject(matchInfo);
|
||||
const statusText = normalized.statusText || matchInfo.status_text;
|
||||
|
||||
if (
|
||||
(normalized.status === undefined ||
|
||||
normalized.status === null ||
|
||||
normalized.status === "") &&
|
||||
MATCH_STATUS_BY_TEXT[statusText] !== undefined
|
||||
) {
|
||||
normalized.status = MATCH_STATUS_BY_TEXT[statusText];
|
||||
}
|
||||
|
||||
if (matchInfo.teams || normalized.teams) {
|
||||
normalized.teams = normalizeMapValues(matchInfo.teams || normalized.teams);
|
||||
}
|
||||
if (Array.isArray(matchInfo.rounds || normalized.rounds)) {
|
||||
normalized.rounds = (matchInfo.rounds || normalized.rounds).map(normalizeRound);
|
||||
}
|
||||
if (matchInfo.current || normalized.current) {
|
||||
normalized.current = normalizePlainObject(matchInfo.current || normalized.current);
|
||||
}
|
||||
if (matchInfo.next || normalized.next) {
|
||||
normalized.next = normalizePlainObject(matchInfo.next || normalized.next);
|
||||
}
|
||||
if (matchInfo.shoot_data || normalized.shootData) {
|
||||
normalized.shootData = normalizePlainObject(matchInfo.shoot_data || normalized.shootData);
|
||||
}
|
||||
if (Array.isArray(matchInfo.result_list || normalized.resultList)) {
|
||||
normalized.resultList = (matchInfo.result_list || normalized.resultList).map(
|
||||
normalizePlainObject
|
||||
);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function normalizeBattleApiResult(result) {
|
||||
if (!result || typeof result !== "object") return result;
|
||||
|
||||
// request 通常已经解出顶层 data;这里额外兼容未解包和旧版扁平结构。
|
||||
const resultData = result.data && typeof result.data === "object" ? result.data : null;
|
||||
const payload =
|
||||
resultData && (resultData.matchInfo || resultData.match_info)
|
||||
? resultData
|
||||
: result;
|
||||
const matchInfo = payload.matchInfo || payload.match_info || payload;
|
||||
const normalized = normalizeMatchInfo(matchInfo);
|
||||
const serverAddr =
|
||||
payload.serverAddr ||
|
||||
payload.server_addr ||
|
||||
matchInfo.serverAddr ||
|
||||
matchInfo.server_addr ||
|
||||
normalized.serverAddr;
|
||||
|
||||
if (serverAddr) normalized.serverAddr = serverAddr;
|
||||
|
||||
return normalized;
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
import protobuf from "protobufjs/minimal.js";
|
||||
|
||||
const { Reader, Writer } = protobuf;
|
||||
|
||||
// 比赛服 protobuf 协议适配层:
|
||||
// 小程序环境不支持 protobufjs 反射模式里的动态 Function codegen,
|
||||
// 所以这里使用 minimal Reader/Writer 做静态字段解码和客户端消息编码。
|
||||
export const ServerMessageType = {
|
||||
SERVER_MSG_UNKNOWN: 0,
|
||||
SERVER_MSG_MATCH_READY: 1,
|
||||
SERVER_MSG_MATCH_START: 2,
|
||||
SERVER_MSG_NOW_YOU: 3,
|
||||
SERVER_MSG_SHOT: 4,
|
||||
SERVER_MSG_NEW_ROUND: 5,
|
||||
SERVER_MSG_MATCH_END: 6,
|
||||
SERVER_MSG_TIMEOUT: 7,
|
||||
SERVER_MSG_CHECK: 8,
|
||||
SERVER_MSG_RAND: 9,
|
||||
SERVER_MSG_NOT_ENOUGH_DISTANCE: 10,
|
||||
SERVER_MSG_PLAYER_LEFT: 11,
|
||||
SERVER_MSG_HEARTBEAT: 12,
|
||||
SERVER_MSG_PRACTICE_END: 13,
|
||||
};
|
||||
|
||||
export const ClientMessageType = {
|
||||
CLIENT_MSG_UNKNOWN: 0,
|
||||
CLIENT_MSG_HEARTBEAT_ACK: 1,
|
||||
CLIENT_MSG_SHOOT_DATA: 2,
|
||||
CLIENT_MSG_ACK: 3,
|
||||
CLIENT_MSG_LEAVE: 4,
|
||||
};
|
||||
|
||||
// protobufjs 的 enum 默认是 name -> value,这里反转成 value -> name 用于日志打印。
|
||||
const ServerMessageTypeNameByValue = Object.keys(ServerMessageType).reduce(
|
||||
(result, name) => {
|
||||
result[ServerMessageType[name]] = name;
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
// 当前只需要解码比赛服下发的字段并打印,字段表按后端 proto 定义维护。
|
||||
const SCHEMAS = {
|
||||
MatchShoot: {
|
||||
1: { name: "player_id", kind: "int64" },
|
||||
2: { name: "status", kind: "int32" },
|
||||
3: { name: "x", kind: "float" },
|
||||
4: { name: "y", kind: "float" },
|
||||
5: { name: "ring", kind: "int32" },
|
||||
6: { name: "ring_x", kind: "bool" },
|
||||
7: { name: "angle", kind: "float" },
|
||||
8: { name: "distance", kind: "float" },
|
||||
},
|
||||
MatchShootList: {
|
||||
1: { name: "items", kind: "message", type: "MatchShoot", repeated: true },
|
||||
},
|
||||
RoundScore: {
|
||||
1: { name: "total_ring", kind: "int32" },
|
||||
2: { name: "score", kind: "int32" },
|
||||
3: { name: "if_win", kind: "bool" },
|
||||
},
|
||||
MatchRound: {
|
||||
1: {
|
||||
name: "shoots",
|
||||
kind: "map",
|
||||
keyKind: "int64",
|
||||
valueKind: "message",
|
||||
valueType: "MatchShootList",
|
||||
},
|
||||
2: {
|
||||
name: "scores",
|
||||
kind: "map",
|
||||
keyKind: "int32",
|
||||
valueKind: "message",
|
||||
valueType: "RoundScore",
|
||||
},
|
||||
3: { name: "round", kind: "int32" },
|
||||
4: { name: "if_gold", kind: "bool" },
|
||||
5: { name: "status", kind: "int32" },
|
||||
6: { name: "gold_round", kind: "int32" },
|
||||
},
|
||||
PlayerMatchResult: {
|
||||
1: { name: "total_ring", kind: "int32" },
|
||||
2: { name: "user_id", kind: "int64" },
|
||||
3: { name: "ten_ring_count", kind: "int32" },
|
||||
4: { name: "average_ring", kind: "float" },
|
||||
},
|
||||
PlayerFull: {
|
||||
1: { name: "id", kind: "int64" },
|
||||
2: { name: "name", kind: "string" },
|
||||
3: { name: "avatar", kind: "string" },
|
||||
4: { name: "exp", kind: "int32" },
|
||||
5: { name: "score", kind: "int32" },
|
||||
6: { name: "level", kind: "int32" },
|
||||
7: { name: "before_level", kind: "int32" },
|
||||
8: { name: "before_exp", kind: "int32" },
|
||||
9: { name: "current_exp", kind: "int32" },
|
||||
10: { name: "upgrade_exp", kind: "int32" },
|
||||
11: { name: "active", kind: "int32" },
|
||||
12: { name: "device_id", kind: "string" },
|
||||
13: { name: "s_vip", kind: "bool" },
|
||||
14: { name: "vip", kind: "bool" },
|
||||
15: { name: "player_match_result", kind: "message", type: "PlayerMatchResult" },
|
||||
},
|
||||
TeamInfo: {
|
||||
1: { name: "players", kind: "message", type: "PlayerFull", repeated: true },
|
||||
2: { name: "id", kind: "int32" },
|
||||
3: { name: "name", kind: "string" },
|
||||
4: { name: "score", kind: "int32" },
|
||||
},
|
||||
CurrentShoot: {
|
||||
1: { name: "round", kind: "int32" },
|
||||
2: { name: "round_id", kind: "int64" },
|
||||
3: { name: "index", kind: "int32" },
|
||||
4: { name: "start_time", kind: "int64" },
|
||||
5: { name: "player_id", kind: "int64" },
|
||||
6: { name: "gold_round", kind: "bool" },
|
||||
7: { name: "start_time_text", kind: "string" },
|
||||
8: { name: "my_index", kind: "int32" },
|
||||
9: {
|
||||
name: "index_map",
|
||||
kind: "map",
|
||||
keyKind: "int64",
|
||||
valueKind: "int32",
|
||||
},
|
||||
},
|
||||
ShootData: {
|
||||
1: { name: "x", kind: "float" },
|
||||
2: { name: "y", kind: "float" },
|
||||
3: { name: "r", kind: "float" },
|
||||
4: { name: "dst", kind: "float" },
|
||||
5: { name: "m", kind: "string" },
|
||||
6: { name: "adc", kind: "float" },
|
||||
7: { name: "device_id", kind: "string" },
|
||||
8: { name: "shoot_id", kind: "string" },
|
||||
},
|
||||
PracticeInfo: {
|
||||
1: { name: "id", kind: "int64" },
|
||||
2: { name: "user_id", kind: "int64" },
|
||||
3: { name: "status", kind: "int32" },
|
||||
4: { name: "status_text", kind: "string" },
|
||||
5: { name: "start_time", kind: "int64" },
|
||||
6: { name: "target_type", kind: "int32" },
|
||||
7: { name: "vip", kind: "bool" },
|
||||
8: { name: "s_vip", kind: "bool" },
|
||||
9: { name: "device_id", kind: "string" },
|
||||
10: { name: "shoot_data", kind: "message", type: "MatchShoot" },
|
||||
11: { name: "details", kind: "message", type: "MatchShoot", repeated: true },
|
||||
},
|
||||
MatchInfo: {
|
||||
1: { name: "match_id", kind: "int64" },
|
||||
2: { name: "create_time", kind: "int64" },
|
||||
3: { name: "start_time", kind: "int64" },
|
||||
4: { name: "server_time", kind: "int64" },
|
||||
5: { name: "shoot_time", kind: "int32" },
|
||||
6: { name: "shoot_number", kind: "int32" },
|
||||
7: { name: "ready_time", kind: "int32" },
|
||||
8: { name: "way", kind: "int32" },
|
||||
9: { name: "mode", kind: "int32" },
|
||||
10: { name: "status", kind: "int32" },
|
||||
11: { name: "status_text", kind: "string" },
|
||||
12: { name: "rounds", kind: "message", type: "MatchRound", repeated: true },
|
||||
13: {
|
||||
name: "teams",
|
||||
kind: "map",
|
||||
keyKind: "int32",
|
||||
valueKind: "message",
|
||||
valueType: "TeamInfo",
|
||||
},
|
||||
14: { name: "current", kind: "message", type: "CurrentShoot" },
|
||||
15: { name: "next", kind: "message", type: "CurrentShoot" },
|
||||
16: { name: "shoot_data", kind: "message", type: "MatchShoot" },
|
||||
17: { name: "win_team", kind: "int32" },
|
||||
18: { name: "mvp", kind: "message", type: "PlayerFull" },
|
||||
19: { name: "room_id", kind: "string" },
|
||||
20: {
|
||||
name: "result_list",
|
||||
kind: "message",
|
||||
type: "PlayerMatchResult",
|
||||
repeated: true,
|
||||
},
|
||||
21: { name: "timeout_time", kind: "int64" },
|
||||
22: { name: "target_type", kind: "int32" },
|
||||
23: { name: "event_type", kind: "int32" },
|
||||
24: { name: "timeout", kind: "int32" },
|
||||
25: { name: "server_addr", kind: "string" },
|
||||
},
|
||||
ServerMessage: {
|
||||
1: { name: "type", kind: "int32" },
|
||||
2: { name: "match_id", kind: "int64" },
|
||||
3: { name: "timestamp", kind: "int64" },
|
||||
4: { name: "match_info", kind: "message", type: "MatchInfo", oneof: "payload" },
|
||||
5: { name: "shoot_data", kind: "message", type: "ShootData", oneof: "payload" },
|
||||
6: { name: "practice_info", kind: "message", type: "PracticeInfo", oneof: "payload" },
|
||||
7: { name: "sequence", kind: "int64" },
|
||||
},
|
||||
};
|
||||
|
||||
// 小程序 websocket 收到的 data 可能是 ArrayBuffer、TypedArray 或字符串,这里统一成 Uint8Array。
|
||||
function toUint8Array(data) {
|
||||
if (data instanceof Uint8Array) return data;
|
||||
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
||||
if (ArrayBuffer.isView(data)) {
|
||||
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
||||
}
|
||||
if (typeof data === "string") {
|
||||
const bytes = new Uint8Array(data.length);
|
||||
for (let i = 0; i < data.length; i += 1) {
|
||||
bytes[i] = data.charCodeAt(i) & 0xff;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
throw new TypeError("Unsupported match websocket message payload");
|
||||
}
|
||||
|
||||
// uni.sendSocketMessage 发送二进制时使用 ArrayBuffer。
|
||||
function toArrayBuffer(bytes) {
|
||||
const output = new Uint8Array(bytes.length);
|
||||
output.set(bytes);
|
||||
return output.buffer;
|
||||
}
|
||||
|
||||
function readScalar(reader, kind) {
|
||||
switch (kind) {
|
||||
case "int32":
|
||||
return reader.int32();
|
||||
case "int64":
|
||||
// 统一把 int64 转成字符串,避免小程序环境下大整数精度丢失。
|
||||
return reader.int64().toString();
|
||||
case "float":
|
||||
return reader.float();
|
||||
case "bool":
|
||||
return reader.bool();
|
||||
case "string":
|
||||
return reader.string();
|
||||
case "bytes":
|
||||
return Array.from(reader.bytes());
|
||||
default:
|
||||
throw new Error(`Unsupported protobuf scalar kind: ${kind}`);
|
||||
}
|
||||
}
|
||||
|
||||
function readMapEntry(reader, field) {
|
||||
const end = reader.uint32() + reader.pos;
|
||||
let key = "";
|
||||
let value;
|
||||
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
key = readScalar(reader, field.keyKind);
|
||||
break;
|
||||
case 2:
|
||||
value =
|
||||
field.valueKind === "message"
|
||||
? decodeMessage(field.valueType, reader, reader.uint32())
|
||||
: readScalar(reader, field.valueKind);
|
||||
break;
|
||||
default:
|
||||
reader.skipType(tag & 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { key: String(key), value };
|
||||
}
|
||||
|
||||
function readValue(reader, field) {
|
||||
if (field.kind === "message") {
|
||||
return decodeMessage(field.type, reader, reader.uint32());
|
||||
}
|
||||
return readScalar(reader, field.kind);
|
||||
}
|
||||
|
||||
function decodeMessage(schemaName, readerOrData, length) {
|
||||
const schema = SCHEMAS[schemaName];
|
||||
const reader =
|
||||
readerOrData instanceof Reader ? readerOrData : Reader.create(readerOrData);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = {};
|
||||
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
const field = schema[tag >>> 3];
|
||||
|
||||
if (!field) {
|
||||
reader.skipType(tag & 7);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.kind === "map") {
|
||||
const entry = readMapEntry(reader, field);
|
||||
const map = message[field.name] || {};
|
||||
map[entry.key] = entry.value;
|
||||
message[field.name] = map;
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = readValue(reader, field);
|
||||
|
||||
if (field.repeated) {
|
||||
if (!message[field.name]) message[field.name] = [];
|
||||
message[field.name].push(value);
|
||||
} else {
|
||||
message[field.name] = value;
|
||||
}
|
||||
|
||||
if (field.oneof) {
|
||||
message[field.oneof] = field.name;
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
export function decodeServerMessage(data) {
|
||||
// 后端已确认比赛服 websocket 下发的是原始 protobuf frame,直接解 ServerMessage。
|
||||
return decodeMessage("ServerMessage", toUint8Array(data));
|
||||
}
|
||||
|
||||
export function encodeClientMessage(payload) {
|
||||
// ClientMessage 字段名按 proto 定义写入,例如 match_id、user_id。
|
||||
const writer = Writer.create();
|
||||
const matchId = payload.match_id ?? payload.matchId;
|
||||
const userId = payload.user_id ?? payload.userId;
|
||||
|
||||
if (payload.type !== undefined) writer.uint32(8).int32(payload.type);
|
||||
if (matchId !== undefined && matchId !== null) writer.uint32(16).int64(matchId);
|
||||
if (userId !== undefined && userId !== null) writer.uint32(24).int64(userId);
|
||||
if (payload.sequence !== undefined && payload.sequence !== null) {
|
||||
writer.uint32(32).int64(payload.sequence);
|
||||
}
|
||||
if (payload.data !== undefined && payload.data !== null) {
|
||||
writer.uint32(42).bytes(toUint8Array(payload.data));
|
||||
}
|
||||
|
||||
return toArrayBuffer(writer.finish());
|
||||
}
|
||||
|
||||
export function getServerMessageTypeName(type) {
|
||||
return ServerMessageTypeNameByValue[type] || String(type);
|
||||
}
|
||||
|
||||
export function createHeartbeatAckMessage() {
|
||||
// 心跳 ACK 按文档立即回,不等待语音播报。
|
||||
return encodeClientMessage({
|
||||
type: ClientMessageType.CLIENT_MSG_HEARTBEAT_ACK,
|
||||
});
|
||||
}
|
||||
|
||||
export function createAckMessage({ matchId, sequence }) {
|
||||
// 普通消息 ACK 仍携带 sequence,但前端不做 sequence 补发或排序。
|
||||
return encodeClientMessage({
|
||||
type: ClientMessageType.CLIENT_MSG_ACK,
|
||||
match_id: matchId,
|
||||
sequence,
|
||||
});
|
||||
}
|
||||
|
||||
export function createLeaveMessage({ matchId, userId }) {
|
||||
// 比赛结束或页面离开时通知比赛服释放当前玩家连接。
|
||||
return encodeClientMessage({
|
||||
type: ClientMessageType.CLIENT_MSG_LEAVE,
|
||||
match_id: matchId,
|
||||
user_id: userId,
|
||||
});
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { connectMatchWebSocket } from "@/matchWebsocket";
|
||||
import useStore from "@/store";
|
||||
|
||||
export const MATCH_RETURN_SNAPSHOT_KEY = "match-return-snapshot";
|
||||
|
||||
function getMatchId(battleInfo) {
|
||||
return battleInfo?.matchId || battleInfo?.id || "";
|
||||
}
|
||||
|
||||
function getBattlePageUrl(battleInfo) {
|
||||
return Number(battleInfo?.mode) <= 3
|
||||
? "/pages/team-battle/index?fromReturn=1"
|
||||
: "/pages/melee-battle?fromReturn=1";
|
||||
}
|
||||
|
||||
export function takeMatchReturnSnapshot() {
|
||||
const snapshot = uni.getStorageSync(MATCH_RETURN_SNAPSHOT_KEY);
|
||||
if (snapshot) uni.removeStorageSync(MATCH_RETURN_SNAPSHOT_KEY);
|
||||
return snapshot && typeof snapshot === "object" ? snapshot : null;
|
||||
}
|
||||
|
||||
export async function returnToBattle(battleInfo, navigate) {
|
||||
const matchId = getMatchId(battleInfo);
|
||||
if (!battleInfo || !matchId || !battleInfo.serverAddr) {
|
||||
uni.showToast({
|
||||
title: "比赛连接信息异常",
|
||||
icon: "none",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const store = useStore();
|
||||
uni.setStorageSync(MATCH_RETURN_SNAPSHOT_KEY, battleInfo);
|
||||
connectMatchWebSocket({
|
||||
serverAddr: battleInfo.serverAddr,
|
||||
matchId,
|
||||
userId: store.user?.id,
|
||||
});
|
||||
|
||||
await navigate(getBattlePageUrl(battleInfo));
|
||||
return true;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ function createWebSocket(token, onMessage) {
|
||||
|
||||
switch (envVersion) {
|
||||
case "develop": // 开发版
|
||||
// url = "ws://192.168.1.2:8000/socket";
|
||||
// url = "ws://192.168.1.30:8000/socket";
|
||||
url = "wss://apitest.shelingxingqiu.com/socket";
|
||||
break;
|
||||
case "trial": // 体验版
|
||||
|
||||