refactor: 优化会议预览页面的密码输入界面和样式

- 更新密码提示和占位符文本
- 重构密码输入界面布局和样式
- 添加背景动画和视觉效果
- 优化移动端显示
dev_na
chenhao 2026-05-11 20:54:51 +08:00
parent a49888587f
commit 3469884bca
2 changed files with 232 additions and 23 deletions

View File

@ -614,6 +614,189 @@
background: #eef1ff;
}
/* --- Password Gate Overhaul --- */
.is-password-gate {
justify-content: center;
align-items: center;
overflow: hidden;
position: relative;
}
.password-gate-background {
position: absolute;
inset: 0;
z-index: 0;
overflow: hidden;
}
.bg-blob {
position: absolute;
filter: blur(80px);
opacity: 0.4;
border-radius: 50%;
animation: blob-float 20s infinite alternate cubic-bezier(0.45, 0.05, 0.55, 0.95);
}
.bg-blob-1 {
width: 500px;
height: 500px;
background: #5f51ff;
top: -100px;
left: -100px;
}
.bg-blob-2 {
width: 400px;
height: 400px;
background: #6c8cff;
bottom: -50px;
right: -50px;
animation-delay: -5s;
}
.bg-blob-3 {
width: 300px;
height: 300px;
background: #8e84ff;
top: 40%;
left: 30%;
animation-delay: -10s;
}
@keyframes blob-float {
0% { transform: translate(0, 0) scale(1) rotate(0deg); }
33% { transform: translate(30px, 50px) scale(1.1) rotate(10deg); }
66% { transform: translate(-20px, 20px) scale(0.9) rotate(-10deg); }
100% { transform: translate(0, 0) scale(1) rotate(0deg); }
}
.is-password-gate .meeting-preview-shell {
width: 100%;
max-width: 480px;
padding: 24px;
z-index: 10;
margin: 0;
}
.meeting-preview-password-card {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(32px) saturate(200%);
border: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 32px;
padding: 48px 40px;
box-shadow:
0 40px 100px -20px rgba(95, 81, 255, 0.15),
0 0 0 1px rgba(95, 81, 255, 0.05);
display: flex;
flex-direction: column;
gap: 32px;
}
.password-card-header {
text-align: center;
}
.password-icon-wrapper {
width: 64px;
height: 64px;
background: var(--primary-gradient);
color: white;
font-size: 28px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 20px;
margin: 0 auto 24px;
box-shadow: 0 12px 24px rgba(95, 81, 255, 0.25);
}
.password-card-title {
font-size: 24px;
font-weight: 800;
color: var(--text-main);
margin-bottom: 12px;
letter-spacing: -0.02em;
}
.password-card-subtitle {
font-size: 15px;
color: var(--text-secondary);
line-height: 1.6;
max-width: 320px;
margin: 0 auto;
}
.password-card-form {
display: flex;
flex-direction: column;
gap: 20px;
}
.modern-password-input {
border-radius: 16px !important;
border: 2px solid rgba(228, 232, 245, 0.8) !important;
padding: 12px 16px !important;
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1) !important;
background: white !important;
}
.modern-password-input:focus,
.modern-password-input-focused {
border-color: var(--primary-blue) !important;
box-shadow: 0 0 0 4px rgba(95, 81, 255, 0.1) !important;
}
.password-submit-btn {
height: 56px !important;
border-radius: 16px !important;
font-size: 16px !important;
font-weight: 700 !important;
letter-spacing: 0.02em;
background: var(--primary-gradient) !important;
border: none !important;
box-shadow: 0 12px 24px rgba(95, 81, 255, 0.2) !important;
transition: all 0.3s ease !important;
}
.password-submit-btn:hover {
transform: translateY(-2px);
box-shadow: 0 16px 32px rgba(95, 81, 255, 0.3) !important;
}
.password-error-message {
animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
}
@keyframes shake {
10%, 90% { transform: translate3d(-1px, 0, 0); }
20%, 80% { transform: translate3d(2px, 0, 0); }
30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }
40%, 60% { transform: translate3d(4px, 0, 0); }
}
.password-gate-footer {
position: absolute;
bottom: 32px;
z-index: 10;
}
.footer-disclaimer {
display: flex;
align-items: center;
gap: 8px;
color: var(--text-secondary);
font-size: 13px;
font-weight: 600;
opacity: 0.7;
}
@media (max-width: 480px) {
.meeting-preview-password-card {
padding: 40px 24px;
border-radius: 24px;
}
}
/* --- Mobile Optimizations --- */
@media (max-width: 768px) {
.meeting-preview-transcript-list {

View File

@ -65,8 +65,8 @@ const TEXT = {
shareFailed: "分享失败,请先复制链接",
accessCheck: "访问校验",
passwordRequired: "该会议需要访问密码",
passwordHint: "请输入会议的 access_password 后继续访问预览内容。",
passwordPlaceholder: "请输入 access_password",
passwordHint: "请输入会议的 访问密码 后继续访问预览内容。",
passwordPlaceholder: "请输入 访问密码",
openPreview: "进入预览",
invalidPassword: "访问密码错误",
basicInfo: "基本信息",
@ -603,34 +603,60 @@ export default function MeetingPreview() {
if (passwordRequired && !passwordVerified) {
return (
<div className="meeting-preview-page">
<div className="meeting-preview-shell meeting-preview-empty">
<div className="meeting-preview-card meeting-preview-section meeting-preview-password-gate">
<div className="meeting-preview-section-header">
<div>
<div className="meeting-preview-section-kicker">
<div className="meeting-preview-page is-password-gate">
<div className="password-gate-background">
<div className="bg-blob bg-blob-1" />
<div className="bg-blob bg-blob-2" />
<div className="bg-blob bg-blob-3" />
</div>
<div className="meeting-preview-shell">
<div className="meeting-preview-password-card">
<div className="password-card-header">
<div className="password-icon-wrapper">
<LockOutlined />
{TEXT.accessCheck}
</div>
<h2 className="meeting-preview-section-title">{TEXT.passwordRequired}</h2>
</div>
<h1 className="password-card-title">{TEXT.passwordRequired}</h1>
<p className="password-card-subtitle">{TEXT.passwordHint}</p>
</div>
<p className="meeting-preview-subtitle">{TEXT.passwordHint}</p>
<div className="meeting-preview-password-form">
<div className="password-card-form">
<div className="password-input-wrapper">
<Input.Password
size="large"
value={accessPassword}
placeholder={TEXT.passwordPlaceholder}
onChange={(event) => setAccessPassword(event.target.value)}
onPressEnter={handlePasswordSubmit}
prefix={<LockOutlined style={{ color: "var(--text-secondary)" }} />}
className="modern-password-input"
/>
<Button type="primary" onClick={handlePasswordSubmit} loading={loading} disabled={!accessPassword.trim()}>
</div>
<Button
type="primary"
size="large"
onClick={handlePasswordSubmit}
loading={loading}
disabled={!accessPassword.trim()}
className="password-submit-btn"
block
>
{TEXT.openPreview}
</Button>
</div>
{passwordError ? <Alert type="error" showIcon message={passwordError} /> : null}
{passwordError && (
<div className="password-error-message">
<Alert type="error" showIcon message={passwordError} />
</div>
)}
</div>
</div>
<div className="password-gate-footer">
<div className="footer-disclaimer">
<RobotOutlined />
<span>Secure Access Powered by iMeeting AI</span>
</div>
</div>
</div>