diff --git a/.vscode/settings.json b/.vscode/settings.json index a466545..9ff8fc8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,9 @@ { - "i18n-ally.localesPaths": [] -} \ No newline at end of file + "i18n-ally.localesPaths": [], + "files.watcherExclude": { + "**/dist/**": true + }, + "search.exclude": { + "**/dist/**": true + } +} diff --git a/jsconfig.json b/jsconfig.json index 77fcff3..5429ea1 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -12,5 +12,6 @@ }, "vueCompilerOptions": { "plugins": ["@uni-helper/uni-types/volar-plugin"] - } + }, + "exclude": ["node_modules", "dist"] } diff --git a/package-lock.json b/package-lock.json index 049156a..55935d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,8 @@ "@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" }, @@ -2422,6 +2424,70 @@ "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", @@ -2438,6 +2504,294 @@ "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", @@ -3424,6 +3778,32 @@ } } }, + "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", @@ -3437,6 +3817,227 @@ "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", @@ -7535,6 +8136,12 @@ "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", @@ -8222,6 +8829,15 @@ } } }, + "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", @@ -8530,6 +9146,18 @@ "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", diff --git a/package.json b/package.json index a50c6be..4fe0d15 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@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" }, diff --git a/project.config.json b/project.config.json index f6d496c..0ffafb0 100644 --- a/project.config.json +++ b/project.config.json @@ -3,7 +3,240 @@ "compileType": "miniprogram", "libVersion": "3.7.7", "packOptions": { - "ignore": [], + "ignore": [ + { + "type": "file", + "value": "static/common/dialog-light.png" + }, + { + "type": "file", + "value": "static/common/dialog-bg.png" + }, + { + "type": "file", + "value": "static/common/dialog-icon.png" + }, + { + "type": "file", + "value": "static/prompt-bg-square.png" + }, + { + "type": "file", + "value": "static/coach-comment.png" + }, + { + "type": "file", + "value": "static/screen-hint-bg.png" + }, + { + "type": "file", + "value": "static/red-team-win.png" + }, + { + "type": "file", + "value": "static/blue-team-win.png" + }, + { + "type": "file", + "value": "static/my-grow.png" + }, + { + "type": "file", + "value": "static/gold-shining.png" + }, + { + "type": "file", + "value": "static/bow-target.png" + }, + { + "type": "file", + "value": "static/matching-bg.png" + }, + { + "type": "file", + "value": "static/versus.png" + }, + { + "type": "file", + "value": "static/point-champion.png" + }, + { + "type": "file", + "value": "static/my-practise.png" + }, + { + "type": "file", + "value": "static/shining-bg.png" + }, + { + "type": "file", + "value": "static/donate.png" + }, + { + "type": "file", + "value": "static/friend-battle.png" + }, + { + "type": "file", + "value": "static/user-upgrade.png" + }, + { + "type": "file", + "value": "static/finish-frame.png" + }, + { + "type": "file", + "value": "static/vip/svip-jian.png" + }, + { + "type": "file", + "value": "static/battle-header.png" + }, + { + "type": "file", + "value": "static/battle-header-melee.png" + }, + { + "type": "file", + "value": "static/player-bg.png" + }, + { + "type": "file", + "value": "static/mvp-blue.png" + }, + { + "type": "file", + "value": "static/finish-tip.png" + }, + { + "type": "file", + "value": "static/2unfinish-tip.png" + }, + { + "type": "file", + "value": "static/have-no-device.png" + }, + { + "type": "file", + "value": "static/device-icon.png" + }, + { + "type": "file", + "value": "static/unfinish-tip.png" + }, + { + "type": "file", + "value": "static/test-tip.png" + }, + { + "type": "file", + "value": "static/mvp-red.png" + }, + { + "type": "file", + "value": "static/vip/svip-lie.png" + }, + { + "type": "file", + "value": "static/mvp-tip.png" + }, + { + "type": "file", + "value": "static/rank/battle-choose.png" + }, + { + "type": "file", + "value": "static/back-to-game-bg.png" + }, + { + "type": "file", + "value": "static/complete-light1.png" + }, + { + "type": "file", + "value": "static/complete-light2.png" + }, + { + "type": "file", + "value": "static/title-2v2.png" + }, + { + "type": "file", + "value": "static/tab-bg.png" + }, + { + "type": "file", + "value": "static/scan.png" + }, + { + "type": "file", + "value": "static/choose-battle-mode.png" + }, + { + "type": "file", + "value": "static/shooter.png" + }, + { + "type": "file", + "value": "static/my-growth.png" + }, + { + "type": "file", + "value": "static/juezhanbang.png" + }, + { + "type": "file", + "value": "static/title-3v3.png" + }, + { + "type": "file", + "value": "static/point-book-tip-bg.png" + }, + { + "type": "file", + "value": "static/reward-us.png" + }, + { + "type": "file", + "value": "static/rank/star.png" + }, + { + "type": "file", + "value": "static/tab-point-book.png" + }, + { + "type": "file", + "value": "static/pk-icon.png" + }, + { + "type": "file", + "value": "static/first-try.png" + }, + { + "type": "file", + "value": "static/row-yellow-bg.png" + }, + { + "type": "file", + "value": "static/room-notfound-title.png" + }, + { + "type": "file", + "value": "static/title-mvp.png" + }, + { + "type": "file", + "value": "static/long-bubble-tall.png" + }, + { + "type": "file", + "value": "static/battle-result.png" + }, + { + "type": "file", + "value": "static/tab-mall.png" + } + ], "include": [] }, "setting": { diff --git a/src/App.vue b/src/App.vue index b62dd14..608b1ec 100644 --- a/src/App.vue +++ b/src/App.vue @@ -7,9 +7,13 @@ 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 @@ -36,6 +40,9 @@ websocket.createWebSocket(token, onShootWsMsg); } if (!newVal) { + matchWebsocket.closeMatchWebSocket({ + reason: "logout" + }); websocket.closeWebSocket(); } }, { @@ -73,18 +80,50 @@ // audioManager.play("射箭声音") } + function onNetworkStatusChange(status) { + matchWebsocket.handleMatchNetworkStatusChange(status); + } + + 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); } onShow(() => { + void audioManager.warmButton(); uni.$on("update-user", emitUpdateUser); uni.$on("update-online", emitUpdateOnline); uni.$on("session-kicked-out", onSessionKickedOut); uni.$on("device-bind-invalid", onDeviceBindInvalid); + if (typeof uni.offNetworkStatusChange === "function") { + uni.offNetworkStatusChange(onNetworkStatusChange); + } + if (typeof uni.onNetworkStatusChange === "function") { + uni.onNetworkStatusChange(onNetworkStatusChange); + } + if (typeof uni.getNetworkType === "function") { + uni.getNetworkType({ + success: onNetworkStatusChange + }); + } const token = uni.getStorageSync( `${uni.getAccountInfoSync().miniProgram.envVersion}_token` ); @@ -99,6 +138,12 @@ uni.$off("update-online", emitUpdateOnline); uni.$off("session-kicked-out", onSessionKickedOut); uni.$off("device-bind-invalid", onDeviceBindInvalid); + if (typeof uni.offNetworkStatusChange === "function") { + uni.offNetworkStatusChange(onNetworkStatusChange); + } + matchWebsocket.closeMatchWebSocket({ + reason: "app-hide" + }); websocket.closeWebSocket(); }); diff --git a/src/apis.js b/src/apis.js index 0352e4b..9c47724 100644 --- a/src/apis.js +++ b/src/apis.js @@ -1,3 +1,5 @@ +import { normalizeBattleApiResult } from "@/utils/matchAdapter"; + let BASE_URL = "https://api.shelingxingqiu.com/api/shoot"; // 默认正式版 try { @@ -6,7 +8,7 @@ try { switch (envVersion) { case "develop": // 开发版 - // BASE_URL = "http://192.168.1.30:8000/api/shoot"; + // BASE_URL = "http://192.168.1.2:8000/api/shoot"; BASE_URL = "https://apitest.shelingxingqiu.com/api/shoot"; break; case "trial": // 体验版 @@ -24,6 +26,7 @@ try { } const ADDONS_BASE_URL = BASE_URL.replace(/\/api\/shoot$/, "/api/shoot"); +const PRACTICE_BASE_URL = BASE_URL.replace(/\/api\/shoot$/, "/api"); // 统一处理业务接口请求,包含登录态、业务错误和 WiFi 连接空响应兼容。 function request(method, url, data = {}, baseUrl = BASE_URL) { @@ -261,12 +264,21 @@ export const createPractiseAPI = (arrows, time, target) => { }); }; -export const startPractiseAPI = () => { - return request("POST", "/user/practice/begin"); +export const createPractiseV2API = (arrows, time, target, deviceId) => { + return request("POST", "/practice/create-v2", { + shootNumber: arrows, + shootTime: time, + targetType: Number(target || 1) * 20, + deviceId, + }, PRACTICE_BASE_URL); }; -export const endPractiseAPI = () => { - return request("POST", "/user/practice/stop"); +export const startPractiseAPI = (id) => { + return request("POST", "/user/practice/begin", { id }); +}; + +export const endPractiseAPI = (id) => { + return request("POST", "/user/practice/stop", { id }); }; export const getPractiseAPI = async (id) => { @@ -553,9 +565,10 @@ export const getReadyAPI = (roomId) => { }; export const getBattleAPI = async (battleId) => { - return request("POST", "/user/match/info", { + const result = await request("POST", "/user/match/info", { id: battleId, }); + return normalizeBattleApiResult(result); }; export const kickPlayerAPI = (number, userId) => { diff --git a/src/audioManager.js b/src/audioManager.js index 156553c..fc7f899 100644 --- a/src/audioManager.js +++ b/src/audioManager.js @@ -95,6 +95,28 @@ export const audioFils = { "https://static.shelingxingqiu.com/shootaudio/%E5%91%BD%E4%B8%AD.mp3" }; +const AUDIO_LOAD_TIMEOUT_MS = 5000; +const AUDIO_WARM_CONCURRENCY = 3; +const AUDIO_WARM_RETRIES = 1; +const AUDIO_WARM_PRIORITY_KEYS = [ + "点击按钮", + "比赛开始", + "练习开始", + "请开始射击", + "轮到你了", + "比赛结束", + "射击无效", + "中场休息", + "下半场开始", + "决金箭轮", + "请蓝方射箭", + "请红方射箭", + "距离合格", + "距离不足", + "未上靶", + "X环", +]; + // 版本控制日志函数 function debugLog(...args) { // 获取当前环境信息 @@ -111,18 +133,15 @@ class AudioManager { constructor() { this.audioMap = new Map(); this.currentPlayingKey = null; - this.maxRetries = 3; - // 多轮统一重试:最多重试的轮次与每轮间隔 - this.maxRetryRounds = 10; - this.retryRoundIntervalMs = 1500; // 显式授权播放标记,防止 iOS 在设置 src 后误播 this.allowPlayMap = new Map(); - // 串行加载相关属性 - this.audioKeys = []; - this.currentLoadingIndex = 0; + // 后台预热与单条加载状态 this.isLoading = false; - this.loadingPromise = null; + this.loadingKeyPromises = new Map(); + this.pendingPlayKeys = new Set(); + this.cacheWritePromises = new Map(); + this.warmAllPromise = null; // 连续播放队列相关属性 this.sequenceQueue = []; @@ -141,19 +160,14 @@ class AudioManager { // 静音开关 this.isMuted = false; this.pendingPlayKey = null; - // 新增:就绪状态映射 this.readyMap = new Map(); - // 新增:首轮失败的音频集合与重试阶段标识 - this.failedLoadKeys = new Set(); - // 加载代数,用于 reloadAll 时作废旧的加载循环 + // 加载代数,用于 reloadAll 时作废旧的异步加载 this.loadGeneration = 0; // 本地路径缓存 Map: { url: localPath } this.localFileCache = uni.getStorageSync("audio_local_files") || {}; // 启动时自动清理过期的缓存文件(URL 已不在 audioFils 中的文件) this.cleanObsoleteCache(); this.bindAudioInterruptionEvents(); - - this.initAudios(); } bindAudioInterruptionEvents() { @@ -217,128 +231,122 @@ class AudioManager { } } - // 初始化音频(两阶段:首轮串行加载全部,次轮仅串行加载失败项一次) - initAudios() { - if (this.isLoading) { - debugLog("音频正在加载中,跳过重复初始化"); - return this.loadingPromise; - } - debugLog("开始串行加载音频..."); - this.isLoading = true; - this.audioKeys = Object.keys(audioFils); - this.currentLoadingIndex = 0; - this.failedLoadKeys.clear(); - - // 增加代数,使得旧的加载循环失效 - this.loadGeneration = (this.loadGeneration || 0) + 1; - const currentGen = this.loadGeneration; - - this.loadingPromise = new Promise((resolve) => { - const finalize = () => { - if (currentGen !== this.loadGeneration) return; - const runRounds = (round) => { - if (currentGen !== this.loadGeneration) return; - // 达到最大轮次或没有失败项,收尾 - if (this.failedLoadKeys.size === 0 || round > this.maxRetryRounds) { - this.isLoading = false; - resolve(); - return; - } - - const retryKeys = Array.from(this.failedLoadKeys); - this.failedLoadKeys.clear(); - debugLog(`开始第 ${round} 轮串行加载,共 ${retryKeys.length} 个`); - - this.loadKeysSequentially( - retryKeys, - () => { - if (currentGen !== this.loadGeneration) return; - // 如仍有失败项,继续下一轮;否则结束 - if (this.failedLoadKeys.size > 0 && round < this.maxRetryRounds) { - setTimeout( - () => runRounds(round + 1), - this.retryRoundIntervalMs - ); - } else { - this.isLoading = false; - resolve(); - } - }, - currentGen - ); - }; - - // 启动第 1 轮重试(如有失败项) - runRounds(1); - }; - this.loadNextAudio(finalize, currentGen); - }); - return this.loadingPromise; + getWarmupKeys() { + return Array.from( + new Set([...AUDIO_WARM_PRIORITY_KEYS, ...Object.keys(audioFils)]) + ); } - // 按自定义列表串行加载音频(避免并发过多) - loadKeysSequentially(keys, onComplete, gen) { - if (gen !== undefined && gen !== this.loadGeneration) return; - let idx = 0; - const list = Array.from(keys); - const next = () => { - if (gen !== undefined && gen !== this.loadGeneration) return; - if (idx >= list.length) { - if (onComplete) onComplete(); - return; - } - const k = list[idx++]; + ensureAudio(key) { + if (!audioFils[key]) return Promise.resolve(false); + if (this.readyMap.get(key) && this.audioMap.has(key)) { + return Promise.resolve(true); + } - // 已就绪的音频不再重载,避免把 ready 状态重置为 false - if (this.readyMap.get(k)) { - setTimeout(next, 50); - return; - } + const loadingPromise = this.loadingKeyPromises.get(key); + if (loadingPromise) return loadingPromise; - // 未就绪:已存在则重载;不存在则创建 - if (this.audioMap.has(k)) { - this.retryLoadAudio(k); - setTimeout(next, 100); - } else { - this.createAudio(k, () => { - setTimeout(next, 100); + let promise; + promise = new Promise((resolve) => { + try { + this.createAudio(key, () => { + resolve(this.readyMap.get(key) === true && this.audioMap.has(key)); }); - return; // createAudio 内部会触发 next + } catch (error) { + debugLog(`音频 ${key} 创建失败`, error); + resolve(false); + } + }).finally(() => { + if (this.loadingKeyPromises.get(key) === promise) { + this.loadingKeyPromises.delete(key); + } + }); + this.loadingKeyPromises.set(key, promise); + return promise; + } + + async warmKeys( + keys, + { concurrency = AUDIO_WARM_CONCURRENCY, retries = AUDIO_WARM_RETRIES } = {} + ) { + const queue = Array.from(new Set(keys)).filter((key) => !!audioFils[key]); + if (!queue.length) return; + + let index = 0; + const worker = async () => { + while (index < queue.length) { + const key = queue[index++]; + let ready = await this.ensureAudio(key); + for (let attempt = 0; !ready && attempt < retries; attempt += 1) { + ready = await this.retryLoadAudio(key); + } } }; - next(); + + const workerCount = Math.min(Math.max(1, concurrency), queue.length); + await Promise.all(Array.from({ length: workerCount }, () => worker())); } - // 串行加载下一个音频(首轮) - loadNextAudio(onComplete, gen) { - if (gen !== undefined && gen !== this.loadGeneration) return; - if (this.currentLoadingIndex >= this.audioKeys.length) { - debugLog("首轮加载遍历完成", this.currentLoadingIndex); - if (onComplete) onComplete(); + warmButton() { + return this.warmKeys(["点击按钮"], { concurrency: 1, retries: 1 }); + } + + warmAll() { + if (this.warmAllPromise) return this.warmAllPromise; + + debugLog("开始后台分级预热全部语音"); + this.isLoading = true; + this.warmAllPromise = this.warmKeys(this.getWarmupKeys()) + .catch((error) => { + debugLog("后台语音预热异常", error); + }) + .finally(() => { + this.isLoading = false; + debugLog("后台语音预热结束", this.getLoadProgress()); + }); + return this.warmAllPromise; + } + + // 保留旧入口兼容测试页或其他历史调用,实际行为改为非阻塞后台预热。 + initAudios() { + return this.warmAll(); + } + + // 创建单个音频实例:缓存命中优先使用,未命中直接使用 CDN。 + createAudio(key, callback) { + const src = audioFils[key]; + const generation = this.loadGeneration; + let completed = false; + + const complete = () => { + if (completed) return; + completed = true; + if (callback) callback(); + }; + + if (!src) { + complete(); return; } - const key = this.audioKeys[this.currentLoadingIndex]; - debugLog( - `开始加载音频 ${this.currentLoadingIndex + 1}/${ - this.audioKeys.length - }: ${key}` - ); - this.createAudio(key, () => { - setTimeout(() => { - this.loadNextAudio(onComplete, gen); - }, 100); - }); - } - // 创建单个音频实例(支持本地缓存) - createAudio(key, callback) { - this.currentLoadingIndex++; - const src = audioFils[key]; + const existingAudio = this.audioMap.get(key); + if (existingAudio && !this.readyMap.get(key)) { + try { + existingAudio.destroy(); + } catch (_) {} + this.audioMap.delete(key); + } + + const isCurrentGeneration = () => generation === this.loadGeneration; + + const setupAudio = (realSrc, resumeAfterReady = false) => { + if (!isCurrentGeneration()) { + complete(); + return; + } - const setupAudio = (realSrc) => { const audio = uni.createInnerAudioContext(); audio.autoplay = false; - audio.src = realSrc; try { if (typeof audio.volume === "number") { audio.volume = this.isMuted ? 0 : 1; @@ -355,63 +363,99 @@ class AudioManager { } }); - const loadTimeout = setTimeout(() => { - debugLog(`音频 ${key} 加载超时`); - this.recordLoadFailure(key); + let loadTimeout = null; + let readyHandled = false; + const clearLoadTimeout = () => { + if (!loadTimeout) return; + clearTimeout(loadTimeout); + loadTimeout = null; + }; + + const handleLoadFailure = (message) => { + if (this.audioMap.get(key) !== audio) return; + clearLoadTimeout(); + debugLog(`音频 ${key} ${message}`); + + const isLocalCache = + realSrc !== src && this.localFileCache[src] === realSrc; + const shouldResume = + this.allowPlayMap.get(key) === true || this.currentPlayingKey === key; + const shouldResumeDirectly = shouldResume && completed; + + this.clearPlayWatchdog(key); + this.allowPlayMap.set(key, false); + if (this.currentPlayingKey === key) { + this.currentPlayingKey = null; + } + if (this.lastPlayKey === key) { + this.lastPlayKey = null; + this.lastPlayAt = 0; + } + this.readyMap.set(key, false); this.audioMap.delete(key); try { audio.destroy(); } catch (_) {} + + if (isLocalCache && isCurrentGeneration()) { + debugLog(`本地缓存失效,立即切换 CDN: ${key}`); + delete this.localFileCache[src]; + uni.setStorageSync("audio_local_files", this.localFileCache); + uni.removeSavedFile({ filePath: realSrc }); + setupAudio(src, shouldResumeDirectly); + void this.cacheRemoteFile(src, key); + return; + } + this.finishPlayback(key, { advanceSequence: true, emitEnded: true, }); - if (callback) callback(); - }, 10000); + complete(); + }; + + loadTimeout = setTimeout(() => { + handleLoadFailure("加载超时"); + }, AUDIO_LOAD_TIMEOUT_MS); audio.onCanplay(() => { + if (this.audioMap.get(key) !== audio) return; + if (readyHandled) return; + readyHandled = true; + if (!isCurrentGeneration()) { + clearLoadTimeout(); + this.audioMap.delete(key); + try { + audio.destroy(); + } catch (_) {} + complete(); + return; + } if (!this.allowPlayMap.get(key)) { try { audio.pause(); } catch (_) {} } - clearTimeout(loadTimeout); + clearLoadTimeout(); this.readyMap.set(key, true); - this.failedLoadKeys.delete(key); - // debugLog(`音频 ${key} 已加载完成`, this.getLoadProgress()); uni.$emit("audioLoaded", key); const loadedAudioKeys = uni.getStorageSync("loadedAudioKeys") || {}; loadedAudioKeys[key] = true; uni.setStorageSync("loadedAudioKeys", loadedAudioKeys); - if (callback) callback(); + complete(); + + if (resumeAfterReady) { + setTimeout(() => { + const isSequenceCurrent = + this.isSequenceRunning && + this.sequenceQueue[this.sequenceIndex] === key; + if (isSequenceCurrent) this._playSingle(key, false); + }, 0); + } }); audio.onError((res) => { - clearTimeout(loadTimeout); - debugLog(`音频 ${key} 加载失败:`, res.errMsg); - // 如果是本地文件加载失败,可能是文件损坏,清除缓存以便下次重新下载 - if (realSrc !== src && this.localFileCache[src] === realSrc) { - debugLog(`本地缓存失效,移除记录: ${key}`); - delete this.localFileCache[src]; - uni.setStorageSync("audio_local_files", this.localFileCache); - // 移除文件 - uni.removeSavedFile({ filePath: realSrc }); - } - this.recordLoadFailure(key); - this.audioMap.delete(key); - try { - audio.destroy(); - } catch (_) {} - this.finishPlayback(key, { - advanceSequence: true, - emitEnded: true, - }); - if (this.readyMap.get(key)) { - // 这里不要去除,不然检查进度的时候由于没有重新加载而进度卡住,等播放失败的时候会重新加载 - // this.readyMap.set(key, false); - } else { - if (callback) callback(); - } + handleLoadFailure(`加载失败:${res.errMsg || "unknown"}`); }); audio.onEnded(() => { @@ -426,58 +470,78 @@ class AudioManager { }); this.audioMap.set(key, audio); + audio.src = realSrc; }; // 检查是否有可用的本地缓存 this.checkLocalFile(src).then((localPath) => { + if (!isCurrentGeneration()) { + complete(); + return; + } if (localPath) { debugLog(`命中本地缓存: ${key}`); setupAudio(localPath); } else { - // 下载并尝试保存 - uni.downloadFile({ - url: src, - timeout: 20000, - success: (res) => { - if (res.tempFilePath) { - // 尝试保存文件到本地存储(持久化) - uni.getFileSystemManager().saveFile({ - tempFilePath: res.tempFilePath, - success: (saveRes) => { - const savedPath = saveRes.savedFilePath; - this.localFileCache[src] = savedPath; - uni.setStorageSync("audio_local_files", this.localFileCache); - debugLog(`音频已缓存到本地: ${key}`); - setupAudio(savedPath); - }, - fail: (err) => { - debugLog( - `保存音频失败(可能空间不足),使用临时文件: ${key}`, - err - ); - setupAudio(res.tempFilePath); - }, - }); - } else { - this.recordLoadFailure(key); - this.finishPlayback(key, { - advanceSequence: true, - emitEnded: true, - }); - if (callback) callback(); - } - }, - fail: () => { - this.recordLoadFailure(key); - this.finishPlayback(key, { - advanceSequence: true, - emitEnded: true, - }); - if (callback) callback(); - }, - }); + setupAudio(src); + void this.cacheRemoteFile(src, key); + } + }).catch(() => { + setupAudio(src); + void this.cacheRemoteFile(src, key); + }); + } + + cacheRemoteFile(url, key) { + if (this.localFileCache[url]) return Promise.resolve(true); + const currentPromise = this.cacheWritePromises.get(url); + if (currentPromise) return currentPromise; + + let promise; + promise = new Promise((resolve) => { + if (typeof uni.downloadFile !== "function") { + resolve(false); + return; + } + + uni.downloadFile({ + url, + timeout: 20000, + success: (res) => { + if (!res.tempFilePath) { + resolve(false); + return; + } + + let fileSystemManager; + try { + fileSystemManager = uni.getFileSystemManager(); + } catch (_) { + resolve(false); + return; + } + + fileSystemManager.saveFile({ + tempFilePath: res.tempFilePath, + success: (saveRes) => { + this.localFileCache[url] = saveRes.savedFilePath; + uni.setStorageSync("audio_local_files", this.localFileCache); + debugLog(`音频已后台缓存到本地: ${key}`); + resolve(true); + }, + fail: () => resolve(false), + }); + }, + fail: () => resolve(false), + }); + }).finally(() => { + if (this.cacheWritePromises.get(url) === promise) { + this.cacheWritePromises.delete(url); } }); + + this.cacheWritePromises.set(url, promise); + return promise; } // 检查本地文件是否有效 @@ -504,11 +568,6 @@ class AudioManager { }); } - // 新增:记录失败(首轮与次轮都会用到) - recordLoadFailure(key) { - this.failedLoadKeys.add(key); - } - clearPlayWatchdog(key) { const timer = this.playWatchdogTimers.get(key); if (timer) { @@ -612,19 +671,14 @@ class AudioManager { } reloadAudioKey(key) { - const audio = this.audioMap.get(key); - if (audio) { - try { - audio.destroy(); - } catch (_) {} - this.audioMap.delete(key); - } - this.readyMap.set(key, false); - this.retryLoadAudio(key); + return this.retryLoadAudio(key); } // 重新加载音频 retryLoadAudio(key) { + const loadingPromise = this.loadingKeyPromises.get(key); + if (loadingPromise) return loadingPromise; + this.clearPlayWatchdog(key); const oldAudio = this.audioMap.get(key); if (oldAudio) { @@ -632,7 +686,9 @@ class AudioManager { oldAudio.destroy(); } catch (_) {} } - this.createAudio(key); + this.audioMap.delete(key); + this.readyMap.set(key, false); + return this.ensureAudio(key); } // 播放指定音频或音频数组(数组则按顺序连续播放) @@ -695,6 +751,32 @@ class AudioManager { } } + waitForAudioAndPlay(key) { + if (this.pendingPlayKeys.has(key)) return; + this.pendingPlayKeys.add(key); + + this.ensureAudio(key) + .then((ready) => { + const isSequenceCurrent = + this.isSequenceRunning && this.sequenceQueue[this.sequenceIndex] === key; + if (!isSequenceCurrent) return; + + if (ready) { + this._playSingle(key, false); + return; + } + + this.finishPlayback(key, { + advanceSequence: true, + emitEnded: true, + force: true, + }); + }) + .finally(() => { + this.pendingPlayKeys.delete(key); + }); + } + // 内部方法:播放单个 key _playSingle(key, forceStopAll = false) { if (this.isInterrupted) { @@ -726,7 +808,7 @@ class AudioManager { } const audio = this.audioMap.get(key); - if (audio) { + if (audio && this.readyMap.get(key)) { // 播放前确保遵循当前静音状态 try { if (typeof audio.volume === "number") { @@ -768,32 +850,8 @@ class AudioManager { this.lastPlayAt = Date.now(); this.startPlayWatchdog(key); } else { - debugLog(`音频 ${key} 不存在,尝试重新加载...`); - this.retryLoadAudio(key); - let loadWaitTimer = null; - const cleanup = () => { - try { - uni.$off("audioLoaded", handler); - } catch (_) {} - if (loadWaitTimer) { - clearTimeout(loadWaitTimer); - loadWaitTimer = null; - } - }; - const handler = (loadedKey) => { - if (loadedKey === key) { - cleanup(); - // 再次校验是否存在且就绪 - const a = this.audioMap.get(key); - if (a && this.readyMap.get(key)) { - this._playSingle(key, false); - } - } - }; - try { - uni.$on("audioLoaded", handler); - } catch (_) {} - loadWaitTimer = setTimeout(cleanup, 12000); + debugLog(`音频 ${key} 尚未就绪,按需加载后播放...`); + this.waitForAudioAndPlay(key); } } @@ -886,9 +944,11 @@ class AudioManager { debugLog("本地音频缓存清理完成"); } - // 手动重置并重新加载所有音频(用于卡住时恢复) + // 手动重置音频实例,并按之前的预热范围在后台恢复。 reloadAll() { - debugLog("执行 reloadAll: 重置所有状态并重新加载"); + debugLog("执行 reloadAll: 重置音频实例并后台恢复"); + const shouldWarmAll = this.warmAllPromise !== null; + this.loadGeneration += 1; // 1. 停止所有播放 this.stopAll(); @@ -903,8 +963,9 @@ class AudioManager { // 3. 重置状态 this.readyMap.clear(); - this.failedLoadKeys.clear(); this.allowPlayMap.clear(); + this.loadingKeyPromises.clear(); + this.pendingPlayKeys.clear(); this.clearAllPlayWatchdogs(); this.currentPlayingKey = null; this.sequenceQueue = []; @@ -915,13 +976,12 @@ class AudioManager { // 这里选择不自动全清,而是依赖 onError 里的单点清除。如果需要彻底重置,可取消注释: // this.clearCache(); - // 4. 强制重置加载锁 + // 4. 重置后台预热状态 this.isLoading = false; - this.loadingPromise = null; - this.currentLoadingIndex = 0; + this.warmAllPromise = null; - // 5. 重新初始化 (initAudios 会自增 loadGeneration,从而终止之前的任何异步循环) - return this.initAudios(); + // 5. 之前已启动过全量预热则继续全量恢复,否则只恢复按钮音效。 + return shouldWarmAll ? this.warmAll() : this.warmButton(); } } diff --git a/src/components/AppFooter.vue b/src/components/AppFooter.vue index bfe2831..f65b8fc 100644 --- a/src/components/AppFooter.vue +++ b/src/components/AppFooter.vue @@ -1,8 +1,8 @@ @@ -64,7 +69,7 @@ const createRoom = debounce(async () => { - + @@ -112,7 +117,7 @@ const createRoom = debounce(async () => { 40厘米全环靶 - 创建房间 + 创建房间 diff --git a/src/components/Guide.vue b/src/components/Guide.vue index 29f2433..9cf5c9d 100644 --- a/src/components/Guide.vue +++ b/src/components/Guide.vue @@ -12,13 +12,13 @@ defineProps({ const bubbleTypes = [ "../static/long-bubble.png", "../static/long-bubble-middle.png", - "../static/long-bubble-tall.png", + "https://static.shelingxingqiu.com/shootmini/static/long-bubble-tall.png", ];