| @@ -10,6 +10,7 @@ | |||
| "lint-fix": "eslint --fix src/" | |||
| }, | |||
| "dependencies": { | |||
| "archiver": "^7.0.1", | |||
| "axios": "^1.13.5", | |||
| "cheerio": "^1.2.0", | |||
| "cos-nodejs-sdk-v5": "^2.14.0", | |||
| @@ -8,6 +8,9 @@ importers: | |||
| .: | |||
| dependencies: | |||
| archiver: | |||
| specifier: ^7.0.1 | |||
| version: 7.0.1 | |||
| axios: | |||
| specifier: ^1.13.5 | |||
| version: 1.13.5 | |||
| @@ -264,6 +267,10 @@ packages: | |||
| cpu: [x64] | |||
| os: [win32] | |||
| '@isaacs/cliui@8.0.2': | |||
| resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} | |||
| engines: {node: '>=12'} | |||
| '@jridgewell/gen-mapping@0.3.13': | |||
| resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} | |||
| @@ -277,6 +284,10 @@ packages: | |||
| '@jridgewell/trace-mapping@0.3.31': | |||
| resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} | |||
| '@pkgjs/parseargs@0.11.0': | |||
| resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} | |||
| engines: {node: '>=14'} | |||
| '@puppeteer/browsers@2.13.0': | |||
| resolution: {integrity: sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==} | |||
| engines: {node: '>=18'} | |||
| @@ -309,6 +320,10 @@ packages: | |||
| a-sync-waterfall@1.0.1: | |||
| resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==} | |||
| abort-controller@3.0.0: | |||
| resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} | |||
| engines: {node: '>=6.5'} | |||
| accepts@1.3.8: | |||
| resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} | |||
| engines: {node: '>= 0.6'} | |||
| @@ -375,6 +390,10 @@ packages: | |||
| resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} | |||
| engines: {node: '>=8'} | |||
| ansi-regex@6.2.2: | |||
| resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} | |||
| engines: {node: '>=12'} | |||
| ansi-styles@1.0.0: | |||
| resolution: {integrity: sha512-3iF4FIKdxaVYT3JqQuY3Wat/T2t7TRbbQ94Fu50ZUCbLy4TFbTzr90NOHQodQkNqmeEGCw8WbeP78WNi6SKYUA==} | |||
| engines: {node: '>=0.8.0'} | |||
| @@ -391,6 +410,10 @@ packages: | |||
| resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} | |||
| engines: {node: '>=8'} | |||
| ansi-styles@6.2.3: | |||
| resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} | |||
| engines: {node: '>=12'} | |||
| any-promise@1.3.0: | |||
| resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} | |||
| @@ -405,10 +428,18 @@ packages: | |||
| resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} | |||
| engines: {node: '>= 10'} | |||
| archiver-utils@5.0.2: | |||
| resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} | |||
| engines: {node: '>= 14'} | |||
| archiver@5.3.2: | |||
| resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} | |||
| engines: {node: '>= 10'} | |||
| archiver@7.0.1: | |||
| resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} | |||
| engines: {node: '>= 14'} | |||
| argparse@1.0.10: | |||
| resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} | |||
| @@ -784,6 +815,10 @@ packages: | |||
| buffer-crc32@0.2.13: | |||
| resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} | |||
| buffer-crc32@1.0.0: | |||
| resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} | |||
| engines: {node: '>=8.0.0'} | |||
| buffer-equal-constant-time@1.0.1: | |||
| resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} | |||
| @@ -797,6 +832,9 @@ packages: | |||
| buffer@5.7.1: | |||
| resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} | |||
| buffer@6.0.3: | |||
| resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} | |||
| buffers@0.1.1: | |||
| resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} | |||
| engines: {node: '>=0.2.0'} | |||
| @@ -1003,6 +1041,10 @@ packages: | |||
| resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} | |||
| engines: {node: '>= 10'} | |||
| compress-commons@6.0.2: | |||
| resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} | |||
| engines: {node: '>= 14'} | |||
| concat-map@0.0.1: | |||
| resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} | |||
| @@ -1081,6 +1123,10 @@ packages: | |||
| resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} | |||
| engines: {node: '>= 10'} | |||
| crc32-stream@6.0.0: | |||
| resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} | |||
| engines: {node: '>= 14'} | |||
| create-error-class@3.0.2: | |||
| resolution: {integrity: sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==} | |||
| engines: {node: '>=0.10.0'} | |||
| @@ -1092,6 +1138,10 @@ packages: | |||
| cross-spawn@5.1.0: | |||
| resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} | |||
| cross-spawn@7.0.6: | |||
| resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} | |||
| engines: {node: '>= 8'} | |||
| css-select@5.2.2: | |||
| resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} | |||
| @@ -1286,6 +1336,9 @@ packages: | |||
| duplexer2@0.1.4: | |||
| resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} | |||
| eastasianwidth@0.2.0: | |||
| resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} | |||
| ecc-jsbn@0.1.2: | |||
| resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} | |||
| @@ -1298,6 +1351,9 @@ packages: | |||
| emoji-regex@8.0.0: | |||
| resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} | |||
| emoji-regex@9.2.2: | |||
| resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} | |||
| empower-core@0.6.2: | |||
| resolution: {integrity: sha512-w9QJ4ROqcjJHWNw+TvpKVeLQV1GQtoFO6aqKoj5IlHi0qxG1Y2157Kg6+5ujs5Bxzm8AgOiOvBCRbNkt6RPe9Q==} | |||
| @@ -1484,9 +1540,17 @@ packages: | |||
| resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} | |||
| engines: {node: '>=0.10.0'} | |||
| event-target-shim@5.0.1: | |||
| resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} | |||
| engines: {node: '>=6'} | |||
| events-universal@1.0.1: | |||
| resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} | |||
| events@3.3.0: | |||
| resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} | |||
| engines: {node: '>=0.8.x'} | |||
| exceljs@4.4.0: | |||
| resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==} | |||
| engines: {node: '>=8.3.0'} | |||
| @@ -1643,6 +1707,10 @@ packages: | |||
| resolution: {integrity: sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==} | |||
| engines: {node: '>=0.10.0'} | |||
| foreground-child@3.3.1: | |||
| resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} | |||
| engines: {node: '>=14'} | |||
| forever-agent@0.6.1: | |||
| resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} | |||
| @@ -1762,6 +1830,10 @@ packages: | |||
| glob-parent@2.0.0: | |||
| resolution: {integrity: sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==} | |||
| glob@10.4.5: | |||
| resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} | |||
| hasBin: true | |||
| glob@7.2.3: | |||
| resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} | |||
| deprecated: Glob versions prior to v9 are no longer supported | |||
| @@ -2268,6 +2340,9 @@ packages: | |||
| isstream@0.1.2: | |||
| resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} | |||
| jackspeak@3.4.3: | |||
| resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} | |||
| jest-diff@18.1.0: | |||
| resolution: {integrity: sha512-PzsL3/aLCOfJyvF6cqp6N6kzkImNfDXAkWIU/9y84WPPTf82Dnhkxex/LD/3nR6Z38VBrsefGTQLSF4yoPlMgg==} | |||
| @@ -2537,6 +2612,9 @@ packages: | |||
| resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} | |||
| engines: {node: '>=0.10.0'} | |||
| lru-cache@10.4.3: | |||
| resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} | |||
| lru-cache@4.1.5: | |||
| resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} | |||
| @@ -2637,9 +2715,17 @@ packages: | |||
| resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} | |||
| engines: {node: '>=10'} | |||
| minimatch@9.0.9: | |||
| resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} | |||
| engines: {node: '>=16 || 14 >=14.17'} | |||
| minimist@1.2.8: | |||
| resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} | |||
| minipass@7.1.3: | |||
| resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} | |||
| engines: {node: '>=16 || 14 >=14.17'} | |||
| mitt@3.0.1: | |||
| resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} | |||
| @@ -2919,6 +3005,9 @@ packages: | |||
| package-hash@1.2.0: | |||
| resolution: {integrity: sha512-W5ILqaI3G6bXDuYb7TrQ95TFHfFdjiunpp61PAXj7z32TgJ5NIBaoqZVI6AXUQy/qcqPoFnz0hAZY9KyKd4xNA==} | |||
| package-json-from-dist@1.0.1: | |||
| resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} | |||
| package-json@2.4.0: | |||
| resolution: {integrity: sha512-PRg65iXMTt/uK8Rfh5zvzkUbfAPitF17YaCY+IbHsYgksiLvtzWWTUildHth3mVaZ7871OJ7gtP4LBRBlmAdXg==} | |||
| engines: {node: '>=0.10.0'} | |||
| @@ -2990,9 +3079,17 @@ packages: | |||
| resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} | |||
| engines: {node: '>=4'} | |||
| path-key@3.1.1: | |||
| resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} | |||
| engines: {node: '>=8'} | |||
| path-parse@1.0.7: | |||
| resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} | |||
| path-scurry@1.11.1: | |||
| resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} | |||
| engines: {node: '>=16 || 14 >=14.18'} | |||
| path-to-regexp@1.9.0: | |||
| resolution: {integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==} | |||
| @@ -3100,6 +3197,10 @@ packages: | |||
| process-nextick-args@2.0.1: | |||
| resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} | |||
| process@0.11.10: | |||
| resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} | |||
| engines: {node: '>= 0.6.0'} | |||
| progress@2.0.3: | |||
| resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} | |||
| engines: {node: '>=0.4.0'} | |||
| @@ -3179,6 +3280,10 @@ packages: | |||
| resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} | |||
| engines: {node: '>= 6'} | |||
| readable-stream@4.7.0: | |||
| resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} | |||
| engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} | |||
| readdir-glob@1.1.3: | |||
| resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} | |||
| @@ -3417,10 +3522,18 @@ packages: | |||
| resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} | |||
| engines: {node: '>=0.10.0'} | |||
| shebang-command@2.0.0: | |||
| resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} | |||
| engines: {node: '>=8'} | |||
| shebang-regex@1.0.0: | |||
| resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} | |||
| engines: {node: '>=0.10.0'} | |||
| shebang-regex@3.0.0: | |||
| resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} | |||
| engines: {node: '>=8'} | |||
| side-channel-list@1.0.0: | |||
| resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} | |||
| engines: {node: '>= 0.4'} | |||
| @@ -3440,6 +3553,10 @@ packages: | |||
| signal-exit@3.0.7: | |||
| resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} | |||
| signal-exit@4.1.0: | |||
| resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} | |||
| engines: {node: '>=14'} | |||
| simple-swizzle@0.2.4: | |||
| resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} | |||
| @@ -3581,6 +3698,10 @@ packages: | |||
| resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} | |||
| engines: {node: '>=8'} | |||
| string-width@5.1.2: | |||
| resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} | |||
| engines: {node: '>=12'} | |||
| string.prototype.trim@1.2.10: | |||
| resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} | |||
| engines: {node: '>= 0.4'} | |||
| @@ -3596,6 +3717,9 @@ packages: | |||
| string_decoder@1.1.1: | |||
| resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} | |||
| string_decoder@1.3.0: | |||
| resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} | |||
| strip-ansi@0.1.1: | |||
| resolution: {integrity: sha512-behete+3uqxecWlDAm5lmskaSaISA+ThQ4oNNBDTBJt0x2ppR6IPqfZNuj6BLaLJ/Sji4TPZlcRyOis8wXQTLg==} | |||
| engines: {node: '>=0.8.0'} | |||
| @@ -3613,6 +3737,10 @@ packages: | |||
| resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} | |||
| engines: {node: '>=8'} | |||
| strip-ansi@7.2.0: | |||
| resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} | |||
| engines: {node: '>=12'} | |||
| strip-bom-buf@1.0.0: | |||
| resolution: {integrity: sha512-1sUIL1jck0T1mhOLP2c696BIznzT525Lkub+n4jjMHjhjhoAQA6Ye659DxdlZBr0aLDMQoTxKIpnlqxgtwjsuQ==} | |||
| engines: {node: '>=4'} | |||
| @@ -4070,6 +4198,11 @@ packages: | |||
| resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} | |||
| hasBin: true | |||
| which@2.0.2: | |||
| resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} | |||
| engines: {node: '>= 8'} | |||
| hasBin: true | |||
| widest-line@1.0.0: | |||
| resolution: {integrity: sha512-r5vvGtqsHUHn98V0jURY4Ts86xJf6+SzK9rpWdV8/73nURB3WFPIHd67aOvPw2fSuunIyHjAUqiJ2TY0x4E5gw==} | |||
| engines: {node: '>=0.10.0'} | |||
| @@ -4082,6 +4215,10 @@ packages: | |||
| resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} | |||
| engines: {node: '>=10'} | |||
| wrap-ansi@8.1.0: | |||
| resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} | |||
| engines: {node: '>=12'} | |||
| wrappy@1.0.2: | |||
| resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} | |||
| @@ -4160,6 +4297,10 @@ packages: | |||
| resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} | |||
| engines: {node: '>= 10'} | |||
| zip-stream@6.0.1: | |||
| resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} | |||
| engines: {node: '>= 14'} | |||
| zod@3.25.76: | |||
| resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} | |||
| @@ -4341,6 +4482,15 @@ snapshots: | |||
| '@img/sharp-win32-x64@0.33.5': | |||
| optional: true | |||
| '@isaacs/cliui@8.0.2': | |||
| dependencies: | |||
| string-width: 5.1.2 | |||
| string-width-cjs: string-width@4.2.3 | |||
| strip-ansi: 7.2.0 | |||
| strip-ansi-cjs: strip-ansi@6.0.1 | |||
| wrap-ansi: 8.1.0 | |||
| wrap-ansi-cjs: wrap-ansi@7.0.0 | |||
| '@jridgewell/gen-mapping@0.3.13': | |||
| dependencies: | |||
| '@jridgewell/sourcemap-codec': 1.5.5 | |||
| @@ -4355,6 +4505,9 @@ snapshots: | |||
| '@jridgewell/resolve-uri': 3.1.2 | |||
| '@jridgewell/sourcemap-codec': 1.5.5 | |||
| '@pkgjs/parseargs@0.11.0': | |||
| optional: true | |||
| '@puppeteer/browsers@2.13.0': | |||
| dependencies: | |||
| debug: 4.4.3 | |||
| @@ -4397,6 +4550,10 @@ snapshots: | |||
| a-sync-waterfall@1.0.1: {} | |||
| abort-controller@3.0.0: | |||
| dependencies: | |||
| event-target-shim: 5.0.1 | |||
| accepts@1.3.8: | |||
| dependencies: | |||
| mime-types: 2.1.35 | |||
| @@ -4459,6 +4616,8 @@ snapshots: | |||
| ansi-regex@5.0.1: {} | |||
| ansi-regex@6.2.2: {} | |||
| ansi-styles@1.0.0: {} | |||
| ansi-styles@2.2.1: {} | |||
| @@ -4471,6 +4630,8 @@ snapshots: | |||
| dependencies: | |||
| color-convert: 2.0.1 | |||
| ansi-styles@6.2.3: {} | |||
| any-promise@1.3.0: {} | |||
| anymatch@1.3.2: | |||
| @@ -4504,6 +4665,16 @@ snapshots: | |||
| normalize-path: 3.0.0 | |||
| readable-stream: 3.6.2 | |||
| archiver-utils@5.0.2: | |||
| dependencies: | |||
| glob: 10.4.5 | |||
| graceful-fs: 4.2.11 | |||
| is-stream: 2.0.1 | |||
| lazystream: 1.0.1 | |||
| lodash: 4.17.23 | |||
| normalize-path: 3.0.0 | |||
| readable-stream: 4.7.0 | |||
| archiver@5.3.2: | |||
| dependencies: | |||
| archiver-utils: 2.1.0 | |||
| @@ -4514,6 +4685,20 @@ snapshots: | |||
| tar-stream: 2.2.0 | |||
| zip-stream: 4.1.1 | |||
| archiver@7.0.1: | |||
| dependencies: | |||
| archiver-utils: 5.0.2 | |||
| async: 3.2.6 | |||
| buffer-crc32: 1.0.0 | |||
| readable-stream: 4.7.0 | |||
| readdir-glob: 1.1.3 | |||
| tar-stream: 3.1.8 | |||
| zip-stream: 6.0.1 | |||
| transitivePeerDependencies: | |||
| - bare-abort-controller | |||
| - bare-buffer | |||
| - react-native-b4a | |||
| argparse@1.0.10: | |||
| dependencies: | |||
| sprintf-js: 1.0.3 | |||
| @@ -5128,6 +5313,8 @@ snapshots: | |||
| buffer-crc32@0.2.13: {} | |||
| buffer-crc32@1.0.0: {} | |||
| buffer-equal-constant-time@1.0.1: {} | |||
| buffer-from@1.1.2: {} | |||
| @@ -5139,6 +5326,11 @@ snapshots: | |||
| base64-js: 1.5.1 | |||
| ieee754: 1.2.1 | |||
| buffer@6.0.3: | |||
| dependencies: | |||
| base64-js: 1.5.1 | |||
| ieee754: 1.2.1 | |||
| buffers@0.1.1: {} | |||
| bytes@3.1.2: {} | |||
| @@ -5377,6 +5569,14 @@ snapshots: | |||
| normalize-path: 3.0.0 | |||
| readable-stream: 3.6.2 | |||
| compress-commons@6.0.2: | |||
| dependencies: | |||
| crc-32: 1.2.2 | |||
| crc32-stream: 6.0.0 | |||
| is-stream: 2.0.1 | |||
| normalize-path: 3.0.0 | |||
| readable-stream: 4.7.0 | |||
| concat-map@0.0.1: {} | |||
| concat-stream@1.6.2: | |||
| @@ -5466,6 +5666,11 @@ snapshots: | |||
| crc-32: 1.2.2 | |||
| readable-stream: 3.6.2 | |||
| crc32-stream@6.0.0: | |||
| dependencies: | |||
| crc-32: 1.2.2 | |||
| readable-stream: 4.7.0 | |||
| create-error-class@3.0.2: | |||
| dependencies: | |||
| capture-stack-trace: 1.0.2 | |||
| @@ -5481,6 +5686,12 @@ snapshots: | |||
| shebang-command: 1.2.0 | |||
| which: 1.3.1 | |||
| cross-spawn@7.0.6: | |||
| dependencies: | |||
| path-key: 3.1.1 | |||
| shebang-command: 2.0.0 | |||
| which: 2.0.2 | |||
| css-select@5.2.2: | |||
| dependencies: | |||
| boolbase: 1.0.0 | |||
| @@ -5659,6 +5870,8 @@ snapshots: | |||
| dependencies: | |||
| readable-stream: 2.3.8 | |||
| eastasianwidth@0.2.0: {} | |||
| ecc-jsbn@0.1.2: | |||
| dependencies: | |||
| jsbn: 0.1.1 | |||
| @@ -5672,6 +5885,8 @@ snapshots: | |||
| emoji-regex@8.0.0: {} | |||
| emoji-regex@9.2.2: {} | |||
| empower-core@0.6.2: | |||
| dependencies: | |||
| call-signature: 0.0.2 | |||
| @@ -5954,12 +6169,16 @@ snapshots: | |||
| esutils@2.0.3: {} | |||
| event-target-shim@5.0.1: {} | |||
| events-universal@1.0.1: | |||
| dependencies: | |||
| bare-events: 2.8.2 | |||
| transitivePeerDependencies: | |||
| - bare-abort-controller | |||
| events@3.3.0: {} | |||
| exceljs@4.4.0: | |||
| dependencies: | |||
| archiver: 5.3.2 | |||
| @@ -6146,6 +6365,11 @@ snapshots: | |||
| dependencies: | |||
| for-in: 1.0.2 | |||
| foreground-child@3.3.1: | |||
| dependencies: | |||
| cross-spawn: 7.0.6 | |||
| signal-exit: 4.1.0 | |||
| forever-agent@0.6.1: {} | |||
| form-data@2.3.3: | |||
| @@ -6281,6 +6505,15 @@ snapshots: | |||
| dependencies: | |||
| is-glob: 2.0.1 | |||
| glob@10.4.5: | |||
| dependencies: | |||
| foreground-child: 3.3.1 | |||
| jackspeak: 3.4.3 | |||
| minimatch: 9.0.9 | |||
| minipass: 7.1.3 | |||
| package-json-from-dist: 1.0.1 | |||
| path-scurry: 1.11.1 | |||
| glob@7.2.3: | |||
| dependencies: | |||
| fs.realpath: 1.0.0 | |||
| @@ -6792,6 +7025,12 @@ snapshots: | |||
| isstream@0.1.2: {} | |||
| jackspeak@3.4.3: | |||
| dependencies: | |||
| '@isaacs/cliui': 8.0.2 | |||
| optionalDependencies: | |||
| '@pkgjs/parseargs': 0.11.0 | |||
| jest-diff@18.1.0: | |||
| dependencies: | |||
| chalk: 1.1.3 | |||
| @@ -7099,6 +7338,8 @@ snapshots: | |||
| lowercase-keys@1.0.1: {} | |||
| lru-cache@10.4.3: {} | |||
| lru-cache@4.1.5: | |||
| dependencies: | |||
| pseudomap: 1.0.2 | |||
| @@ -7213,8 +7454,14 @@ snapshots: | |||
| dependencies: | |||
| brace-expansion: 2.0.2 | |||
| minimatch@9.0.9: | |||
| dependencies: | |||
| brace-expansion: 2.0.2 | |||
| minimist@1.2.8: {} | |||
| minipass@7.1.3: {} | |||
| mitt@3.0.1: {} | |||
| mixin-deep@1.3.2: | |||
| @@ -7490,6 +7737,8 @@ snapshots: | |||
| dependencies: | |||
| md5-hex: 1.3.0 | |||
| package-json-from-dist@1.0.1: {} | |||
| package-json@2.4.0: | |||
| dependencies: | |||
| got: 5.7.1 | |||
| @@ -7559,8 +7808,15 @@ snapshots: | |||
| path-key@2.0.1: {} | |||
| path-key@3.1.1: {} | |||
| path-parse@1.0.7: {} | |||
| path-scurry@1.11.1: | |||
| dependencies: | |||
| lru-cache: 10.4.3 | |||
| minipass: 7.1.3 | |||
| path-to-regexp@1.9.0: | |||
| dependencies: | |||
| isarray: 0.0.1 | |||
| @@ -7646,6 +7902,8 @@ snapshots: | |||
| process-nextick-args@2.0.1: {} | |||
| process@0.11.10: {} | |||
| progress@2.0.3: {} | |||
| proxy-agent@6.5.0: | |||
| @@ -7785,6 +8043,14 @@ snapshots: | |||
| string_decoder: 1.1.1 | |||
| util-deprecate: 1.0.2 | |||
| readable-stream@4.7.0: | |||
| dependencies: | |||
| abort-controller: 3.0.0 | |||
| buffer: 6.0.3 | |||
| events: 3.3.0 | |||
| process: 0.11.10 | |||
| string_decoder: 1.3.0 | |||
| readdir-glob@1.1.3: | |||
| dependencies: | |||
| minimatch: 5.1.9 | |||
| @@ -8062,8 +8328,14 @@ snapshots: | |||
| dependencies: | |||
| shebang-regex: 1.0.0 | |||
| shebang-command@2.0.0: | |||
| dependencies: | |||
| shebang-regex: 3.0.0 | |||
| shebang-regex@1.0.0: {} | |||
| shebang-regex@3.0.0: {} | |||
| side-channel-list@1.0.0: | |||
| dependencies: | |||
| es-errors: 1.3.0 | |||
| @@ -8094,6 +8366,8 @@ snapshots: | |||
| signal-exit@3.0.7: {} | |||
| signal-exit@4.1.0: {} | |||
| simple-swizzle@0.2.4: | |||
| dependencies: | |||
| is-arrayish: 0.3.4 | |||
| @@ -8261,6 +8535,12 @@ snapshots: | |||
| is-fullwidth-code-point: 3.0.0 | |||
| strip-ansi: 6.0.1 | |||
| string-width@5.1.2: | |||
| dependencies: | |||
| eastasianwidth: 0.2.0 | |||
| emoji-regex: 9.2.2 | |||
| strip-ansi: 7.2.0 | |||
| string.prototype.trim@1.2.10: | |||
| dependencies: | |||
| call-bind: 1.0.8 | |||
| @@ -8288,6 +8568,10 @@ snapshots: | |||
| dependencies: | |||
| safe-buffer: 5.1.2 | |||
| string_decoder@1.3.0: | |||
| dependencies: | |||
| safe-buffer: 5.2.1 | |||
| strip-ansi@0.1.1: {} | |||
| strip-ansi@3.0.1: | |||
| @@ -8302,6 +8586,10 @@ snapshots: | |||
| dependencies: | |||
| ansi-regex: 5.0.1 | |||
| strip-ansi@7.2.0: | |||
| dependencies: | |||
| ansi-regex: 6.2.2 | |||
| strip-bom-buf@1.0.0: | |||
| dependencies: | |||
| is-utf8: 0.2.1 | |||
| @@ -8942,6 +9230,10 @@ snapshots: | |||
| dependencies: | |||
| isexe: 2.0.0 | |||
| which@2.0.2: | |||
| dependencies: | |||
| isexe: 2.0.0 | |||
| widest-line@1.0.0: | |||
| dependencies: | |||
| string-width: 1.0.2 | |||
| @@ -8954,6 +9246,12 @@ snapshots: | |||
| string-width: 4.2.3 | |||
| strip-ansi: 6.0.1 | |||
| wrap-ansi@8.1.0: | |||
| dependencies: | |||
| ansi-styles: 6.2.3 | |||
| string-width: 5.1.2 | |||
| strip-ansi: 7.2.0 | |||
| wrappy@1.0.2: {} | |||
| write-file-atomic@1.3.4: | |||
| @@ -9032,4 +9330,10 @@ snapshots: | |||
| compress-commons: 4.1.2 | |||
| readable-stream: 3.6.2 | |||
| zip-stream@6.0.1: | |||
| dependencies: | |||
| archiver-utils: 5.0.2 | |||
| compress-commons: 6.0.2 | |||
| readable-stream: 4.7.0 | |||
| zod@3.25.76: {} | |||
| @@ -0,0 +1,22 @@ | |||
| CREATE TABLE `export_task` ( | |||
| `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, | |||
| `task_no` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '任务编号', | |||
| `title` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '任务标题', | |||
| `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0=待处理 1=打包中 2=已完成 3=失败', | |||
| `file_types` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '勾选的附件类型JSON', | |||
| `filter_params` TEXT COMMENT '筛选条件JSON', | |||
| `total_files` INT NOT NULL DEFAULT 0 COMMENT '总文件数', | |||
| `processed_files` INT NOT NULL DEFAULT 0 COMMENT '已处理文件数', | |||
| `file_url` VARCHAR(500) NOT NULL DEFAULT '' COMMENT 'COS下载地址', | |||
| `file_size` BIGINT NOT NULL DEFAULT 0 COMMENT '文件大小(字节)', | |||
| `error_log` TEXT COMMENT '错误日志', | |||
| `started_at` DATETIME DEFAULT NULL COMMENT '开始打包时间', | |||
| `finished_at` DATETIME DEFAULT NULL COMMENT '完成时间', | |||
| `create_by` INT NOT NULL DEFAULT 0 COMMENT '创建人ID', | |||
| `create_by_name` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '创建人姓名', | |||
| `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, | |||
| `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, | |||
| `is_deleted` TINYINT NOT NULL DEFAULT 0, | |||
| KEY `idx_status` (`status`), | |||
| KEY `idx_create_by` (`create_by`) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='导出任务表'; | |||
| @@ -1 +1,20 @@ | |||
| // invoked in worker | |||
| think.beforeStartServer(async () => { | |||
| // 恢复中断的导出任务 | |||
| try { | |||
| const taskModel = think.model('export_task'); | |||
| const recovered = await taskModel.recoverStuckTasks(); | |||
| if (recovered) { | |||
| think.logger.info(`[Bootstrap] 恢复了 ${recovered} 个中断的导出任务`); | |||
| } | |||
| // 处理待执行的任务 | |||
| const pending = await taskModel.getPendingTask(); | |||
| if (!think.isEmpty(pending)) { | |||
| think.logger.info('[Bootstrap] 发现待处理的导出任务,启动处理'); | |||
| const exportService = think.service('export'); | |||
| setImmediate(() => exportService.startProcessing()); | |||
| } | |||
| } catch (e) { | |||
| think.logger.error('[Bootstrap] 导出任务恢复失败:', e); | |||
| } | |||
| }); | |||
| @@ -91,6 +91,17 @@ module.exports = [ | |||
| ['/admin/content/edit', 'admin/content/edit', 'post'], | |||
| ['/admin/content/delete', 'admin/content/delete', 'post'], | |||
| // 下载管理 | |||
| ['/admin/export_task', 'admin/export_task/index'], | |||
| ['/admin/export_task/list', 'admin/export_task/list'], | |||
| ['/admin/export_task/status', 'admin/export_task/status'], | |||
| ['/admin/export_task/batchStatus', 'admin/export_task/batchStatus'], | |||
| ['/admin/export_task/create', 'admin/export_task/create', 'post'], | |||
| ['/admin/export_task/delete', 'admin/export_task/delete', 'post'], | |||
| ['/admin/export_task/retry', 'admin/export_task/retry', 'post'], | |||
| // 患者附件导出(从患者页面触发) | |||
| ['/admin/patient/exportFiles', 'admin/export_task/create', 'post'], | |||
| // 文件上传 | |||
| ['/admin/upload', 'admin/upload/index', 'post'], | |||
| ['/admin/upload/config', 'admin/upload/config'], | |||
| @@ -0,0 +1,162 @@ | |||
| const Base = require('../base.js'); | |||
| module.exports = class extends Base { | |||
| // 权限校验:所有操作需要 patient:export 权限 | |||
| async __before() { | |||
| const ret = await super.__before(); | |||
| // 父类已处理(未登录等),直接返回 | |||
| if (ret === false) return false; | |||
| // 非页面请求时 loadUserPermissions 未执行,需手动加载 | |||
| if (this.isAjax() && this.adminUser && !this.userPermissions) { | |||
| await this.loadUserPermissions(); | |||
| } | |||
| const hasPermission = this.isSuperAdmin || (this.userPermissions || []).includes('patient:export'); | |||
| if (!hasPermission) { | |||
| if (this.isAjax()) { | |||
| return this.fail('暂无操作权限'); | |||
| } | |||
| return this.redirect('/admin/dashboard.html'); | |||
| } | |||
| } | |||
| // 下载管理页面 | |||
| async indexAction() { | |||
| this.assign('currentPage', 'export_task'); | |||
| this.assign('pageTitle', '下载管理'); | |||
| this.assign('breadcrumb', [{ name: '下载管理' }]); | |||
| this.assign('adminUser', this.adminUser || {}); | |||
| return this.display(); | |||
| } | |||
| // 获取任务列表 | |||
| async listAction() { | |||
| const { page = 1, pageSize = 10, status } = this.get(); | |||
| const model = this.model('export_task'); | |||
| const list = await model.getList({ page, pageSize, status }); | |||
| return this.success(list); | |||
| } | |||
| // 查询单个任务状态(轮询用) | |||
| async statusAction() { | |||
| const { id } = this.get(); | |||
| if (!id) return this.fail('参数错误'); | |||
| const task = await this.model('export_task') | |||
| .field('id, task_no, status, total_files, processed_files, file_url, file_size, error_log, finished_at') | |||
| .where({ id, is_deleted: 0 }) | |||
| .find(); | |||
| if (think.isEmpty(task)) return this.fail('任务不存在'); | |||
| return this.success(task); | |||
| } | |||
| // 批量查询任务状态(轮询用) | |||
| async batchStatusAction() { | |||
| const { ids } = this.get(); | |||
| if (!ids) return this.success([]); | |||
| const idArr = ids.split(',').map(Number).filter(Boolean); | |||
| if (!idArr.length) return this.success([]); | |||
| const tasks = await this.model('export_task') | |||
| .field('id, status, processed_files, total_files, file_url, file_size') | |||
| .where({ id: ['in', idArr], is_deleted: 0 }) | |||
| .select(); | |||
| return this.success(tasks); | |||
| } | |||
| // 创建导出任务 | |||
| async createAction() { | |||
| const { file_types, filter_params } = this.post(); | |||
| if (!file_types || !file_types.length) { | |||
| return this.fail('请选择要导出的附件类型'); | |||
| } | |||
| // 防重复:同一用户5分钟内不允许创建相同条件的任务 | |||
| const fiveMinAgo = think.datetime(new Date(Date.now() - 5 * 60 * 1000)); | |||
| const duplicate = await this.model('export_task').where({ | |||
| create_by: this.adminUser?.id || 0, | |||
| file_types: JSON.stringify(file_types), | |||
| filter_params: JSON.stringify(filter_params || {}), | |||
| create_time: ['>', fiveMinAgo], | |||
| is_deleted: 0, | |||
| status: ['in', [0, 1]] | |||
| }).find(); | |||
| if (!think.isEmpty(duplicate)) { | |||
| return this.fail('您有相同条件的任务正在处理中,请勿重复提交'); | |||
| } | |||
| const model = this.model('export_task'); | |||
| const taskNo = model.generateTaskNo(); | |||
| // 构建标题 | |||
| const typeLabels = { id_photos: '实名认证照片', documents: '上传资料', signs: '签字材料' }; | |||
| const title = file_types.map(t => typeLabels[t] || t).join('、'); | |||
| const id = await model.add({ | |||
| task_no: taskNo, | |||
| title, | |||
| status: 0, | |||
| file_types: JSON.stringify(file_types), | |||
| filter_params: JSON.stringify(filter_params || {}), | |||
| create_by: this.adminUser?.id || 0, | |||
| create_by_name: this.adminUser?.nickname || this.adminUser?.username || '' | |||
| }); | |||
| await this.log('export', '下载管理', `创建导出任务「${title}」编号:${taskNo}`); | |||
| // 触发后台处理 | |||
| const exportService = this.service('export'); | |||
| // 异步执行,不等待 | |||
| setImmediate(() => exportService.startProcessing()); | |||
| return this.success({ id, task_no: taskNo }); | |||
| } | |||
| // 删除任务(软删除) | |||
| async deleteAction() { | |||
| const { id } = this.post(); | |||
| if (!id) return this.fail('参数错误'); | |||
| const task = await this.model('export_task') | |||
| .where({ id, is_deleted: 0 }) | |||
| .find(); | |||
| if (think.isEmpty(task)) return this.fail('任务不存在'); | |||
| // 打包中的任务不允许删除 | |||
| if (task.status === 1) { | |||
| return this.fail('打包中的任务不能删除'); | |||
| } | |||
| await this.model('export_task').where({ id }).update({ is_deleted: 1 }); | |||
| await this.log('delete', '下载管理', `删除导出任务(${task.task_no})`); | |||
| return this.success(); | |||
| } | |||
| // 重试失败的任务 | |||
| async retryAction() { | |||
| const { id } = this.post(); | |||
| if (!id) return this.fail('参数错误'); | |||
| const task = await this.model('export_task') | |||
| .where({ id, is_deleted: 0 }) | |||
| .find(); | |||
| if (think.isEmpty(task)) return this.fail('任务不存在'); | |||
| if (task.status !== 3) return this.fail('只有失败的任务可以重试'); | |||
| await this.model('export_task').where({ id }).update({ | |||
| status: 0, | |||
| error_log: '', | |||
| started_at: null, | |||
| finished_at: null, | |||
| processed_files: 0, | |||
| total_files: 0, | |||
| file_url: '', | |||
| file_size: 0 | |||
| }); | |||
| await this.log('edit', '下载管理', `重试导出任务(${task.task_no})`); | |||
| const exportService = this.service('export'); | |||
| setImmediate(() => exportService.startProcessing()); | |||
| return this.success(); | |||
| } | |||
| }; | |||
| @@ -0,0 +1,64 @@ | |||
| module.exports = class extends think.Model { | |||
| get tableName() { | |||
| return 'export_task'; | |||
| } | |||
| /** | |||
| * 生成任务编号: EX + 时间戳 + 随机数 | |||
| */ | |||
| generateTaskNo() { | |||
| const ts = String(Date.now()).slice(-10); | |||
| const rand = String(Math.floor(Math.random() * 1000)).padStart(3, '0'); | |||
| return 'EX' + ts + rand; | |||
| } | |||
| /** | |||
| * 获取任务列表(分页) | |||
| */ | |||
| async getList({ page = 1, pageSize = 10, status, createBy }) { | |||
| const where = { is_deleted: 0 }; | |||
| if (status !== undefined && status !== '') { | |||
| where.status = parseInt(status, 10); | |||
| } | |||
| if (createBy) { | |||
| where.create_by = createBy; | |||
| } | |||
| return this.where(where) | |||
| .order('id DESC') | |||
| .page(page, pageSize) | |||
| .countSelect(); | |||
| } | |||
| /** | |||
| * 获取待处理的任务(用于后台执行) | |||
| */ | |||
| async getPendingTask() { | |||
| return this.where({ status: 0, is_deleted: 0 }) | |||
| .order('id ASC') | |||
| .find(); | |||
| } | |||
| /** | |||
| * 恢复中断的任务(processing 超过30分钟的标记为失败) | |||
| */ | |||
| async recoverStuckTasks() { | |||
| const thirtyMinAgo = think.datetime(new Date(Date.now() - 30 * 60 * 1000)); | |||
| const stuck = await this.where({ | |||
| status: 1, | |||
| started_at: ['<', thirtyMinAgo], | |||
| is_deleted: 0 | |||
| }).select(); | |||
| if (stuck.length) { | |||
| await this.where({ | |||
| status: 1, | |||
| started_at: ['<', thirtyMinAgo], | |||
| is_deleted: 0 | |||
| }).update({ | |||
| status: 3, | |||
| error_log: '任务超时,服务重启后自动标记为失败' | |||
| }); | |||
| think.logger.warn(`[ExportTask] 恢复了 ${stuck.length} 个中断任务`); | |||
| } | |||
| return stuck.length; | |||
| } | |||
| }; | |||
| @@ -0,0 +1,259 @@ | |||
| const fs = require('fs'); | |||
| const path = require('path'); | |||
| const archiver = require('archiver'); | |||
| const axios = require('axios'); | |||
| const COS = require('cos-nodejs-sdk-v5'); | |||
| const dayjs = require('dayjs'); | |||
| const cosConfig = require('../config/cos.js'); | |||
| // 并发控制:同时最多执行的打包任务数 | |||
| const MAX_CONCURRENT = 2; | |||
| let runningCount = 0; | |||
| module.exports = class extends think.Service { | |||
| /** | |||
| * 启动任务处理循环 | |||
| */ | |||
| async startProcessing() { | |||
| if (runningCount >= MAX_CONCURRENT) return; | |||
| const taskModel = this.model('export_task'); | |||
| const task = await taskModel.getPendingTask(); | |||
| if (think.isEmpty(task)) return; | |||
| runningCount++; | |||
| try { | |||
| await this.processTask(task); | |||
| } catch (e) { | |||
| think.logger.error(`[ExportTask] 任务 ${task.task_no} 异常:`, e); | |||
| await taskModel.where({ id: task.id }).update({ | |||
| status: 3, | |||
| error_log: e.message || '未知错误', | |||
| finished_at: think.datetime(new Date()) | |||
| }); | |||
| } finally { | |||
| runningCount--; | |||
| // 继续处理下一个 | |||
| setTimeout(() => this.startProcessing(), 500); | |||
| } | |||
| } | |||
| /** | |||
| * 处理单个任务 | |||
| */ | |||
| async processTask(task) { | |||
| const taskModel = this.model('export_task'); | |||
| const patientModel = this.model('patient'); | |||
| // 标记为打包中 | |||
| await taskModel.where({ id: task.id }).update({ | |||
| status: 1, | |||
| started_at: think.datetime(new Date()) | |||
| }); | |||
| // 解析参数 | |||
| let fileTypes = []; | |||
| let filterParams = {}; | |||
| try { | |||
| fileTypes = JSON.parse(task.file_types || '[]'); | |||
| filterParams = JSON.parse(task.filter_params || '{}'); | |||
| } catch (e) { | |||
| throw new Error('任务参数解析失败'); | |||
| } | |||
| // 查询患者列表 | |||
| const patients = await patientModel.getAll(filterParams); | |||
| if (!patients.length) { | |||
| await taskModel.where({ id: task.id }).update({ | |||
| status: 2, | |||
| total_files: 0, | |||
| processed_files: 0, | |||
| finished_at: think.datetime(new Date()), | |||
| error_log: '没有符合条件的患者数据' | |||
| }); | |||
| return; | |||
| } | |||
| // 收集所有需要下载的文件 | |||
| const downloadList = this._buildDownloadList(patients, fileTypes); | |||
| const totalFiles = downloadList.reduce((sum, p) => sum + p.files.length, 0); | |||
| await taskModel.where({ id: task.id }).update({ total_files: totalFiles }); | |||
| if (totalFiles === 0) { | |||
| await taskModel.where({ id: task.id }).update({ | |||
| status: 2, | |||
| processed_files: 0, | |||
| finished_at: think.datetime(new Date()), | |||
| error_log: '所选类型下没有可导出的附件' | |||
| }); | |||
| return; | |||
| } | |||
| // 创建临时 ZIP 文件 | |||
| const tmpDir = path.join(think.ROOT_PATH, 'runtime/export'); | |||
| if (!fs.existsSync(tmpDir)) { | |||
| fs.mkdirSync(tmpDir, { recursive: true }); | |||
| } | |||
| const zipFileName = `${task.task_no}.zip`; | |||
| const zipFilePath = path.join(tmpDir, zipFileName); | |||
| // 打包 | |||
| let processedFiles = 0; | |||
| const errors = []; | |||
| await new Promise((resolve, reject) => { | |||
| const output = fs.createWriteStream(zipFilePath); | |||
| const archive = archiver('zip', { zlib: { level: 5 } }); | |||
| output.on('close', resolve); | |||
| archive.on('error', reject); | |||
| archive.pipe(output); | |||
| // 用 Promise 链串行处理每个患者 | |||
| const processAll = async () => { | |||
| for (const patient of downloadList) { | |||
| for (const file of patient.files) { | |||
| try { | |||
| const response = await axios.get(file.url, { | |||
| responseType: 'arraybuffer', | |||
| timeout: 30000 | |||
| }); | |||
| archive.append(Buffer.from(response.data), { | |||
| name: `${patient.folder}/${file.name}` | |||
| }); | |||
| processedFiles++; | |||
| // 每处理10个文件更新一次进度 | |||
| if (processedFiles % 10 === 0) { | |||
| await taskModel.where({ id: task.id }).update({ | |||
| processed_files: processedFiles | |||
| }); | |||
| } | |||
| } catch (e) { | |||
| errors.push(`${patient.folder}/${file.name}: ${e.message}`); | |||
| think.logger.warn(`[ExportTask] 下载失败: ${file.url} - ${e.message}`); | |||
| } | |||
| } | |||
| } | |||
| }; | |||
| processAll().then(() => { | |||
| archive.finalize(); | |||
| }).catch(reject); | |||
| }); | |||
| // 更新已处理数 | |||
| await taskModel.where({ id: task.id }).update({ processed_files: processedFiles }); | |||
| // 上传到 COS | |||
| const cosKey = `uploads/cytx/zip/${dayjs().format('YYYY/MM')}/${zipFileName}`; | |||
| const fileSize = fs.statSync(zipFilePath).size; | |||
| const cos = new COS({ | |||
| SecretId: cosConfig.secretId, | |||
| SecretKey: cosConfig.secretKey | |||
| }); | |||
| await new Promise((resolve, reject) => { | |||
| cos.putObject({ | |||
| Bucket: cosConfig.bucket, | |||
| Region: cosConfig.region, | |||
| Key: cosKey, | |||
| Body: fs.createReadStream(zipFilePath), | |||
| ContentType: 'application/zip' | |||
| }, (err, data) => { | |||
| if (err) reject(err); | |||
| else resolve(data); | |||
| }); | |||
| }); | |||
| // 生成下载 URL | |||
| const fileUrl = `${cosConfig.cdnUrl}/${cosKey}`; | |||
| // 更新任务状态 | |||
| await taskModel.where({ id: task.id }).update({ | |||
| status: 2, | |||
| file_url: fileUrl, | |||
| file_size: fileSize, | |||
| processed_files: processedFiles, | |||
| finished_at: think.datetime(new Date()), | |||
| error_log: errors.length ? errors.join('\n') : '' | |||
| }); | |||
| // 删除本地临时文件 | |||
| try { | |||
| if (fs.existsSync(zipFilePath)) fs.unlinkSync(zipFilePath); | |||
| } catch (e) { | |||
| think.logger.warn(`[ExportTask] 删除临时文件失败: ${e.message}`); | |||
| } | |||
| think.logger.info(`[ExportTask] 任务 ${task.task_no} 完成,共 ${processedFiles}/${totalFiles} 个文件,${errors.length} 个失败`); | |||
| } | |||
| /** | |||
| * 构建下载文件列表 | |||
| */ | |||
| _buildDownloadList(patients, fileTypes) { | |||
| const list = []; | |||
| for (const p of patients) { | |||
| const folder = `${p.name}_${p.patient_no}`; | |||
| const files = []; | |||
| // 实名认证照片 | |||
| if (fileTypes.includes('id_photos')) { | |||
| if (p.id_card_front) { | |||
| files.push({ name: `身份证人像面${this._getExt(p.id_card_front)}`, url: p.id_card_front }); | |||
| } | |||
| if (p.id_card_back) { | |||
| files.push({ name: `身份证国徽面${this._getExt(p.id_card_back)}`, url: p.id_card_back }); | |||
| } | |||
| if (p.photo) { | |||
| files.push({ name: `免冠照片${this._getExt(p.photo)}`, url: p.photo }); | |||
| } | |||
| } | |||
| // 上传资料(检查报告/诊断证明) | |||
| if (fileTypes.includes('documents')) { | |||
| let docs = []; | |||
| try { | |||
| docs = JSON.parse(p.documents || '[]'); | |||
| } catch (e) { /* ignore */ } | |||
| docs.forEach((url, idx) => { | |||
| if (url) { | |||
| files.push({ name: `检查报告_${idx + 1}${this._getExt(url)}`, url }); | |||
| } | |||
| }); | |||
| } | |||
| // 签字材料 | |||
| if (fileTypes.includes('signs')) { | |||
| if (p.sign_income) { | |||
| files.push({ name: `个人可支配收入声明${this._getExt(p.sign_income)}`, url: p.sign_income }); | |||
| } | |||
| if (p.sign_privacy) { | |||
| files.push({ name: `个人信息处理同意书${this._getExt(p.sign_privacy)}`, url: p.sign_privacy }); | |||
| } | |||
| if (p.sign_promise) { | |||
| files.push({ name: `声明与承诺${this._getExt(p.sign_promise)}`, url: p.sign_promise }); | |||
| } | |||
| if (p.sign_privacy_jhr) { | |||
| files.push({ name: `监护人个人信息处理同意书${this._getExt(p.sign_privacy_jhr)}`, url: p.sign_privacy_jhr }); | |||
| } | |||
| } | |||
| if (files.length) { | |||
| list.push({ folder, files }); | |||
| } | |||
| } | |||
| return list; | |||
| } | |||
| /** | |||
| * 从 URL 提取文件扩展名 | |||
| */ | |||
| _getExt(url) { | |||
| if (!url) return '.jpg'; | |||
| const pathname = url.split('?')[0]; | |||
| const ext = path.extname(pathname); | |||
| return ext || '.jpg'; | |||
| } | |||
| }; | |||
| @@ -42,6 +42,16 @@ | |||
| </div> | |||
| {% endif %} | |||
| {# 下载管理 #} | |||
| {% if isSuperAdmin or (userPermissions and 'patient:export' in userPermissions) %} | |||
| <div class="menu-group"> | |||
| <a class="menu-item {% if currentPage == 'export_task' %}active{% endif %}" href="/admin/export_task.html"> | |||
| <span class="menu-icon"><svg viewBox="0 0 1024 1024" width="18" height="18"><path d="M544 864V672h128L512 480 352 672h128v192H320V672h-64v224c0 17.7 14.3 32 32 32h448c17.7 0 32-14.3 32-32V672h-64v192H544z" fill="currentColor"/><path d="M832 96H192c-17.7 0-32 14.3-32 32v256h64V160h576v256h64V128c0-17.7-14.3-32-32-32z" fill="currentColor"/></svg></span> | |||
| <span>下载管理</span> | |||
| </a> | |||
| </div> | |||
| {% endif %} | |||
| {# 系统设置 #} | |||
| {% set hasSysPerm = isSuperAdmin or (userPermissions and ('setting:system:user' in userPermissions or 'setting:system:role' in userPermissions or 'setting:system:log' in userPermissions or 'setting:system:sms' in userPermissions)) %} | |||
| {% if hasSysPerm %} | |||
| @@ -0,0 +1,255 @@ | |||
| {% extends "./layout.html" %} | |||
| {% block title %}下载管理{% endblock %} | |||
| {% block css %} | |||
| <style> | |||
| .task-progress { display: flex; align-items: center; gap: 8px; } | |||
| .task-progress .num { font-size: 12px; color: #909399; white-space: nowrap; } | |||
| </style> | |||
| {% endblock %} | |||
| {% block content %} | |||
| <div id="exportApp" v-cloak> | |||
| <el-card shadow="never" class="mb-4"> | |||
| <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;"> | |||
| <div style="display:flex;align-items:center;gap:12px;"> | |||
| <el-select v-model="statusFilter" placeholder="全部状态" clearable style="width:140px;" @change="loadList(1)"> | |||
| <el-option label="待处理" :value="0"></el-option> | |||
| <el-option label="打包中" :value="1"></el-option> | |||
| <el-option label="已完成" :value="2"></el-option> | |||
| <el-option label="失败" :value="3"></el-option> | |||
| </el-select> | |||
| <el-button type="primary" @click="loadList(1)">查询</el-button> | |||
| </div> | |||
| </div> | |||
| <el-table :data="tableData" v-loading="loading" stripe border> | |||
| <el-table-column prop="task_no" label="任务编号" min-width="160"></el-table-column> | |||
| <el-table-column prop="title" label="附件类型" min-width="200"></el-table-column> | |||
| <el-table-column label="状态" width="120" align="center"> | |||
| <template #default="{ row }"> | |||
| <el-tag v-if="row.status === 0" type="info" size="small">待处理</el-tag> | |||
| <el-tag v-else-if="row.status === 1" type="warning" size="small">打包中</el-tag> | |||
| <el-tag v-else-if="row.status === 2" type="success" size="small">已完成</el-tag> | |||
| <el-tag v-else-if="row.status === 3" type="danger" size="small">失败</el-tag> | |||
| </template> | |||
| </el-table-column> | |||
| <el-table-column label="进度" min-width="180"> | |||
| <template #default="{ row }"> | |||
| <div class="task-progress" v-if="row.status === 1 && row.total_files > 0"> | |||
| <el-progress :percentage="Math.round(row.processed_files / row.total_files * 100)" :stroke-width="8" style="flex:1;"></el-progress> | |||
| <span class="num">${ row.processed_files }/${ row.total_files }</span> | |||
| </div> | |||
| <span v-else-if="row.status === 2" style="color:#67C23A;">${ row.processed_files }/${ row.total_files } 个文件</span> | |||
| <span v-else-if="row.status === 0" style="color:#909399;">等待中</span> | |||
| <span v-else-if="row.status === 3" style="color:#F56C6C;">失败</span> | |||
| </template> | |||
| </el-table-column> | |||
| <el-table-column label="文件大小" width="110" align="center"> | |||
| <template #default="{ row }"> | |||
| <span v-if="row.file_size">${ formatSize(row.file_size) }</span> | |||
| <span v-else style="color:#C0C4CC;">—</span> | |||
| </template> | |||
| </el-table-column> | |||
| <el-table-column prop="create_by_name" label="创建人" width="100" align="center"></el-table-column> | |||
| <el-table-column prop="create_time" label="创建时间" width="170"></el-table-column> | |||
| <el-table-column prop="finished_at" label="完成时间" width="170"> | |||
| <template #default="{ row }"> | |||
| <span v-if="row.finished_at">${ row.finished_at }</span> | |||
| <span v-else style="color:#C0C4CC;">—</span> | |||
| </template> | |||
| </el-table-column> | |||
| <el-table-column label="操作" width="200" align="center" fixed="right"> | |||
| <template #default="{ row }"> | |||
| <el-button v-if="row.status === 2 && row.file_url" type="primary" link @click="downloadFile(row)">下载</el-button> | |||
| <el-button v-if="row.status === 3" type="warning" link @click="retryTask(row)">重试</el-button> | |||
| <el-button v-if="row.error_log" type="info" link @click="showErrorLog(row)">日志</el-button> | |||
| <el-button v-if="row.status !== 1" type="danger" link @click="deleteTask(row)">删除</el-button> | |||
| </template> | |||
| </el-table-column> | |||
| </el-table> | |||
| <div class="flex justify-end mt-4"> | |||
| <el-pagination | |||
| v-model:current-page="pagination.page" | |||
| v-model:page-size="pagination.pageSize" | |||
| :page-sizes="[10, 20, 50]" | |||
| :total="pagination.total" | |||
| layout="total, sizes, prev, pager, next" | |||
| @current-change="loadList" | |||
| @size-change="onSizeChange" | |||
| /> | |||
| </div> | |||
| </el-card> | |||
| <!-- 错误日志弹窗 --> | |||
| <el-dialog v-model="logVisible" title="任务日志" width="600px" destroy-on-close> | |||
| <pre style="max-height:400px;overflow:auto;background:#f5f7fa;padding:16px;border-radius:6px;font-size:13px;line-height:1.6;white-space:pre-wrap;word-break:break-all;">${ logContent }</pre> | |||
| </el-dialog> | |||
| </div> | |||
| {% endblock %} | |||
| {% block js %} | |||
| <script> | |||
| var { createApp, ref, reactive, onMounted, onUnmounted } = Vue; | |||
| var app = createApp({ | |||
| delimiters: ['${', '}'], | |||
| setup() { | |||
| var loading = ref(false); | |||
| var tableData = ref([]); | |||
| var pagination = reactive({ page: 1, pageSize: 10, total: 0 }); | |||
| var statusFilter = ref(''); | |||
| var logVisible = ref(false); | |||
| var logContent = ref(''); | |||
| var pollTimer = null; | |||
| async function loadList(page) { | |||
| if (typeof page === 'number') pagination.page = page; | |||
| loading.value = true; | |||
| try { | |||
| var params = new URLSearchParams({ | |||
| page: pagination.page, | |||
| pageSize: pagination.pageSize | |||
| }); | |||
| if (statusFilter.value !== '' && statusFilter.value !== null) { | |||
| params.set('status', statusFilter.value); | |||
| } | |||
| var res = await fetch('/admin/export_task/list?' + params).then(function(r) { return r.json(); }); | |||
| if (res.code === 0) { | |||
| tableData.value = res.data.data || []; | |||
| pagination.total = res.data.count || 0; | |||
| // 检查是否有进行中的任务,启动轮询 | |||
| checkAndPoll(); | |||
| } | |||
| } finally { | |||
| loading.value = false; | |||
| } | |||
| } | |||
| function checkAndPoll() { | |||
| var processingIds = tableData.value | |||
| .filter(function(t) { return t.status === 0 || t.status === 1; }) | |||
| .map(function(t) { return t.id; }); | |||
| if (processingIds.length > 0) { | |||
| startPoll(processingIds); | |||
| } else { | |||
| stopPoll(); | |||
| } | |||
| } | |||
| function startPoll(ids) { | |||
| stopPoll(); | |||
| pollTimer = setInterval(async function() { | |||
| try { | |||
| var res = await fetch('/admin/export_task/batchStatus?ids=' + ids.join(',')).then(function(r) { return r.json(); }); | |||
| if (res.code === 0 && res.data) { | |||
| var changed = false; | |||
| res.data.forEach(function(updated) { | |||
| var row = tableData.value.find(function(t) { return t.id === updated.id; }); | |||
| if (row) { | |||
| if (row.status !== updated.status) changed = true; | |||
| row.status = updated.status; | |||
| row.processed_files = updated.processed_files; | |||
| row.total_files = updated.total_files; | |||
| row.file_url = updated.file_url; | |||
| row.file_size = updated.file_size; | |||
| } | |||
| }); | |||
| // 如果有状态变化,重新加载完整列表获取最新数据 | |||
| if (changed) { | |||
| loadList(); | |||
| } else { | |||
| checkAndPoll(); | |||
| } | |||
| } | |||
| } catch(e) { /* ignore */ } | |||
| }, 3000); | |||
| } | |||
| function stopPoll() { | |||
| if (pollTimer) { | |||
| clearInterval(pollTimer); | |||
| pollTimer = null; | |||
| } | |||
| } | |||
| function onSizeChange() { | |||
| pagination.page = 1; | |||
| loadList(); | |||
| } | |||
| function formatSize(bytes) { | |||
| if (!bytes) return '0 B'; | |||
| if (bytes < 1024) return bytes + ' B'; | |||
| if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; | |||
| if (bytes < 1024 * 1024 * 1024) return (bytes / 1024 / 1024).toFixed(1) + ' MB'; | |||
| return (bytes / 1024 / 1024 / 1024).toFixed(2) + ' GB'; | |||
| } | |||
| function downloadFile(row) { | |||
| if (row.file_url) { | |||
| window.open(row.file_url, '_blank'); | |||
| } | |||
| } | |||
| async function retryTask(row) { | |||
| try { | |||
| await ElementPlus.ElMessageBox.confirm('确定要重试该任务吗?', '确认重试', { | |||
| confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning' | |||
| }); | |||
| var res = await fetch('/admin/export_task/retry', { | |||
| method: 'POST', | |||
| headers: { 'Content-Type': 'application/json' }, | |||
| body: JSON.stringify({ id: row.id }) | |||
| }).then(function(r) { return r.json(); }); | |||
| if (res.code === 0) { | |||
| ElementPlus.ElMessage.success('已重新提交'); | |||
| loadList(); | |||
| } else { | |||
| ElementPlus.ElMessage.error(res.msg || '操作失败'); | |||
| } | |||
| } catch(e) {} | |||
| } | |||
| async function deleteTask(row) { | |||
| try { | |||
| await ElementPlus.ElMessageBox.confirm('确定要删除该任务吗?', '确认删除', { | |||
| confirmButtonText: '确认删除', cancelButtonText: '取消', type: 'warning' | |||
| }); | |||
| var res = await fetch('/admin/export_task/delete', { | |||
| method: 'POST', | |||
| headers: { 'Content-Type': 'application/json' }, | |||
| body: JSON.stringify({ id: row.id }) | |||
| }).then(function(r) { return r.json(); }); | |||
| if (res.code === 0) { | |||
| ElementPlus.ElMessage.success('删除成功'); | |||
| loadList(); | |||
| } else { | |||
| ElementPlus.ElMessage.error(res.msg || '删除失败'); | |||
| } | |||
| } catch(e) {} | |||
| } | |||
| function showErrorLog(row) { | |||
| logContent.value = row.error_log || '无日志'; | |||
| logVisible.value = true; | |||
| } | |||
| onMounted(function() { loadList(); }); | |||
| onUnmounted(function() { stopPoll(); }); | |||
| return { | |||
| loading, tableData, pagination, statusFilter, | |||
| logVisible, logContent, | |||
| loadList, onSizeChange, formatSize, downloadFile, retryTask, deleteTask, showErrorLog | |||
| }; | |||
| } | |||
| }); | |||
| app.use(ElementPlus, { locale: ElementPlusLocaleZhCn }); | |||
| app.mount('#exportApp'); | |||
| </script> | |||
| {% endblock %} | |||
| @@ -34,6 +34,7 @@ | |||
| <div style="display:flex;gap:8px;margin-top:12px;"> | |||
| <el-button v-if="perms.canAdd" type="success" :icon="Plus" @click="showAddDialog">新增患者</el-button> | |||
| <el-button v-if="perms.canExport" :icon="Download" @click="handleExport" :loading="exporting">导出</el-button> | |||
| <el-button v-if="perms.canExport" type="warning" :icon="Download" @click="showExportFilesDialog">异步下载附件</el-button> | |||
| <el-button v-if="perms.canDelete && selectedIds.length" type="danger" :icon="Delete" @click="handleBatchDelete">批量删除 (${ selectedIds.length })</el-button> | |||
| </div> | |||
| </el-card> | |||
| @@ -251,6 +252,40 @@ | |||
| <el-button type="primary" @click="submitAdd" :loading="addSaving">${ editingId ? '保存修改' : '确认新增' }</el-button> | |||
| </template> | |||
| </el-dialog> | |||
| <!-- 导出附件弹窗 --> | |||
| <el-dialog v-model="exportFilesVisible" title="异步下载附件" width="480px" destroy-on-close :close-on-click-modal="false"> | |||
| <p style="color:#606266;margin-bottom:16px;">将按当前筛选条件导出患者附件,打包为 ZIP 文件。请选择需要导出的附件类型:</p> | |||
| <el-checkbox-group v-model="exportFileTypes"> | |||
| <div style="display:flex;flex-direction:column;gap:12px;"> | |||
| <el-checkbox label="id_photos">实名认证照片(身份证人像面、国徽面、免冠照片)</el-checkbox> | |||
| <el-checkbox label="documents">上传资料(检查报告单、出院诊断证明书)</el-checkbox> | |||
| <el-checkbox label="signs">签字材料(收入声明、信息同意书、声明与承诺等)</el-checkbox> | |||
| </div> | |||
| </el-checkbox-group> | |||
| <template #footer> | |||
| <div style="display:flex;justify-content:space-between;align-items:center;width:100%;"> | |||
| <el-button type="primary" link @click="goExportTask">查看已有任务</el-button> | |||
| <div> | |||
| <el-button @click="exportFilesVisible = false">取消</el-button> | |||
| <el-button type="primary" @click="submitExportFiles" :loading="exportFilesLoading">创建任务</el-button> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| </el-dialog> | |||
| <!-- 导出任务创建成功弹窗 --> | |||
| <el-dialog v-model="exportSuccessVisible" title="任务创建成功" width="420px" :close-on-click-modal="false"> | |||
| <div style="text-align:center;padding:16px 0;"> | |||
| <div style="font-size:48px;margin-bottom:12px;">✅</div> | |||
| <p style="font-size:15px;color:#303133;">导出任务已创建,系统正在后台打包中。</p> | |||
| <p style="font-size:13px;color:#909399;margin-top:8px;">您可以前往「下载管理」页面查看进度和下载文件。</p> | |||
| </div> | |||
| <template #footer> | |||
| <el-button @click="exportSuccessVisible = false">稍后再看</el-button> | |||
| <el-button type="primary" @click="goExportTask">前往下载管理</el-button> | |||
| </template> | |||
| </el-dialog> | |||
| </div> | |||
| {% endblock %} | |||
| @@ -590,6 +625,60 @@ const app = createApp({ | |||
| window.open('/admin/patient/export?' + params.toString(), '_blank'); | |||
| } | |||
| // 导出附件 | |||
| const exportFilesVisible = ref(false); | |||
| const exportFilesLoading = ref(false); | |||
| const exportFileTypes = ref(['id_photos', 'documents', 'signs']); | |||
| const exportSuccessVisible = ref(false); | |||
| function showExportFilesDialog() { | |||
| exportFileTypes.value = ['id_photos', 'documents', 'signs']; | |||
| exportFilesVisible.value = true; | |||
| } | |||
| async function submitExportFiles() { | |||
| if (!exportFileTypes.value.length) { | |||
| return ElementPlus.ElMessage.warning('请至少选择一种附件类型'); | |||
| } | |||
| exportFilesLoading.value = true; | |||
| try { | |||
| var filterObj = { | |||
| keyword: keyword.value, | |||
| tag: tagFilter.value, | |||
| status: tabStatusMap[activeTab.value] || '' | |||
| }; | |||
| if (dateRange.value && dateRange.value.length === 2) { | |||
| filterObj.startDate = dateRange.value[0]; | |||
| filterObj.endDate = dateRange.value[1]; | |||
| } | |||
| if (regionFilter.value && regionFilter.value.length >= 1) filterObj.province_code = regionFilter.value[0]; | |||
| if (regionFilter.value && regionFilter.value.length >= 2) filterObj.city_code = regionFilter.value[1]; | |||
| if (regionFilter.value && regionFilter.value.length >= 3) filterObj.district_code = regionFilter.value[2]; | |||
| var res = await fetch('/admin/patient/exportFiles', { | |||
| method: 'POST', | |||
| headers: { 'Content-Type': 'application/json' }, | |||
| body: JSON.stringify({ | |||
| file_types: exportFileTypes.value, | |||
| filter_params: filterObj | |||
| }) | |||
| }).then(function(r) { return r.json(); }); | |||
| if (res.code === 0) { | |||
| exportFilesVisible.value = false; | |||
| exportSuccessVisible.value = true; | |||
| } else { | |||
| ElementPlus.ElMessage.error(res.msg || '创建任务失败'); | |||
| } | |||
| } finally { | |||
| exportFilesLoading.value = false; | |||
| } | |||
| } | |||
| function goExportTask() { | |||
| window.location.href = '/admin/export_task.html'; | |||
| } | |||
| onMounted(function() { | |||
| loadList(); | |||
| loadRegionTree(); | |||
| @@ -600,8 +689,10 @@ const app = createApp({ | |||
| keyword, dateRange, tagFilter, regionFilter, activeTab, loading, tableData, pagination, counts, | |||
| uploadHeaders, addVisible, addSaving, addForm, exporting, editingId, perms, selectedIds, | |||
| regionTree, tagOptions, isMinorComputed, Plus, Download, Delete, | |||
| exportFilesVisible, exportFilesLoading, exportFileTypes, exportSuccessVisible, | |||
| loadList, resetFilter, onTabChange, onSizeChange, onSelectionChange, viewDetail, showAddDialog, showEditDialog, handleExport, | |||
| onIdCardInput, onDocUpload, onSignUpload, submitAdd, handleDelete, handleBatchDelete | |||
| onIdCardInput, onDocUpload, onSignUpload, submitAdd, handleDelete, handleBatchDelete, | |||
| showExportFilesDialog, submitExportFiles, goExportTask | |||
| }; | |||
| } | |||
| }); | |||