feat: 添加i18n,美化界面
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { theme } from '@kevisual/center-components/theme/index.tsx';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import { CustomThemeProvider } from '@kevisual/center-components/theme/index.tsx';
|
||||
import { Left } from './layout/Left';
|
||||
import { Main } from './main/index';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
@@ -120,11 +119,11 @@ export const App = ({ providers, noProvider, isSingleApp = true }: AppProps) =>
|
||||
return <>{children}</>;
|
||||
}
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CustomThemeProvider>
|
||||
<AntdConfigProvider>
|
||||
{children}
|
||||
<ToastContainer />
|
||||
</AntdConfigProvider>
|
||||
</ThemeProvider>
|
||||
</CustomThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,107 +6,116 @@ import { getIcon } from '../FileIcon';
|
||||
import { Download, Trash } from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
import { useResourceFileStore } from '@kevisual/resources/pages/store/resource-file';
|
||||
import { useConfirm } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
|
||||
export const FileTable = () => {
|
||||
const { list, prefix, download, onOpenPrefix, deleteFile } = useResourceStore();
|
||||
const { setOpenDrawer, setPrefix } = useResourceFileStore();
|
||||
const { confirm, contextHolder } = useConfirm();
|
||||
return (
|
||||
<TableContainer
|
||||
className='scrollbar'
|
||||
sx={{
|
||||
'&': {
|
||||
// scrollbarWidth: 'none',
|
||||
// scrollbarColor: '#888 #fff',
|
||||
},
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '4px !important',
|
||||
height: '4px !important',
|
||||
background: '#fff',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
background: '#888',
|
||||
borderRadius: '2px',
|
||||
},
|
||||
}}
|
||||
component={Paper}>
|
||||
<Table
|
||||
<>
|
||||
{contextHolder}
|
||||
<TableContainer
|
||||
className='scrollbar'
|
||||
sx={{
|
||||
minWidth: 650,
|
||||
'&': {
|
||||
// scrollbarWidth: 'none',
|
||||
// scrollbarColor: '#888 #fff',
|
||||
},
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '4px !important',
|
||||
height: '4px !important',
|
||||
background: '#fff',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
background: '#888',
|
||||
borderRadius: '2px',
|
||||
},
|
||||
}}
|
||||
aria-label='simple table'>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell sx={{ minWidth: 100 }}>Size</TableCell>
|
||||
<TableCell sx={{ minWidth: 180 }}>Last Modified</TableCell>
|
||||
<TableCell sx={{ minWidth: 110 }}>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{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();
|
||||
}}>
|
||||
<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'
|
||||
component={Paper}>
|
||||
<Table
|
||||
sx={{
|
||||
minWidth: 650,
|
||||
}}
|
||||
aria-label='simple table'>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell sx={{ minWidth: 100 }}>Size</TableCell>
|
||||
<TableCell sx={{ minWidth: 180 }}>Last Modified</TableCell>
|
||||
<TableCell sx={{ minWidth: 110 }}>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{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(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>
|
||||
<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();
|
||||
confirm('删除文件', '确定删除该文件吗?', {
|
||||
onConfirm: () => {
|
||||
deleteFile(row);
|
||||
},
|
||||
});
|
||||
}}
|
||||
sx={{
|
||||
color: 'white',
|
||||
minWidth: '32px',
|
||||
padding: '4px',
|
||||
}}>
|
||||
<Trash />
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,37 +1,16 @@
|
||||
import React, { Fragment, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
AppBar,
|
||||
Box,
|
||||
CssBaseline,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
Toolbar,
|
||||
Typography,
|
||||
Button,
|
||||
useTheme,
|
||||
ListItemIcon,
|
||||
Tooltip,
|
||||
Container,
|
||||
} from '@mui/material';
|
||||
import { Box, Divider, List, ListItem, ListItemButton, ListItemText, useTheme, ListItemIcon, Tooltip, Container } from '@mui/material';
|
||||
import { ActiveMenu, useLayoutStore } from '../store/layout';
|
||||
import { activeMenuList } from '../modules/MenuList';
|
||||
import { amber } from '@mui/material/colors';
|
||||
import { lighten, rgbToHex } from '@mui/material/styles';
|
||||
import { SquareMenu } from 'lucide-react';
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
export type LeftProps = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
export const Left = ({ children }: LeftProps) => {
|
||||
const { setActiveMenu, init, showLabel, setShowLabel } = useLayoutStore();
|
||||
const theme = useTheme();
|
||||
// console.log(theme.palette.primary.main, rgbToHex(lighten(theme.palette.primary.main, 0.4)));
|
||||
// console.log(theme.palette.divider);
|
||||
useEffect(() => {
|
||||
init();
|
||||
}, []);
|
||||
@@ -49,23 +28,24 @@ export const Left = ({ children }: LeftProps) => {
|
||||
}, [activeMenuList]);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', backgroundColor: amber[50] }}>
|
||||
<Box sx={{ height: '100%', display: 'flex', backgroundColor: amber[50] }}>
|
||||
<Box component='nav' sx={{ flexShrink: { sm: 0 } }}>
|
||||
<Box
|
||||
sx={{
|
||||
flexShrink: 0,
|
||||
'& .MuiDrawer-paper': { boxSizing: 'border-box' },
|
||||
height: '100%',
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
height: '100vh',
|
||||
borderRight: 1,
|
||||
borderColor: theme.palette.divider,
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
<List sx={{ flexGrow: 1 }} key={list.length}>
|
||||
<List sx={{ flexGrow: 1, height: '100%' }} key={list.length}>
|
||||
{list.map((item) => (
|
||||
<Fragment key={item.value}>
|
||||
<ListItem disablePadding>
|
||||
@@ -106,7 +86,7 @@ export const Left = ({ children }: LeftProps) => {
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1, height: '100vh', overflow: 'hidden' }}>
|
||||
<Box sx={{ flexGrow: 1, height: '100%', 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 className='h-full'>{activeMenu}</div>;
|
||||
return <div className='h-full overflow-hidden'>{activeMenu}</div>;
|
||||
};
|
||||
|
||||
@@ -9,10 +9,12 @@ type ConvertOpts = {
|
||||
version?: string;
|
||||
username?: string;
|
||||
directory?: string;
|
||||
isPublic?: boolean;
|
||||
filename?: string;
|
||||
};
|
||||
|
||||
export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
const { directory, appKey, version, username } = opts;
|
||||
const { directory, appKey, version, username, isPublic } = opts;
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
@@ -21,11 +23,16 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
}
|
||||
|
||||
const taskId = nanoid();
|
||||
const filename = file.name;
|
||||
const filename = opts.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('/api/s1/events?taskId=' + taskId);
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('taskId', taskId);
|
||||
if (isPublic) {
|
||||
searchParams.set('public', 'true');
|
||||
}
|
||||
const eventSource = new EventSource('/api/s1/events?' + searchParams.toString());
|
||||
// 监听服务器推送的进度更新
|
||||
eventSource.onmessage = function (event) {
|
||||
console.log('Progress update:', event.data);
|
||||
@@ -60,9 +67,10 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
const chunk = file.slice(start, end);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', chunk, file.name);
|
||||
formData.append('file', chunk, filename);
|
||||
formData.append('chunkIndex', currentChunk.toString());
|
||||
formData.append('totalChunks', totalChunks.toString());
|
||||
const isLast = currentChunk === totalChunks - 1;
|
||||
if (directory) {
|
||||
formData.append('directory', directory);
|
||||
}
|
||||
@@ -83,10 +91,12 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
},
|
||||
}).then((response) => response.json());
|
||||
fetch('/api/s1/events/close?taskId=' + taskId);
|
||||
eventSource.close();
|
||||
NProgress.done();
|
||||
toast.dismiss(load);
|
||||
resolve(res);
|
||||
if (isLast) {
|
||||
NProgress.done();
|
||||
eventSource.close();
|
||||
toast.dismiss(load);
|
||||
resolve(res);
|
||||
}
|
||||
// console.log(`Chunk ${currentChunk + 1}/${totalChunks} uploaded`, res);
|
||||
} catch (error) {
|
||||
console.log('Error uploading chunk', error);
|
||||
|
||||
Reference in New Issue
Block a user