diff --git a/docs/front-proto/01login/index.html b/docs/front-proto/01login/index.html new file mode 100644 index 0000000..36b1e85 --- /dev/null +++ b/docs/front-proto/01login/index.html @@ -0,0 +1,364 @@ + + + + + + CloudStorage - 登录 + + + + +
+ + +
+
+ + +
+ +
+ + +
+ +
+ + 忘记密码? +
+ + +
+ +
+ 新用户? +
+ + +
+ + + + \ No newline at end of file diff --git a/docs/front-proto/02cloud/index.html b/docs/front-proto/02cloud/index.html new file mode 100644 index 0000000..31eadf1 --- /dev/null +++ b/docs/front-proto/02cloud/index.html @@ -0,0 +1,471 @@ + + + + + + Cloudaloud网盘 + + + + + + +
+
+ +
+
+ + Cloudaloud网盘 +
+
+
+ + 你好,pengshuaiReal + +
+
+
+ + +
+
+ +

云盘文件管理

+

管理您的云盘文件

+
+ + + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
文件名原始文件名大小类型修改时间操作
+
+
+ +
+ 41e2439d-002d-47ed-89c3-360a04c3820f.py +
+
debug_download_detailed.py4.8 KBX-PYTHON2025/10/15 09:57:37 +
+ + +
+
+
+
+ +
+ 0c0834bb-6313-4ae0-91c4-c3b7b123001f.txt +
+
success_test.txt160 BytesPLAIN2025/10/15 09:19:19 +
+ + +
+
+
+
+ +
+ ed900fb4-2cbb-4e5c-abd8-4a4cfa32eb83.txt +
+
test_upload.txt210 BytesPLAIN2025/10/15 09:19:24 +
+ + +
+
+
+
+ +
+ 08be0e5f-969c-4ed5-86c6-2d8470566a10.js +
+
index.js1.9 KBJAVASCRIPT2025/10/15 08:56:34 +
+ + +
+
+
+
+ +
+ 68ba13ca-7bee-4d16-bb1f-22e451ba5b83.json +
+
package.json1.15 KBJSON2025/10/15 08:56:33 +
+ + +
+
+
+
+ +
+ 4345f1f3-c7e8-449e-b69b-c65c7eb4279b.rtf +
+
LICENSE-chs.rtf183.66 KBRTF2025/10/15 08:56:33 +
+ + +
+
+
+
+ +
+ f63509cc-ee0f-4350-9919-351e73fe166f.rtf +
+
LICENSE-bul.rtf476.44 KBRTF2025/10/15 08:56:33 +
+ + +
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index ec72c95..5d08b52 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5,6 +5,7 @@ 云盘应用 +
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7b87dbd..166fb75 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "cloud-drive-frontend", "version": "1.0.0", "dependencies": { + "@fortawesome/fontawesome-free": "^7.1.0", "@hookform/resolvers": "^3.3.2", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", @@ -900,6 +901,15 @@ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.1.0.tgz", + "integrity": "sha512-+WxNld5ZCJHvPQCr/GnzCTVREyStrAJjisUPtUxG5ngDA8TMlPnKp6dddlTpai4+1GNmltAeuk1hJEkBohwZYA==", + "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", + "engines": { + "node": ">=6" + } + }, "node_modules/@hookform/resolvers": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 98b0bc4..0f686e1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "test:e2e": "playwright test" }, "dependencies": { + "@fortawesome/fontawesome-free": "^7.1.0", "@hookform/resolvers": "^3.3.2", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 969027b..f862c8f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,8 +4,7 @@ import { Toaster } from 'sonner' // Pages import HomePage from './pages/HomePage' -import LoginPage from './pages/LoginPage' -import RegisterPage from './pages/RegisterPage' +import BeautifyLoginPage from './pages/BeautifyLoginPage' import CloudPage from './pages/CloudPage' // Components @@ -28,10 +27,10 @@ function App() { }> } /> - } /> - } /> } /> + } /> + } /> diff --git a/frontend/src/components/Header.css b/frontend/src/components/Header.css new file mode 100644 index 0000000..487efc1 --- /dev/null +++ b/frontend/src/components/Header.css @@ -0,0 +1,191 @@ +.header-container { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-bottom: 1px solid rgba(255, 255, 255, 0.3); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + position: relative; + z-index: 100; + overflow: hidden; +} + +.header-container::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: conic-gradient( + transparent, + rgba(168, 85, 247, 0.1), + transparent 30% + ); + animation: rotate 6s linear infinite; + z-index: -1; +} + +@keyframes rotate { + 100% { + transform: rotate(360deg); + } +} + +.header-content { + display: flex; + align-items: center; + justify-content: space-between; + padding: 15px 30px; + position: relative; + z-index: 1; +} + +.header-left { + display: flex; + align-items: center; + gap: 15px; +} + +.cloud-icon { + width: 32px; + height: 32px; + background: linear-gradient(135deg, #667eea, #764ba2); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + color: white; + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); +} + +.header-title { + font-size: 20px; + font-weight: 700; + color: #333; + background: linear-gradient(135deg, #667eea, #764ba2); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin: 0; +} + +.header-right { + display: flex; + align-items: center; + gap: 20px; +} + +.user-info { + display: flex; + align-items: center; + gap: 10px; + background: rgba(255, 255, 255, 0.8); + padding: 8px 16px; + border-radius: 12px; + border: 1px solid rgba(0, 0, 0, 0.1); + backdrop-filter: blur(10px); + transition: all 0.3s ease; +} + +.user-info:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.user-icon { + width: 32px; + height: 32px; + background: linear-gradient(135deg, #667eea, #764ba2); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; +} + +.user-greeting { + font-size: 14px; + font-weight: 600; + color: #333; +} + +.logout-btn { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border: none; + border-radius: 8px; + padding: 8px 16px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); + display: flex; + align-items: center; + gap: 6px; +} + +.logout-btn:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4); +} + +.logout-btn:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +.back-btn { + background: rgba(255, 255, 255, 0.8); + color: #666; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 8px; + padding: 8px 12px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 6px; + backdrop-filter: blur(10px); +} + +.back-btn:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + background: rgba(255, 255, 255, 0.9); +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .header-content { + padding: 10px 15px; + } + + .header-left { + gap: 10px; + } + + .cloud-icon { + width: 28px; + height: 28px; + } + + .header-title { + font-size: 18px; + } + + .user-info { + padding: 6px 12px; + gap: 8px; + } + + .user-greeting { + font-size: 13px; + } + + .header-right { + gap: 10px; + } +} \ No newline at end of file diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index e94d88f..0e1cb68 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,8 +1,6 @@ import { useNavigate, useLocation } from 'react-router-dom' -import { User, LogOut, ArrowLeft } from 'lucide-react' -import { Button } from '@/components/ui/button' import { getUserInfoFromCookie, clearAuthCookies } from '@/utils/cookie' -import { Cloud} from 'lucide-react' + export default function Header() { const navigate = useNavigate() const location = useLocation() @@ -18,25 +16,35 @@ export default function Header() { navigate('/login') } - const handleBack = () => { - navigate(-1) - } - return ( -
+
- - Cloudaloud网盘 + + Cloudaloud网盘
{userInfo && isCloudPage && (
- + 你好,{userInfo.username} - +
)} diff --git a/frontend/src/pages/BeautifyLoginPage.css b/frontend/src/pages/BeautifyLoginPage.css new file mode 100644 index 0000000..c489fb5 --- /dev/null +++ b/frontend/src/pages/BeautifyLoginPage.css @@ -0,0 +1,233 @@ +.beautify-login-container { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #667eea, #764ba2); + background-size: 400% 400%; + animation: gradientBG 15s ease infinite; + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + margin: 0; +} + +@keyframes gradientBG { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +.login-container { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 20px; + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3); + width: 100%; + max-width: 420px; + padding: 40px; + position: relative; + overflow: hidden; +} + +.login-container::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: conic-gradient( + transparent, + rgba(168, 85, 247, 0.4), + transparent 30% + ); + animation: rotate 6s linear infinite; + z-index: -1; +} + +@keyframes rotate { + 100% { + transform: rotate(360deg); + } +} + +.logo { + text-align: center; + margin-bottom: 30px; +} + +.logo i { + font-size: 48px; + color: #667eea; + background: linear-gradient(135deg, #667eea, #764ba2); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.logo h1 { + font-size: 28px; + font-weight: 700; + margin-top: 15px; + color: #333; + background: linear-gradient(135deg, #667eea, #764ba2); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.logo p { + color: #666; + margin-top: 8px; + font-size: 14px; +} + +.input-group { + margin-bottom: 25px; + position: relative; +} + +.input-group i { + position: absolute; + left: 15px; + top: 50%; + transform: translateY(-50%); + color: #999; + font-size: 18px; +} + +.input-group input { + width: 100%; + padding: 15px 15px 15px 45px; + border: 2px solid #e1e5e9; + border-radius: 12px; + font-size: 16px; + transition: all 0.3s ease; + background: #f8f9fa; + box-sizing: border-box; +} + +.input-group input:focus { + outline: none; + border-color: #667eea; + background: white; + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.1); +} + +.options { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 25px; + font-size: 14px; +} + +.remember-me { + display: flex; + align-items: center; + cursor: pointer; +} + +.remember-me input { + margin-right: 8px; + cursor: pointer; +} + +.forgot-password { + color: #667eea; + text-decoration: none; + transition: color 0.3s ease; +} + +.forgot-password:hover { + color: #764ba2; + text-decoration: underline; +} + +.login-btn { + width: 100%; + padding: 15px; + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border: none; + border-radius: 12px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3); + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +.login-btn:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); +} + +.login-btn:active:not(:disabled) { + transform: translateY(0); +} + +.login-btn:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +.divider { + text-align: center; + margin: 30px 0; + position: relative; + color: #999; + font-size: 14px; +} + +.divider::before { + content: ''; + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 1px; + background: #e1e5e9; + z-index: 1; +} + +.divider span { + background: white; + position: relative; + z-index: 2; + padding: 0 15px; +} + + +.signup-link { + text-align: center; + font-size: 14px; + color: #666; +} + +.signup-link a { + color: #667eea; + text-decoration: none; + font-weight: 600; + transition: color 0.3s ease; +} + +.signup-link a:hover { + color: #764ba2; + text-decoration: underline; +} + +@media (max-width: 480px) { + .login-container { + padding: 30px 20px; + margin: 10px; + } + + .logo h1 { + font-size: 24px; + } +} \ No newline at end of file diff --git a/frontend/src/pages/BeautifyLoginPage.tsx b/frontend/src/pages/BeautifyLoginPage.tsx new file mode 100644 index 0000000..010f523 --- /dev/null +++ b/frontend/src/pages/BeautifyLoginPage.tsx @@ -0,0 +1,348 @@ +import React, { useState, useEffect } from 'react' +import { useNavigate, useLocation } from 'react-router-dom' +import { toast } from 'sonner' +import { authAPI } from '@/services/api' +import { setUserInfoInCookie, setTokenInCookie } from '@/utils/cookie' +import './BeautifyLoginPage.css' + +const BeautifyLoginPage: React.FC = () => { + const location = useLocation() + const navigate = useNavigate() + const [isLoginMode, setIsLoginMode] = useState(true) + const [formData, setFormData] = useState({ + // 登录字段 + username: '', + password: '', + rememberMe: false, + // 注册字段 + registerUsername: '', + registerPassword: '', + confirmPassword: '', + email: '' + }) + const [isLoading, setIsLoading] = useState(false) + + // 根据路由决定默认模式 + useEffect(() => { + if (location.pathname === '/register') { + setIsLoginMode(false) + } else { + setIsLoginMode(true) + } + }, [location.pathname]) + + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value, type, checked } = e.target + setFormData(prev => ({ + ...prev, + [name]: type === 'checkbox' ? checked : value + })) + } + + const handleLoginSubmit = async (e: React.FormEvent) => { + e.preventDefault() + + if (!formData.username || !formData.password) { + toast.error('请填写用户名和密码') + return + } + + setIsLoading(true) + + try { + const response = await authAPI.login({ + username: formData.username, + password: formData.password + }) + + if (response.success) { + // 保存用户信息到cookie + if (response.data?.user) { + setUserInfoInCookie(response.data.user) + } + + // 保存JWT token到cookie + if (response.data?.tokens?.access_token) { + setTokenInCookie(response.data.tokens.access_token) + // 同时也保存到localStorage作为备份 + localStorage.setItem('access_token', response.data.tokens.access_token) + } + if (response.data?.tokens?.refresh_token) { + localStorage.setItem('refresh_token', response.data.tokens.refresh_token) + } + + if (formData.rememberMe) { + localStorage.setItem('remember_user', formData.username) + } else { + localStorage.removeItem('remember_user') + } + + toast.success('登录成功!正在跳转...') + setTimeout(() => { + navigate('/cloud') + }, 1000) + } else { + toast.error(response.detail?.message || '登录失败') + } + } catch (error: any) { + console.error('Login error:', error) + + // 尝试从错误响应中提取具体错误信息 + let errorMessage = '登录失败,请稍后重试' + + if (error.response?.data) { + const errorData = error.response.data + if (errorData.detail?.message) { + errorMessage = errorData.detail.message + } else if (errorData.detail?.code) { + // 根据错误码显示更友好的消息 + switch (errorData.detail.code) { + case 'INVALID_CREDENTIALS': + errorMessage = '用户名或密码错误' + break + case 'USER_NOT_FOUND': + errorMessage = '用户不存在' + break + default: + errorMessage = errorData.detail.message || '登录失败' + } + } + } + + toast.error(errorMessage) + } finally { + setIsLoading(false) + } + } + + const handleRegisterSubmit = async (e: React.FormEvent) => { + e.preventDefault() + + // 基本验证 - 只有两个规则 + if (formData.registerPassword !== formData.confirmPassword) { + toast.error('两次输入的密码不一致') + return + } + + if (formData.registerPassword.length <= 5) { + toast.error('密码长度必须大于5个字符') + return + } + + setIsLoading(true) + + try { + const response = await authAPI.register({ + username: formData.registerUsername, + email: formData.email, + password: formData.registerPassword, + confirm_password: formData.confirmPassword + }) + + if (response.success) { + // 保存tokens到localStorage + if (response.data?.tokens?.access_token) { + localStorage.setItem('access_token', response.data.tokens.access_token) + } + if (response.data?.tokens?.refresh_token) { + localStorage.setItem('refresh_token', response.data.tokens.refresh_token) + } + + toast.success('注册成功!正在跳转...') + setTimeout(() => { + navigate('/cloud') + }, 1500) + } else { + // 处理API返回的错误 + const errorMessage = response.detail?.message || '注册失败' + toast.error(errorMessage) + } + } catch (error: any) { + console.error('Registration error:', error) + + // 尝试从错误响应中提取具体错误信息 + let errorMessage = '注册失败,请稍后重试' + + if (error.response?.data) { + const errorData = error.response.data + if (errorData.detail?.message) { + errorMessage = errorData.detail.message + } else if (errorData.detail?.code) { + // 根据错误码显示更友好的消息 + switch (errorData.detail.code) { + case 'USERNAME_EXISTS': + errorMessage = '用户名已存在' + break + case 'CREATION_FAILED': + errorMessage = '用户创建失败,请检查输入信息' + break + default: + errorMessage = errorData.detail.message || '注册失败' + } + } else if (Array.isArray(errorData.detail)) { + // 处理字段验证错误 + errorMessage = errorData.detail.map((err: any) => err.msg).join(', ') + } + } + + toast.error(errorMessage) + } finally { + setIsLoading(false) + } + } + + const toggleMode = () => { + setIsLoginMode(!isLoginMode) + // 清空错误状态 + setIsLoading(false) + } + + return ( +
+
+
+ +

{isLoginMode ? 'CloudStorage' : '用户注册'}

+

{isLoginMode ? '安全可靠的云端存储服务' : '创建您的云端存储账户'}

+
+ + {isLoginMode ? ( +
+
+ + +
+ +
+ + +
+ +
+ + 忘记密码? +
+ + +
+ ) : ( +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ )} + +
+ {isLoginMode ? '新用户?' : '已有账户?'} +
+ +
+ {isLoginMode ? ( + <> + 还没有账户? + { e.preventDefault(); toggleMode(); }}> + 立即注册 + + + ) : ( + <> + 已有账户? + { e.preventDefault(); toggleMode(); }}> + 立即登录 + + + )} +
+
+
+ ) +} + +export default BeautifyLoginPage \ No newline at end of file diff --git a/frontend/src/pages/CloudPage.css b/frontend/src/pages/CloudPage.css new file mode 100644 index 0000000..3673f8f --- /dev/null +++ b/frontend/src/pages/CloudPage.css @@ -0,0 +1,338 @@ +.cloud-page-container { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #667eea, #764ba2); + background-size: 400% 400%; + animation: gradientBG 15s ease infinite; + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + margin: 0; +} + +@keyframes gradientBG { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +.cloud-main-container { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 20px; + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3); + width: 100%; + max-width: 1200px; + overflow: hidden; +} + +/* 新的云盘内容样式 */ +.cloud-content { + padding: 24px; +} + +.cloud-header { + text-align: center; + margin-bottom: 30px; +} + +.cloud-header i { + font-size: 48px; + color: #667eea; + margin-bottom: 15px; +} + +.cloud-header h1 { + font-size: 28px; + font-weight: 700; + margin-bottom: 8px; + color: #333; + background: linear-gradient(135deg, #667eea, #764ba2); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.cloud-header p { + color: #666; + margin: 0; +} + +.user-info { + display: flex; + align-items: center; + gap: 10px; + background: #f1f5f9; + padding: 12px 20px; + border-radius: 12px; + margin-bottom: 25px; + font-size: 14px; +} + +.upload-section { + margin-bottom: 24px; +} + +.upload-btn { + display: inline-flex; + align-items: center; + gap: 8px; + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border: none; + border-radius: 12px; + padding: 12px 24px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3); +} + +.upload-btn:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); +} + +.upload-btn:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +.files-table-container { + overflow-x: auto; +} + +.files-table { + width: 100%; + border-collapse: collapse; + background: white; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); +} + +.files-table th { + background: #f8fafc; + padding: 16px 12px; + text-align: left; + font-weight: 600; + color: #64748b; + font-size: 14px; + border-bottom: 1px solid #e2e8f0; +} + +.files-table td { + padding: 16px 12px; + border-bottom: 1px solid #e2e8f0; + font-size: 14px; +} + +.files-table tr:last-child td { + border-bottom: none; +} + +.files-table tr:hover td { + background: #f8fafc; +} + +.file-name { + display: flex; + align-items: center; + gap: 10px; + font-weight: 600; + color: #333; +} + +.file-icon { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + background: #eff6ff; + border-radius: 8px; + color: #667eea; +} + +.file-icon.image { + background: rgba(34, 197, 94, 0.1); + color: #22c55e; +} + +.file-icon.video { + background: rgba(59, 130, 246, 0.1); + color: #3b82f6; +} + +.file-icon.audio { + background: rgba(168, 85, 247, 0.1); + color: #a855f7; +} + +.file-icon.pdf { + background: rgba(239, 68, 68, 0.1); + color: #ef4444; +} + +.file-icon.archive { + background: rgba(245, 158, 11, 0.1); + color: #f59e0b; +} + +.file-icon.default { + background: rgba(107, 114, 128, 0.1); + color: #6b7280; +} + +.file-original-name { + color: #667eea; + font-weight: 500; +} + +.file-size { + color: #666; + font-size: 14px; +} + +.file-type { + display: inline-block; + padding: 4px 8px; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + background: rgba(102, 126, 234, 0.1); + color: #667eea; +} + +.file-date { + color: #999; + font-size: 14px; +} + +.action-buttons { + display: flex; + gap: 8px; + justify-content: flex-end; +} + +.action-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 8px; + border: none; + cursor: pointer; + transition: all 0.2s ease; +} + +.action-btn.download { + background: #ecfdf5; + color: #10b981; +} + +.action-btn.download:hover { + background: #d1fae5; + transform: scale(1.1); +} + +.action-btn.delete { + background: #fef2f2; + color: #ef4444; +} + +.action-btn.delete:hover { + background: #fee2e2; + transform: scale(1.1); +} + +.action-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.loading-state, .error-state, .empty-state { + text-align: center; + padding: 60px 20px; + color: #666; +} + +.loading-state i, .error-state i, .empty-state i { + font-size: 64px; + margin-bottom: 20px; + color: #667eea; + opacity: 0.6; +} + +.loading-state h3, .error-state h3, .empty-state h3 { + font-size: 20px; + margin-bottom: 10px; + color: #333; +} + +.retry-btn { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border: none; + border-radius: 8px; + padding: 10px 20px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + margin-top: 15px; +} + +.retry-btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .cloud-main-container { + padding: 20px; + margin: 10px; + } + + .cloud-header h1 { + font-size: 24px; + } + + .files-table { + font-size: 14px; + } + + .files-table th, + .files-table td { + padding: 10px 5px; + } + + .action-buttons { + flex-direction: column; + gap: 4px; + } + + .action-btn { + width: 32px; + height: 32px; + } +} + +/* Loading spinner */ +.loading-spinner { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: white; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} \ No newline at end of file diff --git a/frontend/src/pages/CloudPage.tsx b/frontend/src/pages/CloudPage.tsx index 57c6383..b12155a 100644 --- a/frontend/src/pages/CloudPage.tsx +++ b/frontend/src/pages/CloudPage.tsx @@ -2,17 +2,9 @@ import { useState, useEffect, useRef } from 'react' import { useNavigate } from 'react-router-dom' import { authAPI, filesAPI } from '@/services/api' import { toast } from 'sonner' -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table" -import { Button } from '@/components/ui/button' -import { Cloud, Upload, File, Folder, Download, Trash2, User } from 'lucide-react' -import { getUserIdFromCookie, getUserInfoFromCookie } from '@/utils/cookie' +import { Upload, File, Download, Trash2, LogOut, Cloud, User } from 'lucide-react' +import { getUserIdFromCookie, getUserInfoFromCookie, clearAuthCookies } from '@/utils/cookie' +import './CloudPage.css' // Toast 显示时长常量(毫秒) const TOAST_DURATION = 3000 // 3秒,可根据需要修改 @@ -232,6 +224,28 @@ export default function CloudPage() { } } + // 用户登出处理 + const handleLogout = async () => { + try { + // 调用登出API + await authAPI.logout() + toast.success('登出成功!') + } catch (error: any) { + console.error('Logout error:', error) + // 即使API调用失败,也要清除本地数据 + toast.error('登出成功!') + } finally { + // 清除本地存储的用户信息 + clearAuthCookies() + localStorage.removeItem('access_token') + localStorage.removeItem('refresh_token') + localStorage.removeItem('user') + + // 跳转到登录页面 + navigate('/login') + } + } + // 组件加载时检查用户状态并获取文件列表 useEffect(() => { // 检查用户是否已登录 @@ -256,144 +270,163 @@ export default function CloudPage() { return new Date(dateString).toLocaleString('zh-CN') } - // 获取文件图标 - const getFileIcon = (mimeType: string) => { + // 获取文件图标样式类 + const getFileIconClass = (mimeType: string) => { if (mimeType.startsWith('image/')) { - return + return 'image' } else if (mimeType.startsWith('video/')) { - return + return 'video' } else if (mimeType.startsWith('audio/')) { - return + return 'audio' } else if (mimeType.includes('pdf')) { - return + return 'pdf' } else if (mimeType.includes('zip') || mimeType.includes('rar')) { - return + return 'archive' } else { - return + return 'default' } } return ( -
-
-
-

- 云盘文件管理 -

-

管理您的云盘文件

+
+
+
+ {/* 页面标题 */} +
+ +

云盘文件管理

+

管理您的云盘文件

+
+ + {/* 用户信息 */} + {userInfo && ( +
+ + 欢迎,{userInfo.username} + | + 存储空间:{formatFileSize(userInfo.storage_used || 0)} / {formatFileSize(userInfo.storage_quota || 104857600)} +
+ )} + + {/* 上传按钮 */} +
+ + +
+ + {/* 文件列表 */} +
+ {loading ? ( +
+ +

加载中...

+

正在获取您的文件列表

+
+ ) : error ? ( +
+ +

加载失败

+

{error}

+ +
+ ) : files.length === 0 ? ( +
+ +

暂无文件

+

您的云盘还是空的,使用上方按钮上传第一个文件吧!

+
+ ) : ( + + + + + + + + + + + + + {files.map((file) => ( + + + + + + + + + ))} + +
文件名原始文件名大小类型修改时间操作
+
+
+ +
+ {file.filename} +
+
+ {file.original_filename} + + {formatFileSize(file.file_size)} + + + {file.mime_type.split('/')[1]?.toUpperCase() || 'FILE'} + + + {formatDate(file.updated_at)} + +
+ + +
+
+ )} +
- - {/* 上传按钮 */} -
- - -
- - {/* 文件列表 */} -
- {loading ? ( -
-
- -

加载中...

-
-
- ) : error ? ( -
-
-

{error}

- -
-
- ) : files.length === 0 ? ( -
-
- -

暂无文件,请上传

- -
-
- ) : ( - - - - 文件名 - 文件中文名 - 大小 - 类型 - 修改时间 - 操作 - - - - {files.map((file) => ( - - -
- {getFileIcon(file.mime_type)} - {file.filename} -
-
- - {file.original_filename} - - {formatFileSize(file.file_size)} - - - {file.mime_type.split('/')[1]?.toUpperCase() || 'FILE'} - - - - {formatDate(file.updated_at)} - - -
- - -
-
-
- ))} -
-
- )} -
) } \ No newline at end of file