2025-10-28 11:30:30 +00:00
|
|
|
|
import React, { useState, useEffect } from 'react';
|
2025-12-26 08:59:05 +00:00
|
|
|
|
import { Brain, Users, Calendar, TrendingUp, X, User, Lock, Library, Download, LogIn, Eye, EyeOff } from 'lucide-react';
|
2025-10-21 09:30:30 +00:00
|
|
|
|
import { Link } from 'react-router-dom';
|
2025-08-29 08:37:55 +00:00
|
|
|
|
import apiClient from '../utils/apiClient';
|
2025-08-05 01:44:28 +00:00
|
|
|
|
import { buildApiUrl, API_ENDPOINTS } from '../config/api';
|
|
|
|
|
|
import './HomePage.css';
|
|
|
|
|
|
|
|
|
|
|
|
const HomePage = ({ onLogin }) => {
|
|
|
|
|
|
const [showLoginModal, setShowLoginModal] = useState(false);
|
|
|
|
|
|
const [loginForm, setLoginForm] = useState({ username: '', password: '' });
|
|
|
|
|
|
const [loginError, setLoginError] = useState('');
|
|
|
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
2025-10-28 11:30:30 +00:00
|
|
|
|
const [rememberMe, setRememberMe] = useState(false);
|
2025-12-26 08:59:05 +00:00
|
|
|
|
const [showPassword, setShowPassword] = useState(false);
|
2025-10-28 11:30:30 +00:00
|
|
|
|
|
|
|
|
|
|
// 组件挂载时,从localStorage读取保存的用户名
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const savedUsername = localStorage.getItem('rememberedUsername');
|
|
|
|
|
|
const isRemembered = localStorage.getItem('rememberMe') === 'true';
|
|
|
|
|
|
|
|
|
|
|
|
if (savedUsername && isRemembered) {
|
|
|
|
|
|
setLoginForm(prev => ({ ...prev, username: savedUsername }));
|
|
|
|
|
|
setRememberMe(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, []);
|
2025-08-05 01:44:28 +00:00
|
|
|
|
|
|
|
|
|
|
const handleLogin = async (e) => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
setIsLoading(true);
|
|
|
|
|
|
setLoginError('');
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-09-30 04:13:54 +00:00
|
|
|
|
const loginResponse = await apiClient.post(buildApiUrl(API_ENDPOINTS.AUTH.LOGIN), loginForm);
|
2025-10-28 11:30:30 +00:00
|
|
|
|
|
|
|
|
|
|
// 处理记住用户名
|
|
|
|
|
|
if (rememberMe) {
|
|
|
|
|
|
localStorage.setItem('rememberedUsername', loginForm.username);
|
|
|
|
|
|
localStorage.setItem('rememberMe', 'true');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
localStorage.removeItem('rememberedUsername');
|
|
|
|
|
|
localStorage.removeItem('rememberMe');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-30 04:13:54 +00:00
|
|
|
|
onLogin(loginResponse.data);
|
2025-08-05 01:44:28 +00:00
|
|
|
|
setShowLoginModal(false);
|
|
|
|
|
|
} catch (error) {
|
2025-10-28 11:30:30 +00:00
|
|
|
|
setLoginError(error.response?.data?.message || '登录失败,请重试');
|
2025-08-05 01:44:28 +00:00
|
|
|
|
} finally {
|
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleInputChange = (e) => {
|
|
|
|
|
|
setLoginForm({
|
|
|
|
|
|
...loginForm,
|
|
|
|
|
|
[e.target.name]: e.target.value
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const closeModal = () => {
|
|
|
|
|
|
setShowLoginModal(false);
|
|
|
|
|
|
setLoginForm({ username: '', password: '' });
|
|
|
|
|
|
setLoginError('');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="homepage">
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
|
<header className="homepage-header">
|
|
|
|
|
|
<div className="header-content">
|
|
|
|
|
|
<div className="logo">
|
|
|
|
|
|
<Brain className="logo-icon" />
|
2025-09-30 04:13:54 +00:00
|
|
|
|
<span className="logo-text">iMeeting</span>
|
2025-08-05 01:44:28 +00:00
|
|
|
|
</div>
|
2025-10-21 09:30:30 +00:00
|
|
|
|
<div className="header-actions">
|
|
|
|
|
|
<Link to="/downloads" className="download-link">
|
|
|
|
|
|
<Download size={18} />
|
|
|
|
|
|
<span>下载客户端</span>
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="login-btn"
|
|
|
|
|
|
onClick={() => setShowLoginModal(true)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<LogIn size={18} />
|
|
|
|
|
|
<span>登录</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2025-08-05 01:44:28 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Hero Section */}
|
|
|
|
|
|
<section className="hero">
|
|
|
|
|
|
<div className="hero-content">
|
2025-12-16 10:56:28 +00:00
|
|
|
|
<h1 className="hero-title">iMeeting - 灵 枢 (Líng Shū)</h1>
|
2025-08-05 01:44:28 +00:00
|
|
|
|
<p className="hero-subtitle">
|
2025-12-16 10:56:28 +00:00
|
|
|
|
让每一次谈话都产生价值
|
2025-08-05 01:44:28 +00:00
|
|
|
|
</p>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="cta-button"
|
|
|
|
|
|
onClick={() => setShowLoginModal(true)}
|
|
|
|
|
|
>
|
|
|
|
|
|
开始使用
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Features Section */}
|
|
|
|
|
|
<section className="features">
|
|
|
|
|
|
<div className="features-container">
|
|
|
|
|
|
<h2 className="features-title">核心功能</h2>
|
|
|
|
|
|
<div className="features-grid">
|
2025-09-30 04:13:54 +00:00
|
|
|
|
<div className="feature-card feature-card-1">
|
|
|
|
|
|
<Brain className="feature-icon feature-icon-1" />
|
2025-08-05 01:44:28 +00:00
|
|
|
|
<h3>AI智能转录</h3>
|
2025-09-30 04:13:54 +00:00
|
|
|
|
<p>自动将会议音频转为文字,并识别不同发言人。</p>
|
2025-08-05 01:44:28 +00:00
|
|
|
|
</div>
|
2025-09-30 04:13:54 +00:00
|
|
|
|
<div className="feature-card feature-card-2">
|
|
|
|
|
|
<TrendingUp className="feature-icon feature-icon-2" />
|
|
|
|
|
|
<h3>智能摘要</h3>
|
|
|
|
|
|
<p>AI自动生成会议摘要,提取关键信息和决策。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="feature-card feature-card-3">
|
|
|
|
|
|
<Library className="feature-icon feature-icon-3" />
|
|
|
|
|
|
<h3>交叉知识库</h3>
|
|
|
|
|
|
<p>所有会议的摘要和内容汇聚成一个可供检索的知识库。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="feature-card feature-card-4">
|
|
|
|
|
|
<Users className="feature-icon feature-icon-4" />
|
2025-08-05 01:44:28 +00:00
|
|
|
|
<h3>参会人管理</h3>
|
2025-09-30 04:13:54 +00:00
|
|
|
|
<p>轻松管理会议参与者,追踪会议参与情况。</p>
|
2025-08-05 01:44:28 +00:00
|
|
|
|
</div>
|
2025-09-30 04:13:54 +00:00
|
|
|
|
<div className="feature-card feature-card-5">
|
|
|
|
|
|
<Calendar className="feature-icon feature-icon-5" />
|
2025-08-05 01:44:28 +00:00
|
|
|
|
<h3>时间轴视图</h3>
|
2025-09-30 04:13:54 +00:00
|
|
|
|
<p>按时间顺序展示所有会议,快速回顾历史记录。</p>
|
2025-08-05 01:44:28 +00:00
|
|
|
|
</div>
|
2025-09-30 04:13:54 +00:00
|
|
|
|
<div className="feature-card feature-card-6">
|
|
|
|
|
|
<Download className="feature-icon feature-icon-6" />
|
|
|
|
|
|
<h3>数据导出</h3>
|
|
|
|
|
|
<p>导出会议摘要、脑图和转录内容,方便归档和分享。</p>
|
2025-08-05 01:44:28 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
2025-09-11 05:16:24 +00:00
|
|
|
|
{/* Footer */}
|
|
|
|
|
|
<footer className="homepage-footer">
|
|
|
|
|
|
<p>© 2025 紫光汇智信息技术有限公司. All rights reserved.</p>
|
|
|
|
|
|
<p>备案号:渝ICP备2023007695号-11</p>
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
|
2025-08-05 01:44:28 +00:00
|
|
|
|
{/* Login Modal */}
|
|
|
|
|
|
{showLoginModal && (
|
|
|
|
|
|
<div className="modal-overlay" onClick={closeModal}>
|
|
|
|
|
|
<div className="login-modal" onClick={e => e.stopPropagation()}>
|
|
|
|
|
|
<div className="modal-header">
|
|
|
|
|
|
<h2>登录到iMeeting</h2>
|
|
|
|
|
|
<button className="close-btn" onClick={closeModal}>
|
|
|
|
|
|
<X size={20} />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<form className="login-form" onSubmit={handleLogin}>
|
|
|
|
|
|
<div className="form-group">
|
|
|
|
|
|
<label htmlFor="username">
|
|
|
|
|
|
<User size={18} />
|
|
|
|
|
|
用户名
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
id="username"
|
|
|
|
|
|
name="username"
|
|
|
|
|
|
value={loginForm.username}
|
|
|
|
|
|
onChange={handleInputChange}
|
|
|
|
|
|
placeholder="请输入用户名"
|
|
|
|
|
|
required
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2025-10-28 11:30:30 +00:00
|
|
|
|
|
|
|
|
|
|
<div className="form-group password-group">
|
2025-08-05 01:44:28 +00:00
|
|
|
|
<label htmlFor="password">
|
|
|
|
|
|
<Lock size={18} />
|
|
|
|
|
|
密码
|
|
|
|
|
|
</label>
|
2025-12-26 08:59:05 +00:00
|
|
|
|
<div className="password-input-wrapper">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type={showPassword ? "text" : "password"}
|
|
|
|
|
|
id="password"
|
|
|
|
|
|
name="password"
|
|
|
|
|
|
value={loginForm.password}
|
|
|
|
|
|
onChange={handleInputChange}
|
|
|
|
|
|
placeholder="请输入密码"
|
|
|
|
|
|
required
|
|
|
|
|
|
/>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className="password-toggle-btn"
|
|
|
|
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
|
|
|
|
aria-label={showPassword ? "隐藏密码" : "显示密码"}
|
|
|
|
|
|
>
|
|
|
|
|
|
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2025-10-28 11:30:30 +00:00
|
|
|
|
<label htmlFor="rememberMe" className="remember-me-label">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
id="rememberMe"
|
|
|
|
|
|
checked={rememberMe}
|
|
|
|
|
|
onChange={(e) => setRememberMe(e.target.checked)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span>记住用户名</span>
|
|
|
|
|
|
</label>
|
2025-08-05 01:44:28 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-28 11:30:30 +00:00
|
|
|
|
<div className="error-message-container">
|
|
|
|
|
|
{loginError && (
|
|
|
|
|
|
<div className="error-message">{loginError}</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-08-05 01:44:28 +00:00
|
|
|
|
|
2025-10-28 11:30:30 +00:00
|
|
|
|
<button
|
|
|
|
|
|
type="submit"
|
2025-08-05 01:44:28 +00:00
|
|
|
|
className="submit-btn"
|
|
|
|
|
|
disabled={isLoading}
|
|
|
|
|
|
>
|
|
|
|
|
|
{isLoading ? '登录中...' : '登录'}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-30 04:13:54 +00:00
|
|
|
|
export default HomePage;
|