feat: 添加i18n,美化界面
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Apps</title>
|
||||
<link rel="stylesheet" href="./src/assets/index.css">
|
||||
<link rel="stylesheet" href="./src/global.css">
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
@import 'tailwindcss';
|
||||
@import '@kevisual/center-components/theme/wind-theme.css';
|
||||
|
||||
@layer components {
|
||||
.test-loading {
|
||||
@apply w-20 h-20 bg-gray-300 rounded-full animate-spin;
|
||||
}
|
||||
}
|
||||
:root {
|
||||
--scrollbar-color: #ffbf00;
|
||||
--primary-color: #ffc107;
|
||||
--secondary-color: #ffa000;
|
||||
}
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#ai-bot-root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: -100px;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-color) #fff;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: var(--scrollbar-color);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar-track {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.ant-select-outlined.ant-select-multiple .ant-select-selection-item {
|
||||
background: var(--secondary-color);
|
||||
color: white;
|
||||
svg {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
.ant-select-selection-item {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.ant-select {
|
||||
.ant-select-arrow {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.ant-picker-input {
|
||||
.ant-picker-suffix,
|
||||
.ant-picker-clear {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
4
packages/resources/src/global.css
Normal file
4
packages/resources/src/global.css
Normal file
@@ -0,0 +1,4 @@
|
||||
@import 'tailwindcss';
|
||||
@import '@kevisual/center-components/theme/wind-theme.css';
|
||||
|
||||
@import './style.css';
|
||||
@@ -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);
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
:root {
|
||||
--scrollbar-color: #ffbf00;
|
||||
--primary-color: #ffc107;
|
||||
--secondary-color: #ffa000;
|
||||
}
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#ai-bot-root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: -100px;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-color) #fff;
|
||||
@@ -15,5 +34,4 @@
|
||||
|
||||
.scrollbar::-webkit-scrollbar-track {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user