From ee71a9839e4a6b7c617288761de521544595c345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=90=9B?= Date: Thu, 2 Apr 2026 16:58:48 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(cloud):=20=E6=B7=BB=E5=8A=A0=E4=BA=91?= =?UTF-8?q?=E7=9B=98=E9=9F=B3=E4=B9=90=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.d.ts | 3 + electron/server/index.ts | 8 +- electron/server/netease/index.ts | 20 +- package.json | 2 +- pnpm-lock.yaml | 236 ++++++++++++---- src/api/cloud.ts | 58 ++++ src/components/Modal/CloudUpload.vue | 400 +++++++++++++++++++++++++++ src/utils/modal.ts | 20 ++ src/views/Cloud.vue | 10 +- 9 files changed, 698 insertions(+), 59 deletions(-) create mode 100644 src/components/Modal/CloudUpload.vue diff --git a/components.d.ts b/components.d.ts index 78068fdaf..fc16a0390 100644 --- a/components.d.ts +++ b/components.d.ts @@ -22,6 +22,7 @@ declare module 'vue' { CacheSizeLimit: typeof import('./src/components/Setting/components/CacheSizeLimit.vue')['default'] ChangeRate: typeof import('./src/components/Modal/ChangeRate.vue')['default'] CloudMatch: typeof import('./src/components/Modal/CloudMatch.vue')['default'] + CloudUpload: typeof import('./src/components/Modal/CloudUpload.vue')['default'] CommentList: typeof import('./src/components/List/CommentList.vue')['default'] ContextMenuManager: typeof import('./src/components/Modal/Setting/ContextMenuManager.vue')['default'] CopyLyrics: typeof import('./src/components/Modal/CopyLyrics.vue')['default'] @@ -136,6 +137,8 @@ declare module 'vue' { NText: typeof import('naive-ui')['NText'] NThing: typeof import('naive-ui')['NThing'] NTree: typeof import('naive-ui')['NTree'] + NUpload: typeof import('naive-ui')['NUpload'] + NUploadDragger: typeof import('naive-ui')['NUploadDragger'] PersonalFM: typeof import('./src/components/Player/PlayerComponents/PersonalFM.vue')['default'] PlayerBackground: typeof import('./src/components/Player/PlayerMeta/PlayerBackground.vue')['default'] PlayerComment: typeof import('./src/components/Player/PlayerComponents/PlayerComment.vue')['default'] diff --git a/electron/server/index.ts b/electron/server/index.ts index 5096ba4af..7c4479207 100644 --- a/electron/server/index.ts +++ b/electron/server/index.ts @@ -12,15 +12,21 @@ import fastify from "fastify"; const initAppServer = async () => { try { + const uploadFileSizeLimit = 100 * 1024 * 1024; // 100MB const server = fastify({ routerOptions: { // 忽略尾随斜杠 ignoreTrailingSlash: true, }, + bodyLimit: uploadFileSizeLimit, }); // 注册插件 server.register(fastifyCookie); - server.register(fastifyMultipart); + server.register(fastifyMultipart, { + limits: { + fileSize: uploadFileSizeLimit + }, + }); // 生产环境启用静态文件 if (!isDev) { serverLog.info("📂 Serving static files from /renderer"); diff --git a/electron/server/netease/index.ts b/electron/server/netease/index.ts index d82e859e0..57c56522f 100644 --- a/electron/server/netease/index.ts +++ b/electron/server/netease/index.ts @@ -41,11 +41,27 @@ export const initNcmAPI = async (fastify: FastifyInstance) => { serverLog.log("🌐 Request NcmAPI:", requestPath); try { - const result = await neteaseApi({ + const params: Record = { ...(req.query as Record), ...(req.body as Record), cookie: req.cookies, - }); + }; + + // 处理 multipart/form-data 文件上传 + if (req.isMultipart()) { + const data = await req.file(); + if (data) { + const buffer = await data.toBuffer(); + params.songFile = { + name: data.filename, + data: buffer, + mimetype: data.mimetype, + size: buffer.byteLength, + }; + } + } + + const result = await neteaseApi(params); return reply.send(result.body); } catch (error: unknown) { serverLog.error("❌ NcmAPI Error:", error); diff --git a/package.json b/package.json index c7cfb878c..c5fd1f7c4 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@electron-toolkit/utils": "^4.0.0", "@imsyy/color-utils": "^1.0.2", "@material/material-color-utilities": "^0.4.0", - "@neteasecloudmusicapienhanced/api": "^4.30.1", + "@neteasecloudmusicapienhanced/api": "^4.31.0", "@pixi/app": "^7.4.3", "@pixi/core": "^7.4.3", "@pixi/display": "^7.4.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 381438eb4..b91742bb5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,8 +31,8 @@ importers: specifier: ^0.4.0 version: 0.4.0 '@neteasecloudmusicapienhanced/api': - specifier: ^4.30.1 - version: 4.30.1 + specifier: ^4.31.0 + version: 4.31.0 '@pixi/app': specifier: ^7.4.3 version: 7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)) @@ -465,6 +465,9 @@ packages: '@borewit/text-codec@0.2.1': resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==} + '@borewit/text-codec@0.2.2': + resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} + '@css-render/plugin-bem@0.15.14': resolution: {integrity: sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg==} peerDependencies: @@ -1660,13 +1663,13 @@ packages: resolution: {integrity: sha512-enkZYyuCdo+9jneCPE/0fjIta4wWnvVN9hBo2HuiMpRF0q3lzv1J6b/cl7i0mxZUKhBrV3aCKDBQnCOhwKbPmQ==} engines: {node: '>= 10'} - '@neteasecloudmusicapienhanced/api@4.30.1': - resolution: {integrity: sha512-//Wl5CmYJBJJW3jvFRCGOtJ4tqvuox5xwHo8MFep9f/dXO3hjg+Q37B3UWseT+vOCdXbcoLbQbe5Um0CprUCbQ==} + '@neteasecloudmusicapienhanced/api@4.31.0': + resolution: {integrity: sha512-mDj1FYt2dzRKIE0bUcmY7ka6JOIVZyeGY4XaanvwljkiIBltQ17xeE2LuJk1HwicwV6uwI1s8cO8GP1uYCciZg==} engines: {node: '>=12'} hasBin: true - '@neteasecloudmusicapienhanced/unblockmusic-utils@0.2.2': - resolution: {integrity: sha512-3YdfPWL/bLhsdUtvZ4nBEgeZjyy0QLD7Py9v7uwEt7V2WQK+q1qV2EWKn9TkCkeOujwCMNvGbhiWouk0PJnP6g==} + '@neteasecloudmusicapienhanced/unblockmusic-utils@0.2.4': + resolution: {integrity: sha512-4DBBrCMyBewUfAlVnmFDUe5UHwrB+zosyTNJTyw48mYCNtAXIm1BuRX8St5S4JsTvvyt0OEixGr2rECeckhqTg==} hasBin: true '@nodelib/fs.scandir@2.1.5': @@ -2601,6 +2604,9 @@ packages: axios@1.13.5: resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} + axios@1.14.0: + resolution: {integrity: sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -2615,8 +2621,8 @@ packages: resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true - basic-ftp@5.1.0: - resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==} + basic-ftp@5.2.0: + resolution: {integrity: sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==} engines: {node: '>=10.0.0'} before-after-hook@4.0.0: @@ -2667,6 +2673,9 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@2.0.3: + resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} + brace-expansion@5.0.2: resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} engines: {node: 20 || >=22} @@ -3154,8 +3163,8 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} - dotenv@17.3.1: - resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + dotenv@17.4.0: + resolution: {integrity: sha512-kCKF62fwtzwYm0IGBNjRUjtJgMfGapII+FslMHIjMR5KTnwEmBmWLDRSnc3XSNP8bNy34tekgQyDT0hr7pERRQ==} engines: {node: '>=12'} dunder-proto@1.0.1: @@ -3515,6 +3524,10 @@ packages: resolution: {integrity: sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==} engines: {node: '>=20'} + file-type@21.3.4: + resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==} + engines: {node: '>=20'} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -3601,6 +3614,10 @@ packages: resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} engines: {node: '>=14.14'} + fs-extra@11.3.4: + resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + engines: {node: '>=14.14'} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -3644,8 +3661,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} get-intrinsic@1.3.0: @@ -3734,6 +3751,9 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + gzip@0.1.0: + resolution: {integrity: sha512-o2kq0ogD3MMAwDUt3G8y6EtFPqXDtpNI/zNWkZoOwYkjp/j8/Q9QLpVySPxYaQ521hPT8GoV9g1/WTkd7DM5Aw==} + has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -4148,6 +4168,9 @@ packages: lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} @@ -4307,6 +4330,10 @@ packages: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -4334,6 +4361,10 @@ packages: resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} engines: {node: '>= 8'} + minipass-flush@1.0.7: + resolution: {integrity: sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==} + engines: {node: '>= 8'} + minipass-pipeline@1.2.4: resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} engines: {node: '>=8'} @@ -4404,6 +4435,10 @@ packages: resolution: {integrity: sha512-9ChYnmVmyHvFxR2g0MWFSHmJfbssRy07457G4gbb4LA9WYvyZea/8EMbqvg5dcv4oXNCNL01m8HXtymLlhhkYg==} engines: {node: '>=18'} + music-metadata@11.12.3: + resolution: {integrity: sha512-n6hSTZkuD59qWgHh6IP5dtDlDZQXoxk/bcA85Jywg8Z1iFrlNgl2+GTFgjZyn52W5UgQpV42V4XqrQZZAMbZTQ==} + engines: {node: '>=18'} + mute-stream@3.0.0: resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} engines: {node: ^20.17.0 || >=22.9.0} @@ -4471,8 +4506,8 @@ packages: node-api-version@0.2.1: resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==} - node-forge@1.3.3: - resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==} + node-forge@1.4.0: + resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} node-gyp@11.5.0: @@ -4635,11 +4670,11 @@ packages: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} - path-to-regexp@0.1.12: - resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + path-to-regexp@0.1.13: + resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -4813,9 +4848,16 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode@1.4.1: resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} @@ -4832,6 +4874,10 @@ packages: resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} engines: {node: '>=0.6'} + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -5011,6 +5057,10 @@ packages: resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} engines: {node: '>=11.0.0'} + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} @@ -5230,8 +5280,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} strip-json-comments@2.0.1: @@ -5249,6 +5299,10 @@ packages: resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} engines: {node: '>=18'} + strtok3@10.3.5: + resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} + engines: {node: '>=18'} + strtok3@6.3.0: resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} engines: {node: '>=10'} @@ -6063,6 +6117,8 @@ snapshots: '@borewit/text-codec@0.2.1': {} + '@borewit/text-codec@0.2.2': {} + '@css-render/plugin-bem@0.15.14(css-render@0.15.14)': dependencies: css-render: 0.15.14 @@ -6250,7 +6306,7 @@ snapshots: dependencies: cross-dirname: 0.1.0 debug: 4.4.3 - fs-extra: 11.3.3 + fs-extra: 11.3.4 minimist: 1.2.8 postject: 1.0.0-alpha.6 transitivePeerDependencies: @@ -6783,7 +6839,7 @@ snapshots: dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 + 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 @@ -7086,17 +7142,17 @@ snapshots: '@napi-rs/wasm-tools-win32-ia32-msvc': 1.0.1 '@napi-rs/wasm-tools-win32-x64-msvc': 1.0.1 - '@neteasecloudmusicapienhanced/api@4.30.1': + '@neteasecloudmusicapienhanced/api@4.31.0': dependencies: - '@neteasecloudmusicapienhanced/unblockmusic-utils': 0.2.2 - axios: 1.13.5 + '@neteasecloudmusicapienhanced/unblockmusic-utils': 0.2.4 + axios: 1.14.0 crypto-js: 4.2.0 - dotenv: 17.3.1 + dotenv: 17.4.0 express: 5.2.1 express-fileupload: 1.5.2 - md5: 2.3.0 - music-metadata: 11.12.0 - node-forge: 1.3.3 + gzip: 0.1.0 + music-metadata: 11.12.3 + node-forge: 1.4.0 pac-proxy-agent: 7.2.0 qrcode: 1.5.4 safe-decode-uri-component: 1.2.1 @@ -7107,7 +7163,7 @@ snapshots: - debug - supports-color - '@neteasecloudmusicapienhanced/unblockmusic-utils@0.2.2': + '@neteasecloudmusicapienhanced/unblockmusic-utils@0.2.4': dependencies: '@unblockneteasemusic/server': 0.28.0 axios: 1.13.5 @@ -8134,6 +8190,14 @@ snapshots: transitivePeerDependencies: - debug + axios@1.14.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + balanced-match@1.0.2: {} balanced-match@4.0.2: @@ -8144,7 +8208,7 @@ snapshots: baseline-browser-mapping@2.9.19: {} - basic-ftp@5.1.0: {} + basic-ftp@5.2.0: {} before-after-hook@4.0.0: {} @@ -8202,7 +8266,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.14.2 + qs: 6.15.0 raw-body: 3.0.2 type-is: 2.0.1 transitivePeerDependencies: @@ -8222,6 +8286,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@2.0.3: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.2: dependencies: balanced-match: 4.0.2 @@ -8332,7 +8400,7 @@ snapshots: lru-cache: 7.18.3 minipass: 3.3.6 minipass-collect: 1.0.2 - minipass-flush: 1.0.5 + minipass-flush: 1.0.7 minipass-pipeline: 1.2.4 mkdirp: 1.0.4 p-map: 4.0.0 @@ -8477,7 +8545,7 @@ snapshots: cliui@9.0.1: dependencies: string-width: 7.2.0 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrap-ansi: 9.0.2 clone-response@1.0.3: @@ -8758,7 +8826,7 @@ snapshots: dotenv@16.6.1: {} - dotenv@17.3.1: {} + dotenv@17.4.0: {} dunder-proto@1.0.1: dependencies: @@ -8875,7 +8943,7 @@ snapshots: '@electron/asar': 3.4.1 debug: 4.4.3 fs-extra: 7.0.1 - lodash: 4.17.23 + lodash: 4.18.1 temp: 0.9.4 optionalDependencies: '@electron/windows-sign': 1.2.2 @@ -9145,7 +9213,7 @@ snapshots: methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.12 + path-to-regexp: 0.1.13 proxy-addr: 2.0.7 qs: 6.14.2 range-parser: 1.2.1 @@ -9182,7 +9250,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.14.2 + qs: 6.15.0 range-parser: 1.2.1 router: 2.2.0 send: 1.2.1 @@ -9298,6 +9366,15 @@ snapshots: transitivePeerDependencies: - supports-color + file-type@21.3.4: + dependencies: + '@tokenizer/inflate': 0.4.1 + strtok3: 10.3.5 + token-types: 6.1.2 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + file-uri-to-path@1.0.0: {} filelist@1.0.4: @@ -9397,6 +9474,13 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 + fs-extra@11.3.4: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + optional: true + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -9437,7 +9521,7 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.4.0: {} + get-east-asian-width@1.5.0: {} get-intrinsic@1.3.0: dependencies: @@ -9469,7 +9553,7 @@ snapshots: get-uri@6.0.5: dependencies: - basic-ftp: 5.1.0 + basic-ftp: 5.2.0 data-uri-to-buffer: 6.0.2 debug: 4.4.3 transitivePeerDependencies: @@ -9527,7 +9611,7 @@ snapshots: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 5.1.6 + minimatch: 5.1.9 once: 1.4.0 global-agent@3.0.0: @@ -9566,6 +9650,8 @@ snapshots: graceful-fs@4.2.11: {} + gzip@0.1.0: {} + has-flag@3.0.0: {} has-flag@4.0.0: {} @@ -9987,6 +10073,8 @@ snapshots: lodash@4.17.23: {} + lodash@4.18.1: {} + log-symbols@4.1.0: dependencies: chalk: 4.1.2 @@ -10024,7 +10112,7 @@ snapshots: minipass: 3.3.6 minipass-collect: 1.0.2 minipass-fetch: 2.1.2 - minipass-flush: 1.0.5 + minipass-flush: 1.0.7 minipass-pipeline: 1.2.4 negotiator: 0.6.4 promise-retry: 2.0.1 @@ -10151,6 +10239,10 @@ snapshots: dependencies: brace-expansion: 2.0.2 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.0.3 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 @@ -10185,6 +10277,10 @@ snapshots: dependencies: minipass: 3.3.6 + minipass-flush@1.0.7: + dependencies: + minipass: 3.3.6 + minipass-pipeline@1.2.4: dependencies: minipass: 3.3.6 @@ -10254,6 +10350,21 @@ snapshots: transitivePeerDependencies: - supports-color + music-metadata@11.12.3: + dependencies: + '@borewit/text-codec': 0.2.2 + '@tokenizer/token': 0.3.0 + content-type: 1.0.5 + debug: 4.4.3 + file-type: 21.3.4 + media-typer: 1.1.0 + strtok3: 10.3.5 + token-types: 6.1.2 + uint8array-extras: 1.5.0 + win-guid: 0.2.1 + transitivePeerDependencies: + - supports-color + mute-stream@3.0.0: {} naive-ui@2.43.2(vue@3.5.28(typescript@5.9.3)): @@ -10329,7 +10440,7 @@ snapshots: dependencies: semver: 7.7.4 - node-forge@1.3.3: {} + node-forge@1.4.0: {} node-gyp@11.5.0: dependencies: @@ -10504,9 +10615,9 @@ snapshots: lru-cache: 11.2.6 minipass: 7.1.2 - path-to-regexp@0.1.12: {} + path-to-regexp@0.1.13: {} - path-to-regexp@8.3.0: {} + path-to-regexp@8.4.2: {} pathe@2.0.3: {} @@ -10557,7 +10668,7 @@ snapshots: joycon: 3.1.1 on-exit-leak-free: 0.2.0 pino-abstract-transport: 0.5.0 - pump: 3.0.3 + pump: 3.0.4 readable-stream: 3.6.2 rfdc: 1.4.1 secure-json-parse: 2.7.0 @@ -10691,11 +10802,18 @@ snapshots: proxy-from-env@1.1.0: {} + proxy-from-env@2.1.0: {} + pump@3.0.3: dependencies: end-of-stream: 1.4.5 once: 1.4.0 + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode@1.4.1: {} punycode@2.3.1: {} @@ -10710,6 +10828,10 @@ snapshots: dependencies: side-channel: 1.1.0 + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + quansync@0.2.11: {} queue-microtask@1.2.3: {} @@ -10878,7 +11000,7 @@ snapshots: depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 - path-to-regexp: 8.3.0 + path-to-regexp: 8.4.2 transitivePeerDependencies: - supports-color @@ -10914,6 +11036,8 @@ snapshots: sax@1.4.4: {} + sax@1.6.0: {} + scule@1.3.0: {} secure-json-parse@2.7.0: {} @@ -11182,13 +11306,13 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 string-width@7.2.0: dependencies: emoji-regex: 10.6.0 - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 string_decoder@1.3.0: dependencies: @@ -11203,7 +11327,7 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.2: + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 @@ -11219,6 +11343,10 @@ snapshots: dependencies: '@tokenizer/token': 0.3.0 + strtok3@10.3.5: + dependencies: + '@tokenizer/token': 0.3.0 + strtok3@6.3.0: dependencies: '@tokenizer/token': 0.3.0 @@ -11787,13 +11915,13 @@ snapshots: dependencies: ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrappy@1.0.2: {} @@ -11807,7 +11935,7 @@ snapshots: xml2js@0.6.2: dependencies: - sax: 1.4.4 + sax: 1.6.0 xmlbuilder: 11.0.1 xml@1.0.1: {} diff --git a/src/api/cloud.ts b/src/api/cloud.ts index 638784353..3d224f0e8 100644 --- a/src/api/cloud.ts +++ b/src/api/cloud.ts @@ -85,3 +85,61 @@ export const importCloudSong = ( params: { id, song, fileType, fileSize, bitrate, md5, artist, album, timestamp: Date.now() }, }); }; + +/** + * 获取云盘上传凭证 + * @param {string} md5 - 文件MD5 + * @param {number} fileSize - 文件大小 + * @param {string} filename - 文件名 + */ +export const getCloudUploadToken = ( + md5: string, + fileSize: number, + filename: string, +) => { + return request({ + url: "/cloud/upload/token", + method: "POST", + params: { + md5, + fileSize, + filename, + timestamp: Date.now(), + }, + }); +}; + +/** + * 完成云盘上传 + * @param {string} songId - 歌曲ID + * @param {string} filename - 文件名 + * @param {string} resourceId - 资源ID + * @param {string} md5 - 文件MD5 + * @param {string} song - 歌曲名(可选) + * @param {string} artist - 艺术家(可选) + * @param {string} album - 专辑(可选) + */ +export const completeCloudUpload = ( + songId: string, + filename: string, + resourceId: string, + md5: string, + song?: string, + artist?: string, + album?: string, +) => { + return request({ + url: "/cloud/upload/complete", + method: "POST", + params: { + songId, + filename, + resourceId, + md5, + song, + artist, + album, + timestamp: Date.now(), + }, + }); +}; diff --git a/src/components/Modal/CloudUpload.vue b/src/components/Modal/CloudUpload.vue new file mode 100644 index 000000000..45b54feb1 --- /dev/null +++ b/src/components/Modal/CloudUpload.vue @@ -0,0 +1,400 @@ + + + + + diff --git a/src/utils/modal.ts b/src/utils/modal.ts index 56f843f03..5e0f95514 100644 --- a/src/utils/modal.ts +++ b/src/utils/modal.ts @@ -721,3 +721,23 @@ export const openExcludeComment = async () => { }, }); }; + +/** 打开云盘上传弹窗 */ +export const openCloudUpload = async (onSuccess?: () => void) => { + const { default: CloudUpload } = await import("@/components/Modal/CloudUpload.vue"); + const modal = window.$modal.create({ + preset: "card", + transformOrigin: "center", + autoFocus: false, + style: { width: "700px" }, + title: "云盘上传", + content: () => { + return h(CloudUpload, { + onClose: () => modal.destroy(), + onSuccess: () => { + if (onSuccess) onSuccess(); + }, + }); + }, + }); +}; diff --git a/src/views/Cloud.vue b/src/views/Cloud.vue index f40e22290..76bd3c9e9 100644 --- a/src/views/Cloud.vue +++ b/src/views/Cloud.vue @@ -97,7 +97,7 @@ import { useDataStore } from "@/stores"; import { userCloud } from "@/api/cloud"; import { formatSongsList } from "@/utils/format"; import { fuzzySearch, renderIcon } from "@/utils/helper"; -import { openBatchList } from "@/utils/modal"; +import { openBatchList, openCloudUpload } from "@/utils/modal"; import { usePlayerController } from "@/core/player/PlayerController"; const router = useRouter(); @@ -131,6 +131,14 @@ const isCloudPage = computed(() => router.currentRoute.value.name === " // 更多操作 const moreOptions = computed(() => [ + { + label: "上传音乐", + key: "upload", + props: { + onClick: () => openCloudUpload(getAllCloudMusic), + }, + icon: renderIcon("Cloud"), + }, { label: "批量操作", key: "batch", From 2b0d0c468338752575f5dbe121013dd1fb92c2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=90=9B?= Date: Sun, 5 Apr 2026 13:16:53 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(cloud):=20=E4=BF=AE=E5=A4=8D=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E7=AA=97=E5=8F=A3=E5=A4=96=E9=83=A8=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E7=AA=97=E5=8F=A3=E5=85=B3=E9=97=AD=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E7=AA=97=E5=8F=A3=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/CloudUpload.vue | 69 +++++++++++++++++++--------- src/utils/modal.ts | 18 ++++++++ 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/src/components/Modal/CloudUpload.vue b/src/components/Modal/CloudUpload.vue index 45b54feb1..c1b251d8d 100644 --- a/src/components/Modal/CloudUpload.vue +++ b/src/components/Modal/CloudUpload.vue @@ -59,7 +59,7 @@ - 关闭 + 关闭 @@ -73,6 +73,7 @@ import axios from "axios"; const emit = defineEmits<{ close: []; success: []; + updateHasActive: [val: boolean]; }>(); // 上传队列 @@ -88,6 +89,36 @@ interface UploadItem { const uploadQueue = ref([]); const isUploading = ref(false); +// 关闭按钮:有活跃任务时弹出确认 +const handleClose = () => { + const hasActive = uploadQueue.value.some( + (item) => item.status === "pending" || item.status === "uploading", + ); + if (hasActive) { + window.$dialog.warning({ + title: "确认关闭", + content: "上传队列中还有文件正在上传或等待上传,关闭将中断上传,是否确认关闭?", + positiveText: "确认关闭", + negativeText: "取消", + onPositiveClick: () => emit("close"), + }); + } else { + emit("close"); + } +}; + +// 同步活跃上传状态给父级(用于 X 按钮拦截) +watch( + uploadQueue, + (queue) => { + const hasActive = queue.some( + (item) => item.status === "pending" || item.status === "uploading", + ); + emit("updateHasActive", hasActive); + }, + { deep: true }, +); + // 文件上传前校验 const beforeUpload = (data: { file: { file: File | null }; fileList: any[] }) => { const file = data.file.file; @@ -102,13 +133,13 @@ const beforeUpload = (data: { file: { file: File | null }; fileList: any[] }) => return true; }; -// 检查文件是否已在队列中(排除已成功和失败的文件) +// 检查文件是否已在队列中 const isFileInQueue = (file: File): boolean => { - return uploadQueue.value.some(item => - item.file.name === file.name && - item.file.size === file.size && - item.file.lastModified === file.lastModified && - (item.status === 'pending' || item.status === 'uploading') + return uploadQueue.value.some( + (item) => + item.file.name === file.name && + item.file.size === file.size && + item.file.lastModified === file.lastModified, ); }; @@ -160,15 +191,21 @@ const handleUpload = async (options: UploadCustomRequestOptions) => { // 处理上传队列 const processUploadQueue = async () => { - for (const item of uploadQueue.value) { - if (item.status !== "pending") continue; - + let item: UploadItem | undefined; + while ((item = uploadQueue.value.find((i) => i.status === "pending")) !== undefined) { try { // 先尝试后端代理,失败则回退到客户端直传 await uploadFileWithFallback(item); item.status = "success"; item.statusText = "上传完成!"; item.percent = 100; + emit("success"); + // 3 秒后清理该成功条目 + const doneItem = item; + setTimeout(() => { + const idx = uploadQueue.value.indexOf(doneItem); + if (idx !== -1) uploadQueue.value.splice(idx, 1); + }, 3000); } catch (error: any) { console.error(`${item.file.name} 上传失败:`, error); item.status = "error"; @@ -178,18 +215,6 @@ const processUploadQueue = async () => { item.percent = 0; } } - - // 检查是否有成功的上传 - const hasSuccess = uploadQueue.value.some(item => item.status === "success"); - if (hasSuccess) { - // 通知刷新 - emit("success"); - - // 清理已成功上传的文件 - setTimeout(() => { - uploadQueue.value = uploadQueue.value.filter(item => item.status !== "success"); - }, 3000); - } }; // 先尝试后端代理,失败则回退到客户端直传 diff --git a/src/utils/modal.ts b/src/utils/modal.ts index 5e0f95514..358abf519 100644 --- a/src/utils/modal.ts +++ b/src/utils/modal.ts @@ -725,18 +725,36 @@ export const openExcludeComment = async () => { /** 打开云盘上传弹窗 */ export const openCloudUpload = async (onSuccess?: () => void) => { const { default: CloudUpload } = await import("@/components/Modal/CloudUpload.vue"); + // 追踪是否有活跃上传任务 + let hasActiveUploads = false; const modal = window.$modal.create({ preset: "card", transformOrigin: "center", autoFocus: false, + maskClosable: false, style: { width: "700px" }, title: "云盘上传", + onClose: () => { + if (hasActiveUploads) { + window.$dialog.warning({ + title: "确认关闭", + content: "上传队列中还有文件正在上传或等待上传,关闭将中断上传,是否确认关闭?", + positiveText: "确认关闭", + negativeText: "取消", + onPositiveClick: () => modal.destroy(), + }); + return false; + } + }, content: () => { return h(CloudUpload, { onClose: () => modal.destroy(), onSuccess: () => { if (onSuccess) onSuccess(); }, + onUpdateHasActive: (val: boolean) => { + hasActiveUploads = val; + }, }); }, }); From 68ec5a51942f04ecb82c20536e6d958955f520b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=90=9B?= Date: Sun, 5 Apr 2026 13:31:54 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(cloud):=20=E4=BF=AE=E5=A4=8D=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/modal.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/modal.ts b/src/utils/modal.ts index 358abf519..de2545dec 100644 --- a/src/utils/modal.ts +++ b/src/utils/modal.ts @@ -745,6 +745,7 @@ export const openCloudUpload = async (onSuccess?: () => void) => { }); return false; } + return true; }, content: () => { return h(CloudUpload, {