Commit 2d5293e7 authored by 青山's avatar 青山

新增登录云函数接口

parent 85f9803e
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@cloudbase/js-sdk": "^2.17.4",
"@imgly/background-removal": "^1.5.5", "@imgly/background-removal": "^1.5.5",
"@react-oauth/google": "^0.12.1", "@react-oauth/google": "^0.12.1",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.16",
......
This diff is collapsed.
...@@ -229,7 +229,7 @@ function Header() { ...@@ -229,7 +229,7 @@ function Header() {
className="flex items-center space-x-3 focus:outline-none" className="flex items-center space-x-3 focus:outline-none"
> >
<img <img
src={user.picture} src="https://th.bing.com/th/id/OIP.nl9qIl6NYdJmv6jeMd8H7gAAAA?rs=1&pid=ImgDetMain&cb=idpwebpc2"
alt="User Avatar" alt="User Avatar"
className="w-8 h-8 sm:w-10 sm:h-10 rounded-full ring-2 ring-offset-2 ring-indigo-500 transition transform hover:scale-105" className="w-8 h-8 sm:w-10 sm:h-10 rounded-full ring-2 ring-offset-2 ring-indigo-500 transition transform hover:scale-105"
/> />
......
import React from 'react';
import { Navigate, useLocation } from 'react-router-dom';
/**
* 用于保护需要登录才能访问的页面
* 未登录时自动跳转到 /login,并记录原始跳转路径
*/
const PrivateRoute = ({ children }) => {
const location = useLocation();
const user = localStorage.getItem('user');
if (!user) {
// 记录跳转前的路径,登录后可重定向回来
sessionStorage.setItem('redirectAfterLogin', location.pathname + location.search);
return <Navigate to="/login" replace />;
}
return children;
};
export default PrivateRoute;
// main.jsx // main.jsx
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import cloudbase from '@cloudbase/js-sdk';
const cloudbaseApp = cloudbase.init({
env: 'xingzhi-authing-5gkb8pggc4cf2edf',
});
window.cloudbase = cloudbaseApp;
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
import { GoogleOAuthProvider } from '@react-oauth/google'; import { GoogleOAuthProvider } from '@react-oauth/google';
import App from './App'; import App from './App';
......
// src/pages/Login.jsx // src/pages/Login.jsx
import React, { useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { GoogleLogin } from '@react-oauth/google';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
import '../styles/Login.css'; import '../styles/Login.css';
...@@ -9,46 +8,73 @@ const Login = () => { ...@@ -9,46 +8,73 @@ const Login = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
const handleLoginSuccess = (credentialResponse) => { // 新增:用户名密码登录相关状态
const { credential } = credentialResponse; const [username, setUsername] = useState('testuser');
const base64Url = credential.split('.')[1]; const [password, setPassword] = useState('testpass');
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const [loginLoading, setLoginLoading] = useState(false);
const decodedPayload = JSON.parse(window.atob(base64)); const [loginError, setLoginError] = useState('');
const [rememberMe, setRememberMe] = useState(false);
// Save user data to localStorage
localStorage.setItem('user', JSON.stringify(decodedPayload));
// 新增:腾讯云开发用户名密码登录
// Check if there's a redirect path saved const handleCloudbaseLogin = async (e) => {
const redirectPath = sessionStorage.getItem('redirectAfterLogin'); e.preventDefault();
setLoginError('');
// 使用直接的 window.location 跳转而不是 React Router 导航 setLoginLoading(true);
// 这样可以确保页面完全重新加载,刷新登录状态
if (redirectPath) { // 记住密码逻辑
sessionStorage.removeItem('redirectAfterLogin'); if (rememberMe) {
window.location.href = redirectPath; localStorage.setItem('rememberedLogin', JSON.stringify({ username, password }));
} else { } else {
window.location.href = '/'; localStorage.removeItem('rememberedLogin');
} }
};
const handleLoginError = () => { const app = cloudbase.init({
console.log('Login failed'); env: 'xingzhi-authing-5gkb8pggc4cf2edf',
}; });
useEffect(() => { try {
// Redirect if user is already logged in if (!app) {
const userData = localStorage.getItem('user'); setLoginError('CloudBase SDK 未初始化');
if (userData) { setLoginLoading(false);
// Check if there's a redirect path saved return;
const redirectPath = sessionStorage.getItem('redirectAfterLogin'); }
if (redirectPath) { // 0. 先进行匿名登录,避免 "you can't request without auth" 报错
sessionStorage.removeItem('redirectAfterLogin'); const auth = app.auth();
window.location.href = redirectPath; await auth.signInAnonymously();
} else { // 1. 调用云函数 login 校验用户名密码,返回 ticket
const res = await app.callFunction({
name: 'login',
data: {
username,
password,
},
});
const { code } = res.result || {};
if (code == 200) {
localStorage.setItem('user', JSON.stringify({ username }));
window.location.href = '/'; window.location.href = '/';
} else {
setLoginError('用户名或密码错误');
} }
} catch (err) {
setLoginError(err.message || '用户名或密码错误');
setLoginLoading(false);
} }
}, [navigate]); };
// 记住密码功能:初始化时自动填充
useEffect(() => {
const saved = localStorage.getItem('rememberedLogin');
if (saved) {
try {
const { username: savedUser, password: savedPass } = JSON.parse(saved);
setUsername(savedUser || '');
setPassword(savedPass || '');
setRememberMe(true);
} catch {}
}
}, []);
return ( return (
<div className="login-container"> <div className="login-container">
...@@ -58,19 +84,44 @@ const Login = () => { ...@@ -58,19 +84,44 @@ const Login = () => {
{t('loginSubtitle', '欢迎使用 AI 工具箱,请登录以获得完整体验')} {t('loginSubtitle', '欢迎使用 AI 工具箱,请登录以获得完整体验')}
</p> </p>
<div className="login-options"> <div className="login-options">
<div className="google-login-wrapper"> {/* 腾讯云开发用户名密码登录表单 */}
<GoogleLogin <form className="cloudbase-login-form" onSubmit={handleCloudbaseLogin}>
onSuccess={handleLoginSuccess} <input
onError={handleLoginError} type="text"
theme="outline" placeholder="用户名"
size="large" value={username}
width="100%" onChange={(e) => setUsername(e.target.value)}
text="signin_with" required
shape="rectangular" className="login-input enhanced-input"
locale="zh_CN" autoComplete="username"
useOneTap />
<input
type="password"
placeholder="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="login-input enhanced-input"
autoComplete="current-password"
/> />
</div> <label style={{ display: 'flex', alignItems: 'center', marginBottom: '0.5rem', fontSize: '0.98rem', color: '#6B7280', userSelect: 'none' }}>
<input
type="checkbox"
checked={rememberMe}
onChange={e => setRememberMe(e.target.checked)}
style={{ marginRight: '8px', accentColor: '#6366f1' }}
/>
记住密码
</label>
<button
type="submit"
className="login-btn enhanced-btn"
disabled={loginLoading}
>
{loginLoading ? '登录中...' : '用户名密码登录'}
</button>
{loginError && <div className="login-error">{loginError}</div>}
</form>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -7,6 +7,64 @@ ...@@ -7,6 +7,64 @@
background: linear-gradient(135deg, #f8faff 0%, #f3f6ff 100%); background: linear-gradient(135deg, #f8faff 0%, #f3f6ff 100%);
padding: 1rem; padding: 1rem;
margin-top: -44px; margin-top: -44px;
}
/* 美化用户名密码登录输入框和按钮 */
.cloudbase-login-form {
display: flex;
flex-direction: column;
gap: 1.2rem;
margin-top: 0.5rem;
}
.enhanced-input {
width: 100%;
padding: 12px 16px;
font-size: 1rem;
border: 1.5px solid #c7d2fe;
border-radius: 8px;
background: #f3f6ff;
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.06);
transition: border 0.2s, box-shadow 0.2s;
outline: none;
}
.enhanced-input:focus {
border-color: #6366f1;
background: #fff;
box-shadow: 0 4px 16px rgba(99, 102, 241, 0.13);
}
.enhanced-btn {
width: 100%;
padding: 12px 0;
font-size: 1.08rem;
font-weight: 600;
color: #fff;
background: linear-gradient(90deg, #6366f1 0%, #4f46e5 100%);
border: none;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(99, 102, 241, 0.10);
cursor: pointer;
transition: background 0.2s, transform 0.15s, box-shadow 0.2s;
letter-spacing: 0.02em;
}
.enhanced-btn:hover, .enhanced-btn:focus {
background: linear-gradient(90deg, #4f46e5 0%, #6366f1 100%);
transform: translateY(-2px) scale(1.03);
box-shadow: 0 6px 24px rgba(99, 102, 241, 0.18);
}
.login-error {
color: #ef4444;
margin-top: 0.5rem;
font-size: 0.98rem;
background: #fef2f2;
border-radius: 6px;
padding: 0.5rem 0.8rem;
border: 1px solid #fecaca;
box-shadow: 0 1px 4px rgba(239, 68, 68, 0.06);
} }
.login-card { .login-card {
...@@ -108,4 +166,4 @@ ...@@ -108,4 +166,4 @@
.login-title { .login-title {
font-size: 1.5rem; font-size: 1.5rem;
} }
} }
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment