diff --git a/.gitmodules b/.gitmodules index 15a607d..ca798f6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "submodules/store"] path = submodules/store url = git@git.xiongxiao.me:kevisual/store.git +[submodule "submodules/query-upload"] + path = submodules/query-upload + url = git@git.xiongxiao.me:kevisual/kevisual-query-upload.git diff --git a/packages/components b/packages/components index 62c5c2a..0a8d7ab 160000 --- a/packages/components +++ b/packages/components @@ -1 +1 @@ -Subproject commit 62c5c2a82a2d346e27e064301a7c36c114f8512a +Subproject commit 0a8d7abf9a78bce528e31741d4643e017812616d diff --git a/packages/mark b/packages/mark index 508ec96..a99d9c2 160000 --- a/packages/mark +++ b/packages/mark @@ -1 +1 @@ -Subproject commit 508ec960292ad5b009e594082a2e8945ab783d28 +Subproject commit a99d9c2322df537d7ffaac09545e9b2e121511fa diff --git a/packages/resources/package.json b/packages/resources/package.json index 1cd7675..3dead02 100644 --- a/packages/resources/package.json +++ b/packages/resources/package.json @@ -20,6 +20,7 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@kevisual/components": "workspace:*", + "@kevisual/query-upload": "workspace:*", "@kevisual/router": "^0.0.9", "@kevisual/store": "^0.0.2", "@mui/material": "^6.4.8", diff --git a/packages/resources/src/index.ts b/packages/resources/src/index.ts index c2310a6..fb079bd 100644 --- a/packages/resources/src/index.ts +++ b/packages/resources/src/index.ts @@ -5,3 +5,5 @@ export { iText } from './i-text/index.ts'; export { uploadFiles, uploadFileChunked, getDirectoryAndName, toFile, createDirectory } from './pages/upload/app'; export { DialogDirectory, DialogDeleteDirectory } from './pages/upload/DialogDirectory'; + +export { uploadChunkV2 } from './pages/upload/v2/upload-chunk'; diff --git a/packages/resources/src/pages/upload/index.tsx b/packages/resources/src/pages/upload/index.tsx index 502bf81..ac96a90 100644 --- a/packages/resources/src/pages/upload/index.tsx +++ b/packages/resources/src/pages/upload/index.tsx @@ -5,6 +5,9 @@ import { FileText, CloudUpload as UploadIcon } from 'lucide-react'; import { uploadFileChunked } from './utils/upload-chunk'; import { filterFiles } from './utils/filter-files'; import { IconButton } from '@kevisual/components/button/index.tsx'; +import { uploadChunkV2 } from './v2/upload-chunk'; +import { uploadFilesV2 } from './v2/upload'; + type UploadButtonProps = { /** * 前缀 @@ -51,12 +54,10 @@ export const UploadButton = (props: UploadButtonProps) => { console.log(acceptedFiles); acceptedFiles = filterFiles(acceptedFiles); if (acceptedFiles.length > 1) { - const res = await uploadFiles(acceptedFiles, { directory, appKey, version, username }); - console.log('uploadFiles res', res); + const res = await uploadFilesV2(acceptedFiles, { directory, appKey, version, username }); props.onUpload?.(res); } else if (acceptedFiles.length === 1) { - const res = await uploadFileChunked(acceptedFiles[0], { directory, appKey, version, username }); - console.log('uploadFiles res', res); + const res = await uploadChunkV2(acceptedFiles[0], { directory, appKey, version, username }); props.onUpload?.(res); } }; @@ -83,11 +84,9 @@ export const Upload = ({ uploadDirectory = false }: { uploadDirectory?: boolean const onDrop = async (acceptedFiles) => { acceptedFiles = filterFiles(acceptedFiles); if (acceptedFiles.length > 1) { - const res = await uploadFiles(acceptedFiles, {}); - console.log('uploadFiles res', res); + const res = await uploadFilesV2(acceptedFiles, {}); } else if (acceptedFiles.length === 1) { - const res = await uploadFileChunked(acceptedFiles[0], {}); - console.log('uploadFiles res', res); + const res = await uploadChunkV2(acceptedFiles[0], {}); } }; diff --git a/packages/resources/src/pages/upload/utils/create-directory.ts b/packages/resources/src/pages/upload/utils/create-directory.ts index 488e5f1..4290cc2 100644 --- a/packages/resources/src/pages/upload/utils/create-directory.ts +++ b/packages/resources/src/pages/upload/utils/create-directory.ts @@ -1,4 +1,4 @@ -import { toTextFile } from '../app'; +import { toTextFile } from '../tools/to-file'; import { ConvertOpts, uploadFileChunked } from './upload-chunk'; /** * 对创建的directory的路径进行解析, diff --git a/packages/resources/src/pages/upload/v2/upload-chunk.ts b/packages/resources/src/pages/upload/v2/upload-chunk.ts new file mode 100644 index 0000000..196a2e6 --- /dev/null +++ b/packages/resources/src/pages/upload/v2/upload-chunk.ts @@ -0,0 +1,55 @@ +import NProgress from 'nprogress'; +import 'nprogress/nprogress.css'; +import { Id, toast } from 'react-toastify'; +import { nanoid } from 'nanoid'; +import { toastLogin } from '@kevisual/resources/pages/message/ToastLogin'; +import { uploadFileChunked, UploadProgress } from '@kevisual/query-upload/query-upload'; + +export type ConvertOpts = { + appKey?: string; + version?: string; + username?: string; + directory?: string; + isPublic?: boolean; + filename?: string; +}; + +export const uploadChunkV2 = async (file: File, opts: ConvertOpts) => { + const filename = opts.filename || file.name; + const token = localStorage.getItem('token'); + if (!token) { + console.log('uploadChunk token', token); + toastLogin(); + return; + } + let loaded: Id; + const uploadProgress = new UploadProgress({ + onStart: function () { + NProgress.start(); + loaded = toast.loading(`${filename} 上传中...`); + }, + onDone: () => { + NProgress.done(); + toast.dismiss(loaded); + }, + onProgress: (progress, data) => { + NProgress.set(progress); + console.log('uploadChunk progress', progress, data); + toast.update(loaded, { + render: `${filename} 上传中... ${progress.toFixed(2)}%`, + isLoading: true, + autoClose: false, + }); + }, + }); + + const result = await uploadFileChunked(file, opts, { + uploadProgress, + token, + createEventSource: (url: string, searchParams: URLSearchParams) => { + return new EventSource(url + '?' + searchParams.toString()); + }, + FormDataFn: FormData, + }); + return result; +}; diff --git a/packages/resources/src/pages/upload/v2/upload.ts b/packages/resources/src/pages/upload/v2/upload.ts new file mode 100644 index 0000000..3954256 --- /dev/null +++ b/packages/resources/src/pages/upload/v2/upload.ts @@ -0,0 +1,70 @@ +import NProgress from 'nprogress'; +import 'nprogress/nprogress.css'; +import { Id, toast } from 'react-toastify'; +import { nanoid } from 'nanoid'; +import { toastLogin } from '@kevisual/resources/pages/message/ToastLogin'; +import { uploadFiles, UploadProgress } from '@kevisual/query-upload/query-upload'; + +export type ConvertOpts = { + appKey?: string; + version?: string; + username?: string; + directory?: string; + isPublic?: boolean; + + maxSize?: number; + maxCount?: number; +}; + +export const uploadFilesV2 = async (files: File[], opts: ConvertOpts) => { + const token = localStorage.getItem('token'); + if (!token) { + console.log('uploadChunk token', token); + toastLogin(); + return; + } + const length = files.length; + const maxSize = opts.maxSize || 20 * 1024 * 1024; // 10MB + const totalSize = files.reduce((acc, file) => acc + file.size, 0); + if (totalSize > maxSize) { + toast.error('有文件大小不能超过20MB'); + return; + } + const maxCount = opts.maxCount || 10; + if (length > maxCount) { + toast.error(`最多只能上传${maxCount}个文件`); + return; + } + toast.info(`上传中,共${length}个文件`); + + let loaded: Id; + const uploadProgress = new UploadProgress({ + onStart: function () { + NProgress.start(); + loaded = toast.loading(`上传中...`); + }, + onDone: () => { + NProgress.done(); + toast.dismiss(loaded); + }, + onProgress: (progress, data) => { + NProgress.set(progress); + console.log('uploadChunk progress', progress, data); + toast.update(loaded, { + render: `上传中... ${progress.toFixed(2)}%`, + isLoading: true, + autoClose: false, + }); + }, + }); + + const result = await uploadFiles(files, opts, { + uploadProgress, + token, + createEventSource: (url: string, searchParams: URLSearchParams) => { + return new EventSource(url + '?' + searchParams.toString()); + }, + FormDataFn: FormData, + }); + return result; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bcd8d5f..6358d79 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -481,6 +481,9 @@ importers: '@kevisual/components': specifier: workspace:* version: link:../components + '@kevisual/query-upload': + specifier: workspace:* + version: link:../../submodules/query-upload '@kevisual/router': specifier: ^0.0.9 version: 0.0.9 @@ -600,6 +603,18 @@ importers: specifier: ^8.4.0 version: 8.4.0(@microsoft/api-extractor@7.52.2(@types/node@22.13.13))(jiti@2.4.2)(postcss@8.5.3)(typescript@5.8.2)(yaml@2.5.1) + submodules/query-upload: + devDependencies: + '@types/node': + specifier: ^22.13.14 + version: 22.13.14 + eventsource: + specifier: ^3.0.6 + version: 3.0.6 + tsup: + specifier: ^8.4.0 + version: 8.4.0(@microsoft/api-extractor@7.52.2(@types/node@22.13.14))(jiti@2.4.2)(postcss@8.5.3)(typescript@5.8.2)(yaml@2.5.1) + submodules/store: dependencies: eventemitter3: @@ -3181,6 +3196,14 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + eventsource-parser@3.0.1: + resolution: {integrity: sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.6: + resolution: {integrity: sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==} + engines: {node: '>=18.0.0'} + exsolve@1.0.4: resolution: {integrity: sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==} @@ -6931,7 +6954,7 @@ snapshots: '@types/qrcode@1.5.5': dependencies: - '@types/node': 22.13.13 + '@types/node': 22.13.14 '@types/react-dom@19.0.4(@types/react@19.0.12)': dependencies: @@ -7812,6 +7835,12 @@ snapshots: eventemitter3@5.0.1: {} + eventsource-parser@3.0.1: {} + + eventsource@3.0.6: + dependencies: + eventsource-parser: 3.0.1 + exsolve@1.0.4: {} extend@3.0.2: {} diff --git a/src/App.tsx b/src/App.tsx index 069893d..0b797b1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -75,7 +75,7 @@ export const App = () => { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/modules/query.ts b/src/modules/query.ts index 92f3afb..a4dd823 100644 --- a/src/modules/query.ts +++ b/src/modules/query.ts @@ -1,6 +1,7 @@ import { QueryClient } from '@kevisual/query'; import { QueryLoginBrowser } from '@kevisual/query-login'; import { toastLogin } from '@kevisual/resources/pages/message/ToastLogin.tsx'; +import { toast } from 'react-toastify'; export const query = new QueryClient({ io: true, }); @@ -25,6 +26,15 @@ query.afterResponse = async (res, ctx) => { afterAlso401: () => { toastLogin(); }, + afterCheck: (res) => { + if (res.code === 200) { + // console.log('afterCheck'); + toast.success('刷新登陆信息'); + setTimeout(() => { + window.location.reload(); + }, 1000); + } + }, }); return newRes as any; diff --git a/submodules/query-login b/submodules/query-login index 05f0373..98c8a2a 160000 --- a/submodules/query-login +++ b/submodules/query-login @@ -1 +1 @@ -Subproject commit 05f037383436cd0e30a6a12e7a4e08bf5b884ea8 +Subproject commit 98c8a2ad86331566058ca7cc12df5ee2b406fa2f diff --git a/submodules/query-mark b/submodules/query-mark index 39f0d7c..757be9f 160000 --- a/submodules/query-mark +++ b/submodules/query-mark @@ -1 +1 @@ -Subproject commit 39f0d7c566584811b73c027fb3eb43075d74ee1f +Subproject commit 757be9fc2fbfa2ac0af92a3002cccc9edba995ba diff --git a/submodules/query-upload b/submodules/query-upload new file mode 160000 index 0000000..a7faaca --- /dev/null +++ b/submodules/query-upload @@ -0,0 +1 @@ +Subproject commit a7faaca4188cae055deb1af1d1b758b6d5439f8d diff --git a/submodules/store b/submodules/store index 415f008..69784e8 160000 --- a/submodules/store +++ b/submodules/store @@ -1 +1 @@ -Subproject commit 415f008209c9579cccc5692bdc8a5f9f97b78d8c +Subproject commit 69784e8ed4f4546c91b2653adc75034e5946123c diff --git a/vite.config.ts b/vite.config.ts index 092b342..0e32b46 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,8 +7,8 @@ const isDev = process.env.NODE_ENV === 'development'; const plugins: any[] = [basicSsl()]; plugins.push(tailwindcss()); const devBackend = 'https://kevisual.silkyai.cn'; -// const meBackend = 'https://kevisual.xiongxiao.me'; -const meBackend = 'https://kevisual.cn'; +const meBackend = 'https://kevisual.xiongxiao.me'; +// const meBackend = 'https://kevisual.cn'; // const backend = isDev ? devBackend : meBackend; const backendWss = devBackend.replace(/^https:/, 'wss:'); const backend = meBackend;