feat: add drawer and add upload feat
This commit is contained in:
parent
fd30741151
commit
cc76842582
@ -1,7 +1,42 @@
|
||||
import { createTheme, ThemeOptions } from '@mui/material/styles';
|
||||
import { createTheme, Shadows, ThemeOptions } from '@mui/material/styles';
|
||||
import { useTheme as useMuiTheme, Theme } from '@mui/material/styles';
|
||||
import { amber } from '@mui/material/colors';
|
||||
const generateShadows = (color: string): Shadows => {
|
||||
return [
|
||||
'none',
|
||||
`0px 2px 1px -1px ${color}`,
|
||||
`0px 1px 1px 0px ${color}`,
|
||||
`0px 1px 3px 0px ${color}`,
|
||||
`0px 2px 4px -1px ${color}`,
|
||||
|
||||
`0px 3px 5px -1px ${color}`,
|
||||
`0px 3px 5px -1px ${color}`,
|
||||
`0px 4px 5px -2px ${color}`,
|
||||
`0px 5px 5px -3px ${color}`,
|
||||
`0px 5px 6px -3px ${color}`,
|
||||
|
||||
`0px 6px 6px -3px ${color}`,
|
||||
`0px 6px 7px -4px ${color}`,
|
||||
`0px 7px 8px -4px ${color}`,
|
||||
`0px 7px 8px -4px ${color}`,
|
||||
`0px 8px 9px -5px ${color}`,
|
||||
|
||||
`0px 8px 9px -5px ${color}`,
|
||||
`0px 9px 10px -5px ${color}`,
|
||||
`0px 9px 11px -6px ${color}`,
|
||||
`0px 10px 12px -6px ${color}`,
|
||||
`0px 10px 13px -6px ${color}`,
|
||||
|
||||
`0px 11px 13px -7px ${color}`,
|
||||
`0px 11px 14px -7px ${color}`,
|
||||
`0px 12px 15px -7px ${color}`,
|
||||
`0px 12px 16px -8px ${color}`,
|
||||
`0px 13px 17px -8px ${color}`,
|
||||
];
|
||||
};
|
||||
export const themeOptions: ThemeOptions = {
|
||||
// @ts-ignore
|
||||
// cssVariables: true,
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#ffc107', // amber[300]
|
||||
@ -22,6 +57,7 @@ export const themeOptions: ThemeOptions = {
|
||||
// paper: '#f5f5f5', // 设置纸张背景颜色
|
||||
},
|
||||
},
|
||||
shadows: generateShadows('rgba(255, 193, 7, 0.2)'),
|
||||
typography: {
|
||||
// fontFamily: 'Roboto, sans-serif',
|
||||
},
|
||||
@ -30,6 +66,22 @@ export const themeOptions: ThemeOptions = {
|
||||
defaultProps: {
|
||||
disableRipple: true,
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:hover': {
|
||||
backgroundColor: amber[100],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiButtonGroup: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'& .MuiButton-root': {
|
||||
borderColor: amber[600],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTextField: {
|
||||
styleOverrides: {
|
||||
@ -51,6 +103,13 @@ export const themeOptions: ThemeOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiCard: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
// border: `1px solid ${amber[300]}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -5,6 +5,9 @@
|
||||
@apply w-20 h-20 bg-gray-300 rounded-full animate-spin;
|
||||
}
|
||||
}
|
||||
:root {
|
||||
--scrollbar-color: #ffbf00;
|
||||
}
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -18,3 +21,8 @@
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-color) #fff;
|
||||
}
|
||||
|
16
packages/resources/src/pages/file/draw/FileDrawer.tsx
Normal file
16
packages/resources/src/pages/file/draw/FileDrawer.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { useResourceStore } from '@/pages/store/resource';
|
||||
import { useResourceFileStore } from '@/pages/store/resource-file';
|
||||
import { Drawer } from '@mui/material';
|
||||
|
||||
export const FileDrawer = () => {
|
||||
const { prefix } = useResourceStore();
|
||||
const { resource, openDrawer, setOpenDrawer } = useResourceFileStore();
|
||||
return (
|
||||
<Drawer open={openDrawer} onClose={() => setOpenDrawer(false)} anchor='right' {...(!openDrawer && { inert: true })}>
|
||||
<div className='p-4 w-[600px]'>
|
||||
<h2 className='text-2xl font-bold'>{resource?.name ? resource.name.replace(prefix, '') : resource?.prefix?.replace(prefix, '')}</h2>
|
||||
<pre className='flex flex-col gap-2'>{JSON.stringify(resource, null, 2)}</pre>
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
@ -1,50 +1,93 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useResourceStore } from '../store/resource';
|
||||
import { useSettingsStore } from '../store/settings';
|
||||
import { Box, Button, Card, CardContent, Typography, ButtonGroup, useTheme } from '@mui/material';
|
||||
import { FileText, Image, File, Table, Grid } from 'lucide-react';
|
||||
import { getIcon } from './FileIcon';
|
||||
import { Box, Button, Typography, ButtonGroup } from '@mui/material';
|
||||
import { FileText, Table, Grid } from 'lucide-react';
|
||||
import { FileTable } from './list/FileTable';
|
||||
import { FileCard } from './list/FileCard';
|
||||
import { PrefixRedirect } from './modules/PrefixRedirect';
|
||||
import { UploadButton } from '../upload';
|
||||
import { FileDrawer } from './draw/FileDrawer';
|
||||
import { useResourceFileStore } from '../store/resource-file';
|
||||
export const FileApp = () => {
|
||||
const { list, getList, prefix, setListType, listType } = useResourceStore();
|
||||
const { settings } = useSettingsStore();
|
||||
|
||||
const { getList, prefix, setListType, listType } = useResourceStore();
|
||||
const { getStatFile, prefix: statPrefix, openDrawer } = useResourceFileStore();
|
||||
useEffect(() => {
|
||||
getList();
|
||||
}, []);
|
||||
const theme = useTheme();
|
||||
const directory = useMemo(() => {
|
||||
const _prefix = prefix.split('/');
|
||||
let dir = _prefix.slice(2).join('/');
|
||||
if (dir.endsWith('/')) {
|
||||
dir = dir.slice(0, -1);
|
||||
}
|
||||
return dir;
|
||||
}, [prefix]);
|
||||
useEffect(() => {
|
||||
if (statPrefix && openDrawer) {
|
||||
getStatFile();
|
||||
}
|
||||
}, [statPrefix, openDrawer]);
|
||||
const handleUpload = (res: any) => {
|
||||
getList();
|
||||
};
|
||||
return (
|
||||
<Box sx={{ padding: 2, backgroundColor: 'white', borderRadius: 2, marginTop: 4, boxShadow: '0 0 10px 0 rgba(0, 0, 0, 0.1)' }}>
|
||||
<div className='flex items-center gap-3 mb-8'>
|
||||
<FileText className='w-8 h-8 text-amber-600' />
|
||||
<Typography
|
||||
variant='h1'
|
||||
className='text-amber-900'
|
||||
<div className='h-full py-4 px-2 w-full'>
|
||||
<Box
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
display: 'flex',
|
||||
marginBottom: 4,
|
||||
height: '100%',
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
<div className='flex items-center gap-3 mb-8'>
|
||||
<FileText className='w-8 h-8 text-amber-600' />
|
||||
<Typography
|
||||
variant='h1'
|
||||
className='text-amber-900'
|
||||
sx={{
|
||||
fontSize: { xs: '1.3rem', sm: '2rem' },
|
||||
fontWeight: 'bold',
|
||||
}}>
|
||||
Resources
|
||||
</Typography>
|
||||
</div>
|
||||
<Box className='flex items-center gap-2 mb-4'>
|
||||
<PrefixRedirect />
|
||||
<UploadButton prefix={directory} onUpload={handleUpload} />
|
||||
<ButtonGroup className='ml-auto' variant='contained' color='primary' sx={{ color: 'white' }}>
|
||||
<Button
|
||||
variant={listType === 'table' ? 'contained' : 'outlined'}
|
||||
sx={{
|
||||
'& > svg': {
|
||||
color: listType === 'table' ? 'white' : 'inherit',
|
||||
},
|
||||
}}
|
||||
onClick={() => setListType('table')}>
|
||||
<Table />
|
||||
</Button>
|
||||
<Button
|
||||
variant={listType === 'card' ? 'contained' : 'outlined'}
|
||||
sx={{
|
||||
'& > svg': {
|
||||
color: listType === 'card' ? 'white' : 'inherit',
|
||||
},
|
||||
}}
|
||||
onClick={() => setListType('card')}>
|
||||
<Grid />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Box>
|
||||
<Box
|
||||
className='scrollbar'
|
||||
sx={{
|
||||
fontSize: { xs: '1.3rem', sm: '2rem' },
|
||||
fontWeight: 'bold',
|
||||
height: 'calc(100% - 80px)',
|
||||
overflow: 'auto',
|
||||
}}>
|
||||
Resources
|
||||
</Typography>
|
||||
</div>
|
||||
<Box className='flex items-center gap-2 mb-4'>
|
||||
<Typography variant='h5' sx={{ fontWeight: 'bold', color: theme.palette.primary.main }}>
|
||||
<span className='mr-2' style={{ color: theme.palette.secondary.main }}>
|
||||
Prefix:
|
||||
</span>
|
||||
{prefix}
|
||||
</Typography>
|
||||
<ButtonGroup className='ml-auto' variant='contained' color='primary' sx={{ color: 'white' }}>
|
||||
<Button variant={listType === 'table' ? 'contained' : 'outlined'} onClick={() => setListType('table')}>
|
||||
<Table />
|
||||
</Button>
|
||||
<Button variant={listType === 'card' ? 'contained' : 'outlined'} onClick={() => setListType('card')}>
|
||||
<Grid />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
{listType === 'card' ? <FileCard /> : <FileTable />}
|
||||
</Box>
|
||||
</Box>
|
||||
<div>{listType === 'card' ? <FileCard /> : <FileTable />}</div>
|
||||
</Box>
|
||||
<FileDrawer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,21 +1,40 @@
|
||||
import { useResourceStore } from '@/pages/store/resource';
|
||||
import { Card, CardContent, Typography } from '@mui/material';
|
||||
import { getIcon } from '../FileIcon';
|
||||
|
||||
import { amber } from '@mui/material/colors';
|
||||
import clsx from 'clsx';
|
||||
import { useResourceFileStore } from '@/pages/store/resource-file';
|
||||
export const FileCard = () => {
|
||||
const { list, prefix } = useResourceStore();
|
||||
const { list, prefix, onOpenPrefix } = useResourceStore();
|
||||
const { setPrefix, setOpenDrawer } = useResourceFileStore();
|
||||
return (
|
||||
<>
|
||||
{list.map((resource) => (
|
||||
<Card key={resource.etag} style={{ margin: '10px' }}>
|
||||
<Card
|
||||
key={resource.etag || resource.name || resource.prefix}
|
||||
sx={{
|
||||
boxShadow: `0px 2px 1px -1px rgba(255, 193, 7, 0.2), 0px 1px 1px 0px rgba(255, 193, 7, 0.14), 0px 1px 3px 0px rgba(255, 193, 7, 0.12)`,
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
style={{ margin: '10px' }}>
|
||||
<CardContent>
|
||||
<Typography
|
||||
variant='h5'
|
||||
component='div'
|
||||
// className='flex items-center gap-2'
|
||||
>
|
||||
{getIcon(resource.name)}
|
||||
{resource.name ? resource.name.replace(prefix, '') : resource.prefix?.replace(prefix, '')}
|
||||
className={clsx('flex items-center gap-2', {
|
||||
'cursor-pointer': true,
|
||||
})}
|
||||
onClick={(e) => {
|
||||
if (!resource.name) {
|
||||
onOpenPrefix(resource.prefix || '');
|
||||
} else {
|
||||
setPrefix(resource.name || '');
|
||||
setOpenDrawer(true);
|
||||
}
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<div className='shrink-0'>{getIcon(resource.name)}</div>
|
||||
<div className='flex-1 truncate'>{resource.name ? resource.name.replace(prefix, '') : resource.prefix?.replace(prefix, '')}</div>
|
||||
</Typography>
|
||||
{resource.lastModified && <Typography color='text.secondary'>Last Modified: {resource.lastModified}</Typography>}
|
||||
{resource.size > 0 && <Typography color='text.secondary'>Size: {resource.size} bytes</Typography>}
|
||||
|
@ -3,48 +3,87 @@ import { Button, Paper, Table, TableBody, TableCell, TableContainer, TableHead,
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
import dayjs from 'dayjs';
|
||||
import { getIcon } from '../FileIcon';
|
||||
import { Download, Trash } from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
import { useResourceFileStore } from '@/pages/store/resource-file';
|
||||
|
||||
export const FileTable = () => {
|
||||
const { list, prefix, download } = useResourceStore();
|
||||
const { list, prefix, download, onOpenPrefix, deleteFile } = useResourceStore();
|
||||
const { setOpenDrawer, setPrefix } = useResourceFileStore();
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell sx={{ minWidth: 100 }}>Size</TableCell>
|
||||
<TableCell sx={{ minWidth: 100 }}>Last Modified</TableCell>
|
||||
<TableCell sx={{ minWidth: 100 }}>Actions</TableCell>
|
||||
<TableCell sx={{ maxWidth: 100 }}>Size</TableCell>
|
||||
<TableCell sx={{ maxWidth: 100 }}>Last Modified</TableCell>
|
||||
<TableCell sx={{ maxWidth: 100 }}>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{list.map((row) => (
|
||||
<TableRow key={row.name}>
|
||||
<TableCell>
|
||||
<div className='flex items-center gap-2'>
|
||||
{getIcon(row.name)}
|
||||
{row.name ? row.name.replace(prefix, '') : row.prefix?.replace?.(prefix, '')}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{row.size ? prettyBytes(row.size) : ''}</TableCell>
|
||||
<TableCell>{row.lastModified ? dayjs(row.lastModified).format('YYYY-MM-DD HH:mm:ss') : ''}</TableCell>
|
||||
<TableCell>
|
||||
{!row.prefix ? (
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
onClick={() => download(row)}
|
||||
sx={{
|
||||
color: 'white',
|
||||
{list.map((row) => {
|
||||
const isFile = !!row.name;
|
||||
return (
|
||||
<TableRow key={row.etag || row.name || row.prefix}>
|
||||
<TableCell>
|
||||
<div
|
||||
className={clsx('flex items-center gap-2 max-w-[300px] line-clamp-2 text-ellipsis', {
|
||||
'cursor-pointer': true,
|
||||
})}
|
||||
onClick={(e) => {
|
||||
if (!row.name) {
|
||||
onOpenPrefix(row.prefix || '');
|
||||
} else {
|
||||
setPrefix(row.name || '');
|
||||
setOpenDrawer(true);
|
||||
}
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
Download
|
||||
</Button>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
<div className='shrink-0'>{getIcon(row.name)}</div>
|
||||
{row.name ? row.name.replace(prefix, '') : row.prefix?.replace?.(prefix, '')}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{row.size ? prettyBytes(row.size) : ''}</TableCell>
|
||||
<TableCell>{row.lastModified ? dayjs(row.lastModified).format('YYYY-MM-DD HH:mm:ss') : ''}</TableCell>
|
||||
<TableCell>
|
||||
{isFile && (
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
download(row);
|
||||
}}
|
||||
sx={{
|
||||
color: 'white',
|
||||
minWidth: '32px',
|
||||
padding: '4px',
|
||||
}}>
|
||||
<Download />
|
||||
</Button>
|
||||
)}
|
||||
{isFile && (
|
||||
<Button
|
||||
variant='contained'
|
||||
color='error'
|
||||
className='ml-2!'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteFile(row);
|
||||
}}
|
||||
sx={{
|
||||
color: 'white',
|
||||
minWidth: '32px',
|
||||
padding: '4px',
|
||||
}}>
|
||||
<Trash />
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
35
packages/resources/src/pages/file/modules/PrefixRedirect.tsx
Normal file
35
packages/resources/src/pages/file/modules/PrefixRedirect.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { useResourceStore } from '@/pages/store/resource';
|
||||
import { Breadcrumbs, Typography } from '@mui/material';
|
||||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const PrefixRedirect = () => {
|
||||
const { prefix, onOpenPrefix } = useResourceStore();
|
||||
const prefixCom = useMemo(() => {
|
||||
const _prefix = prefix.split('/').filter(Boolean);
|
||||
return _prefix.map((item, index) => {
|
||||
const path = _prefix.slice(0, index + 1).join('/');
|
||||
const onClick = () => {
|
||||
console.log('path', path);
|
||||
const openPath = path + '/';
|
||||
if (openPath !== prefix) {
|
||||
onOpenPrefix(openPath);
|
||||
}
|
||||
};
|
||||
return {
|
||||
name: item,
|
||||
path,
|
||||
onClick: index > 0 ? onClick : undefined,
|
||||
};
|
||||
});
|
||||
}, [prefix]);
|
||||
return (
|
||||
<Breadcrumbs>
|
||||
{prefixCom.map((item) => (
|
||||
<Typography variant='h5' key={item.name} className={clsx({ 'cursor-pointer': item.onClick })} onClick={item.onClick}>
|
||||
{item.name}
|
||||
</Typography>
|
||||
))}
|
||||
</Breadcrumbs>
|
||||
);
|
||||
};
|
@ -106,7 +106,9 @@ export const Left = ({ children }: LeftProps) => {
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Container sx={{ flexGrow: 1 }}>{children}</Container>
|
||||
<Box sx={{ flexGrow: 1, height: '100vh', overflow: 'hidden' }}>
|
||||
<Container sx={{ width: '100%', height: '100%' }}>{children}</Container>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -18,5 +18,5 @@ export const Main = () => {
|
||||
if (activeMenu === ActiveMenu.Statistic) {
|
||||
return <Statistic />;
|
||||
}
|
||||
return <div>{activeMenu}</div>;
|
||||
return <div className='h-full'>{activeMenu}</div>;
|
||||
};
|
||||
|
@ -80,11 +80,11 @@ export const Settings = () => {
|
||||
<FormText label='Key' value={config.key} onChange={handleChange} focused={true} />
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<FormText label='Version' value={config.version} onChange={handleChange} disabled={true} />
|
||||
<FormText label='Version' value={config.version} onChange={handleChange} disabled={false} />
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
{/* <Box mb={2}>
|
||||
<FormText label='Username' value={config.username} onChange={handleChange} disabled={true} />
|
||||
</Box>
|
||||
</Box> */}
|
||||
<Box mb={2}>
|
||||
<FormText label='Prefix' value={config.prefix} onChange={handleChange} disabled={true} />
|
||||
</Box>
|
||||
|
35
packages/resources/src/pages/store/resource-file.ts
Normal file
35
packages/resources/src/pages/store/resource-file.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { create } from 'zustand';
|
||||
import { Resource } from './resource';
|
||||
import { query } from '@/modules/query';
|
||||
|
||||
interface ResourceFileStore {
|
||||
resource: Resource | null;
|
||||
setResource: (resource: Resource) => void;
|
||||
openDrawer: boolean;
|
||||
setOpenDrawer: (openDrawer: boolean) => void;
|
||||
prefix: string;
|
||||
setPrefix: (prefix: string, replace?: string) => void;
|
||||
getStatFile: () => Promise<any>;
|
||||
}
|
||||
|
||||
export const useResourceFileStore = create<ResourceFileStore>((set, get) => ({
|
||||
resource: null,
|
||||
setResource: (resource) => set({ resource }),
|
||||
openDrawer: false,
|
||||
setOpenDrawer: (openDrawer) => set({ openDrawer }),
|
||||
prefix: '',
|
||||
setPrefix: (prefix, replace) => set({ prefix: replace ? prefix.replace(replace, '') : prefix }),
|
||||
getStatFile: async () => {
|
||||
const { prefix } = get();
|
||||
const res = await query.post({
|
||||
path: 'file',
|
||||
key: 'stat',
|
||||
data: {
|
||||
prefix,
|
||||
},
|
||||
});
|
||||
if (res.code === 200) {
|
||||
set({ resource: { ...res.data, name: prefix } });
|
||||
}
|
||||
},
|
||||
}));
|
@ -19,11 +19,17 @@ interface ResourceStore {
|
||||
setList: (list: Resource[]) => void;
|
||||
prefix: string;
|
||||
setPrefix: (prefix: string) => void;
|
||||
getList: () => Promise<void>;
|
||||
download: (resource: Resource) => void;
|
||||
listType: 'table' | 'card';
|
||||
setListType: (listType: 'table' | 'card') => void;
|
||||
init: () => void;
|
||||
/**
|
||||
* 打开前缀
|
||||
* @param prefix 前缀
|
||||
*/
|
||||
onOpenPrefix: (prefix: string) => void;
|
||||
getList: () => Promise<void>;
|
||||
deleteFile: (resource: Resource) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useResourceStore = create<ResourceStore>((set, get) => ({
|
||||
@ -76,4 +82,29 @@ export const useResourceStore = create<ResourceStore>((set, get) => ({
|
||||
set({ listType: listType as 'table' | 'card' });
|
||||
}
|
||||
},
|
||||
onOpenPrefix: (prefix: string) => {
|
||||
set({ prefix });
|
||||
get().getList();
|
||||
},
|
||||
deleteFile: async (resource: Resource) => {
|
||||
console.log('deleteFile', resource);
|
||||
const name = resource.name;
|
||||
if (!name) {
|
||||
toast.error('Resource is not a file');
|
||||
return;
|
||||
}
|
||||
const res = await query.post({
|
||||
path: 'file',
|
||||
key: 'delete',
|
||||
data: {
|
||||
prefix: name,
|
||||
},
|
||||
});
|
||||
if (res.code === 200) {
|
||||
get().getList();
|
||||
toast.success('Delete file success');
|
||||
} else {
|
||||
toast.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
@ -1,14 +1,39 @@
|
||||
import { Box, useTheme, Container, Typography } from '@mui/material';
|
||||
import { Box, useTheme, Container, Typography, Button } from '@mui/material';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { uploadFiles } from './utils/upload';
|
||||
import { FileText, CloudUpload as UploadIcon } from 'lucide-react';
|
||||
import { uploadFileChunked } from './utils/upload-chunk';
|
||||
|
||||
export const UploadButton = (props: { prefix?: string; onUpload?: (res: any) => void }) => {
|
||||
const onDrop = async (acceptedFiles) => {
|
||||
console.log(acceptedFiles);
|
||||
if (acceptedFiles.length > 1) {
|
||||
const res = await uploadFiles(acceptedFiles, { directory: props.prefix });
|
||||
console.log('uploadFiles res', res);
|
||||
props.onUpload?.(res);
|
||||
} else if (acceptedFiles.length === 1) {
|
||||
const res = await uploadFileChunked(acceptedFiles[0], { directory: props.prefix });
|
||||
console.log('uploadFiles res', res);
|
||||
props.onUpload?.(res);
|
||||
}
|
||||
};
|
||||
const { getRootProps, getInputProps } = useDropzone({ onDrop });
|
||||
return (
|
||||
<Box {...getRootProps()}>
|
||||
<Button
|
||||
color='primary'
|
||||
sx={{
|
||||
minWidth: 'unset',
|
||||
padding: '2px',
|
||||
}}>
|
||||
<UploadIcon />
|
||||
</Button>
|
||||
<input type='file' style={{ display: 'none' }} {...getInputProps()} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export const Upload = () => {
|
||||
const onDrop = async (acceptedFiles) => {
|
||||
console.log(acceptedFiles);
|
||||
// Handle the files here
|
||||
// const res = await uploadFiles(acceptedFiles, {});
|
||||
if (acceptedFiles.length > 1) {
|
||||
const res = await uploadFiles(acceptedFiles, {});
|
||||
console.log('uploadFiles res', res);
|
||||
|
@ -8,9 +8,11 @@ type ConvertOpts = {
|
||||
appKey?: string;
|
||||
version?: string;
|
||||
username?: string;
|
||||
directory?: string;
|
||||
};
|
||||
|
||||
export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
const { directory } = opts;
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
@ -22,7 +24,8 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
const filename = file.name;
|
||||
const load = toast.loading(`${filename} 上传中...`);
|
||||
NProgress.start();
|
||||
const eventSource = new EventSource('http://49.232.155.236:11015/api/s1/events?taskId=' + taskId);
|
||||
// const eventSource = new EventSource('http://49.232.155.236:11015/api/s1/events?taskId=' + taskId);
|
||||
const eventSource = new EventSource('/api/s1/events?taskId=' + taskId);
|
||||
// 监听服务器推送的进度更新
|
||||
eventSource.onmessage = function (event) {
|
||||
console.log('Progress update:', event.data);
|
||||
@ -60,7 +63,9 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
formData.append('file', chunk, file.name);
|
||||
formData.append('chunkIndex', currentChunk.toString());
|
||||
formData.append('totalChunks', totalChunks.toString());
|
||||
|
||||
if (directory) {
|
||||
formData.append('directory', directory);
|
||||
}
|
||||
try {
|
||||
const res = await fetch('/api/s1/resources/upload/chunk?taskId=' + taskId, {
|
||||
method: 'POST',
|
||||
@ -70,19 +75,17 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}).then((response) => response.json());
|
||||
|
||||
console.log(`Chunk ${currentChunk + 1}/${totalChunks} uploaded`, res);
|
||||
fetch('/api/s1/events/close?taskId=' + taskId);
|
||||
eventSource.close();
|
||||
NProgress.done();
|
||||
toast.dismiss(load);
|
||||
resolve(res);
|
||||
// console.log(`Chunk ${currentChunk + 1}/${totalChunks} uploaded`, res);
|
||||
} catch (error) {
|
||||
console.log('Error uploading chunk', error);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fetch('/api/s1/events/close?taskId=' + taskId);
|
||||
eventSource.close();
|
||||
NProgress.done();
|
||||
toast.dismiss(load);
|
||||
resolve({ message: 'All chunks uploaded successfully' });
|
||||
});
|
||||
};
|
||||
|
@ -8,13 +8,18 @@ type ConvertOpts = {
|
||||
appKey?: string;
|
||||
version?: string;
|
||||
username?: string;
|
||||
directory?: string;
|
||||
};
|
||||
export const uploadFiles = async (files: File[], opts: ConvertOpts) => {
|
||||
const { directory } = opts;
|
||||
return new Promise((resolve, reject) => {
|
||||
const formData = new FormData();
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
formData.append('file', files[i], files[i].name);
|
||||
}
|
||||
if (directory) {
|
||||
formData.append('directory', directory);
|
||||
}
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
toastLogin();
|
||||
@ -23,8 +28,8 @@ export const uploadFiles = async (files: File[], opts: ConvertOpts) => {
|
||||
const taskId = nanoid();
|
||||
// 49.232.155.236:11015
|
||||
// const eventSource = new EventSource('https://kevisual.silkyai.cn/api/s1/events?taskId=' + taskId);
|
||||
// const eventSource = new EventSource('/api/s1/events?taskId=' + taskId);
|
||||
const eventSource = new EventSource('http://49.232.155.236:11015/api/s1/events?taskId=' + taskId);
|
||||
const eventSource = new EventSource('/api/s1/events?taskId=' + taskId);
|
||||
// const eventSource = new EventSource('http://49.232.155.236:11015/api/s1/events?taskId=' + taskId);
|
||||
const load = toast.loading('上传中...');
|
||||
NProgress.start();
|
||||
eventSource.onopen = async function (event) {
|
||||
@ -63,7 +68,6 @@ export const uploadFiles = async (files: File[], opts: ConvertOpts) => {
|
||||
if (progress) {
|
||||
NProgress.set(progress);
|
||||
}
|
||||
|
||||
};
|
||||
eventSource.onerror = function (event) {
|
||||
console.log('eventSource.onerror', event);
|
||||
|
Loading…
x
Reference in New Issue
Block a user