前端: 完成完整登录和文件管理界面
This commit is contained in:
parent
4b395fe2c3
commit
4e448794f7
@ -6,19 +6,31 @@
|
|||||||
<title>CloudDisk</title>
|
<title>CloudDisk</title>
|
||||||
<link rel="stylesheet" href="https://unpkg.com/antd@5.12.0/dist/reset.css">
|
<link rel="stylesheet" href="https://unpkg.com/antd@5.12.0/dist/reset.css">
|
||||||
<style>
|
<style>
|
||||||
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
#root { min-height: 100vh; }
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
|
||||||
.login-container { display: flex; justify-content: center; align-items: center; height: 100vh; background: #f0f2f5; }
|
.login-wrap { display: flex; justify-content: center; align-items: center; height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
|
||||||
.login-box { width: 400px; padding: 40px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
.login-box { width: 400px; padding: 40px; background: white; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); }
|
||||||
.login-title { text-align: center; margin-bottom: 30px; font-size: 24px; }
|
.logo { text-align: center; margin-bottom: 30px; font-size: 28px; font-weight: bold; color: #1890ff; }
|
||||||
.main-layout { min-height: 100vh; }
|
.main-layout { min-height: 100vh; }
|
||||||
.header { background: #1890ff; padding: 0 20px; display: flex; align-items: center; justify-content: space-between; color: white; font-size: 20px; font-weight: bold; }
|
.header { background: linear-gradient(90deg, #1890ff, #40a9ff); padding: 0 24px; height: 56px; display: flex; align-items: center; justify-content: space-between; color: white; }
|
||||||
.toolbar { padding: 16px; background: #fff; border-bottom: 1px solid #f0f0f0; }
|
.header-title { font-size: 20px; font-weight: bold; }
|
||||||
.file-list { padding: 16px; background: #fff; }
|
.toolbar { padding: 16px 24px; background: #fff; border-bottom: 1px solid #f0f0f0; display: flex; gap: 12px; }
|
||||||
|
.content { padding: 24px; background: #fff; }
|
||||||
|
.file-item { display: flex; align-items: center; padding: 12px; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background 0.2s; }
|
||||||
|
.file-item:hover { background: #f5f5f5; }
|
||||||
|
.file-icon { font-size: 24px; margin-right: 12px; }
|
||||||
|
.file-info { flex: 1; }
|
||||||
|
.file-name { font-size: 14px; }
|
||||||
|
.file-meta { font-size: 12px; color: #999; }
|
||||||
|
.empty-wrap { text-align: center; padding: 60px 0; color: #999; }
|
||||||
|
.folder { color: #faad14; }
|
||||||
|
.pdf { color: #ff4d4f; }
|
||||||
|
.img { color: #1890ff; }
|
||||||
|
.doc { color: #1890ff; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root">Loading...</div>
|
<div id="root"></div>
|
||||||
|
|
||||||
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
||||||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
||||||
@ -26,76 +38,164 @@
|
|||||||
<script src="https://unpkg.com/@ant-design/icons@5.2.6/dist/index-umd.min.js"></script>
|
<script src="https://unpkg.com/@ant-design/icons@5.2.6/dist/index-umd.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const { Button, Layout, Menu, Input, Table, Avatar, Spin } = antd;
|
const { Button, Input, Modal, Progress, Message, Breadcrumb, Dropdown, Spin } = antd;
|
||||||
const { FolderOutlined, FileOutlined, UploadOutlined, DeleteOutlined, DownloadOutlined, ShareAltOutlined, UserOutlined, ClockCircleOutlined, HomeOutlined } = icons;
|
const { FolderOutlined, FileOutlined, FilePdfOutlined, FileImageOutlined, FileWordOutlined, FileExcelOutlined, UploadOutlined, FolderAddOutlined, DeleteOutlined, DownloadOutlined, ShareAltOutlined, UserOutlined, SearchOutlined, HomeOutlined, ClockCircleOutlined, CloudOutlined, SettingOutlined, LogoutOutlined } = icons;
|
||||||
const { Header, Sider, Content } = Layout;
|
|
||||||
|
// Mock Data
|
||||||
|
const mockFiles = [
|
||||||
|
{ id: 1, name: '项目文档', type: 'folder', size: 0, updated_at: '2026-03-10 10:30' },
|
||||||
|
{ id: 2, name: '产品需求.pdf', type: 'pdf', size: 2500000, updated_at: '2026-03-09 15:20' },
|
||||||
|
{ id: 3, name: '首页设计.png', type: 'img', size: 3200000, updated_at: '2026-03-08 09:15' },
|
||||||
|
{ id: 4, name: '会议记录.docx', type: 'doc', size: 156000, updated_at: '2026-03-07 14:30' },
|
||||||
|
{ id: 5, name: '数据报表.xlsx', type: 'xls', size: 450000, updated_at: '2026-03-06 11:00' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Get Icon by type
|
||||||
|
const getIcon = (type, name) => {
|
||||||
|
if (type === 'folder') return React.createElement(FolderOutlined, { className: 'file-icon folder' });
|
||||||
|
const ext = name.split('.').pop();
|
||||||
|
const iconMap = {
|
||||||
|
pdf: FilePdfOutlined,
|
||||||
|
img: FileImageOutlined,
|
||||||
|
doc: FileWordOutlined,
|
||||||
|
docx: FileWordOutlined,
|
||||||
|
xls: FileExcelOutlined,
|
||||||
|
xlsx: FileExcelOutlined,
|
||||||
|
};
|
||||||
|
const Icon = iconMap[ext] || FileOutlined;
|
||||||
|
return React.createElement(Icon, { className: 'file-icon ' + (ext === 'pdf' ? 'pdf' : ext === 'img' ? 'img' : 'doc') });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format size
|
||||||
|
const formatSize = (bytes) => {
|
||||||
|
if (!bytes) return '-';
|
||||||
|
return (bytes / 1024 / 1024).toFixed(2) + ' MB';
|
||||||
|
};
|
||||||
|
|
||||||
// App Component
|
// App Component
|
||||||
function App() {
|
function App() {
|
||||||
|
const [page, setPage] = React.useState('files');
|
||||||
const [isLoggedIn, setIsLoggedIn] = React.useState(false);
|
const [isLoggedIn, setIsLoggedIn] = React.useState(false);
|
||||||
const [loading, setLoading] = React.useState(false);
|
|
||||||
const [username, setUsername] = React.useState('');
|
const [username, setUsername] = React.useState('');
|
||||||
const [password, setPassword] = React.useState('');
|
const [password, setPassword] = React.useState('');
|
||||||
|
const [files, setFiles] = React.useState(mockFiles);
|
||||||
|
const [selectedFiles, setSelectedFiles] = React.useState([]);
|
||||||
|
const [uploadModal, setUploadModal] = React.useState(false);
|
||||||
|
const [searchKeyword, setSearchKeyword] = React.useState('');
|
||||||
|
|
||||||
const handleLogin = () => {
|
const handleLogin = () => { if (username && password) setIsLoggedIn(true); };
|
||||||
if (username && password) {
|
const handleLogout = () => { setIsLoggedIn(false); };
|
||||||
setIsLoggedIn(true);
|
|
||||||
}
|
const filteredFiles = searchKeyword
|
||||||
};
|
? files.filter(f => f.name.includes(searchKeyword))
|
||||||
|
: files;
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ key: 'files', icon: React.createElement(FolderOutlined), label: '我的文件' },
|
{ key: 'files', icon: React.createElement(FolderOutlined), label: '我的文件', onClick: () => setPage('files') },
|
||||||
{ key: 'recent', icon: React.createElement(ClockCircleOutlined), label: '最近访问' },
|
{ key: 'recent', icon: React.createElement(ClockCircleOutlined), label: '最近访问', onClick: () => setPage('recent') },
|
||||||
{ key: 'shared', icon: React.createElement(ShareAltOutlined), label: '共享文件' },
|
{ key: 'shared', icon: React.createElement(ShareAltOutlined), label: '共享文件', onClick: () => setPage('shared') },
|
||||||
{ key: 'trash', icon: React.createElement(DeleteOutlined), label: '回收站' },
|
{ key: 'trash', icon: React.createElement(DeleteOutlined), label: '回收站', onClick: () => setPage('trash') },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Login Page
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
return React.createElement('div', { className: 'login-container' },
|
return React.createElement('div', { className: 'login-wrap' },
|
||||||
React.createElement('div', { className: 'login-box' },
|
React.createElement('div', { className: 'login-box' },
|
||||||
React.createElement('h1', { className: 'login-title' }, 'CloudDisk'),
|
React.createElement('div', { className: 'logo' }, '☁️ CloudDisk'),
|
||||||
React.createElement(Input, {
|
React.createElement(Input, { placeholder: '用户名', value: username, onChange: e => setUsername(e.target.value), style: { marginBottom: 16 }, prefix: React.createElement(UserOutlined) }),
|
||||||
placeholder: '用户名',
|
React.createElement(Input.Password, { placeholder: '密码', value: password, onChange: e => setPassword(e.target.value), style: { marginBottom: 16 } }),
|
||||||
value: username,
|
React.createElement(Button, { type: 'primary', block: true, onClick: handleLogin, style: { height: 40, fontSize: 16 } }, '登 录'),
|
||||||
onChange: (e) => setUsername(e.target.value),
|
React.createElement('p', { style: { textAlign: 'center', marginTop: 16, color: '#999', fontSize: 12 } }, '还没有账号?立即注册')
|
||||||
style: { marginBottom: '16px' }
|
|
||||||
}),
|
|
||||||
React.createElement(Input.Password, {
|
|
||||||
placeholder: '密码',
|
|
||||||
value: password,
|
|
||||||
onChange: (e) => setPassword(e.target.value),
|
|
||||||
style: { marginBottom: '16px' }
|
|
||||||
}),
|
|
||||||
React.createElement(Button, {
|
|
||||||
type: 'primary',
|
|
||||||
block: true,
|
|
||||||
onClick: handleLogin
|
|
||||||
}, '登录')
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return React.createElement(Layout, { className: 'main-layout' },
|
// Main App
|
||||||
React.createElement(Header, { className: 'header' },
|
return React.createElement('div', { className: 'main-layout' },
|
||||||
React.createElement('span', null, 'CloudDisk'),
|
// Header
|
||||||
React.createElement(Avatar, { icon: React.createElement(UserOutlined), style: { cursor: 'pointer' } })
|
React.createElement('div', { className: 'header' },
|
||||||
|
React.createElement('span', { className: 'header-title' }, 'CloudDisk'),
|
||||||
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 16 } },
|
||||||
|
React.createElement(Input, { placeholder: '搜索文件...', prefix: React.createElement(SearchOutlined), style: { width: 200, background: 'rgba(255,255,255,0.2)', border: 'none', color: 'white' } }),
|
||||||
|
React.createElement(Button, { type: 'text', icon: React.createElement(SettingOutlined), style: { color: 'white' } }),
|
||||||
|
React.createElement(Button, { type: 'text', icon: React.createElement(LogoutOutlined), onClick: handleLogout, style: { color: 'white' } })
|
||||||
|
)
|
||||||
),
|
),
|
||||||
React.createElement(Layout, {},
|
|
||||||
React.createElement(Sider, { width: 200, style: { background: '#fff' } },
|
// Content
|
||||||
React.createElement(Menu, { mode: 'inline', defaultSelectedKeys: ['files'], items: menuItems, style: { height: '100%' } })
|
React.createElement('div', { style: { display: 'flex' } },
|
||||||
),
|
// Sidebar
|
||||||
React.createElement(Content, { style: { padding: '20px', background: '#fff' } },
|
React.createElement('div', { style: { width: 200, background: '#fff', borderRight: '1px solid #f0f0f0', padding: '16px 0' } },
|
||||||
React.createElement('div', { className: 'toolbar' },
|
...menuItems.map(item =>
|
||||||
React.createElement(Button, { icon: React.createElement(UploadOutlined), style: { marginRight: '8px' } }, '上传文件'),
|
React.createElement('div', {
|
||||||
React.createElement(Button, { icon: React.createElement(FolderOutlined) }, '新建文件夹')
|
key: item.key,
|
||||||
|
onClick: item.onClick,
|
||||||
|
style: {
|
||||||
|
padding: '12px 24px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
background: page === item.key ? '#e6f7ff' : 'transparent',
|
||||||
|
color: page === item.key ? '#1890ff' : '#666',
|
||||||
|
display: 'flex', alignItems: 'center', gap: 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
React.createElement(item.icon),
|
||||||
|
React.createElement('span', null, item.label)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
React.createElement('p', { style: { color: '#999', textAlign: 'center', marginTop: '50px' } }, '暂无文件,请上传文件开始使用')
|
React.createElement('div', { style: { padding: '16px 24px', borderTop: '1px solid #f0f0f0', marginTop: 'auto' } },
|
||||||
|
React.createElement(CloudOutlined, { style: { marginRight: 8 } }),
|
||||||
|
React.createElement('span', { fontSize: 12, color: '#999' }, '2.1GB / 10GB')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// File List
|
||||||
|
React.createElement('div', { style: { flex: 1, background: '#f5f5f5', minHeight: 'calc(100vh - 56px)' } },
|
||||||
|
// Toolbar
|
||||||
|
React.createElement('div', { className: 'toolbar', style: { background: '#fff', marginBottom: 2 } },
|
||||||
|
React.createElement(Button, { icon: React.createElement(UploadOutlined), type: 'primary', onClick: () => setUploadModal(true) }, '上传文件'),
|
||||||
|
React.createElement(Button, { icon: React.createElement(FolderAddOutlined) }, '新建文件夹'),
|
||||||
|
React.createElement(Button, { icon: React.createElement(DeleteOutlined), disabled: selectedFiles.length === 0 }, '删除'),
|
||||||
|
React.createElement(Button, { icon: React.createElement(DownloadOutlined), disabled: selectedFiles.length === 0 }, '下载'),
|
||||||
|
React.createElement(Button, { icon: React.createElement(ShareAltOutlined), disabled: selectedFiles.length === 0 }, '分享')
|
||||||
|
),
|
||||||
|
|
||||||
|
// Breadcrumb
|
||||||
|
React.createElement(Breadcrumb, { style: { padding: '12px 24px', background: '#fff' },
|
||||||
|
React.createElement(Breadcrumb.Item, null, React.createElement(HomeOutlined), ' 全部文件')
|
||||||
|
),
|
||||||
|
|
||||||
|
// Files
|
||||||
|
React.createElement('div', { style: { background: '#fff', margin: '0 24px 24px', borderRadius: 8 } },
|
||||||
|
filteredFiles.length === 0
|
||||||
|
? React.createElement('div', { className: 'empty-wrap' }, React.createElement(FolderOutlined, { style: { fontSize: 48, marginBottom: 16 } }), React.createElement('p', null, '暂无文件'))
|
||||||
|
: filteredFiles.map(file =>
|
||||||
|
React.createElement('div', { key: file.id, className: 'file-item' },
|
||||||
|
getIcon(file.type, file.name),
|
||||||
|
React.createElement('div', { className: 'file-info' },
|
||||||
|
React.createElement('div', { className: 'file-name' }, file.name),
|
||||||
|
React.createElement('div', { className: 'file-meta' }, file.updated_at + ' · ' + formatSize(file.size))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// Upload Modal
|
||||||
|
React.createElement(Modal, {
|
||||||
|
title: '上传文件',
|
||||||
|
open: uploadModal,
|
||||||
|
onCancel: () => setUploadModal(false),
|
||||||
|
footer: null
|
||||||
|
},
|
||||||
|
React.createElement('div', { padding: '40px 0', textAlign: 'center', border: '2px dashed #d9d9d9', borderRadius: 8 },
|
||||||
|
React.createElement(UploadOutlined, { style: { fontSize: 48, color: '#1890ff' } }),
|
||||||
|
React.createElement('p', { marginTop: 16 }, '点击或拖拽文件到此处上传')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(App));
|
ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(App));
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user