- 新增「简记memo」一体化小程序产品原型设计文档 - 新增简记memo完整版UI视觉设计规范和界面细节 - 添加IDEA项目配置文件.gitignore - 创建404页面HTML文件,包含响应式布局和错误提示 - 添加关于页面HTML文件,展示品牌介绍和团队信息 - 实现AES加解密工具函数,支持请求体加密 - 添加用户协议页面基础框架
1662 lines
66 KiB
HTML
1662 lines
66 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>登录 - 简记memo</title>
|
|
<link rel="stylesheet" href="https://unpkg.com/element-ui@2.15.14/lib/theme-chalk/index.css">
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
html { scroll-behavior: smooth; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; min-height: 100vh; }
|
|
|
|
@keyframes float {
|
|
0%, 100% { transform: translateY(0); }
|
|
50% { transform: translateY(-10px); }
|
|
}
|
|
|
|
@keyframes fadeInUp {
|
|
from { opacity: 0; transform: translateY(20px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.animate-float { animation: float 3s ease-in-out infinite; }
|
|
.animate-fadeInUp { animation: fadeInUp 0.6s ease forwards; }
|
|
|
|
.delay-1 { animation-delay: 0.1s; }
|
|
.delay-2 { animation-delay: 0.2s; }
|
|
.delay-3 { animation-delay: 0.3s; }
|
|
|
|
.form-card {
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
padding: 48px;
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.08);
|
|
width: 560px;
|
|
max-width: 95%;
|
|
}
|
|
|
|
.form-input {
|
|
width: 100%;
|
|
padding: 12px 40px;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
background: #f8fafc;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.form-input:focus {
|
|
outline: none;
|
|
border-color: #4F9EDE;
|
|
box-shadow: 0 0 0 2px rgba(79, 158, 222, 0.1);
|
|
}
|
|
|
|
.btn-primary {
|
|
width: 100%;
|
|
padding: 14px;
|
|
background: linear-gradient(135deg, #4F9EDE 0%, #3A8BC9 100%);
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: #fff;
|
|
cursor: pointer;
|
|
box-shadow: 0 4px 12px rgba(79, 158, 222, 0.3);
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-primary:hover:not(:disabled) {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 6px 16px rgba(79, 158, 222, 0.4);
|
|
}
|
|
|
|
.btn-primary:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.btn-secondary {
|
|
padding: 12px 16px;
|
|
background: #f1f5f9;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
color: #64748b;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-secondary:hover:not(:disabled) {
|
|
background: #e2e8f0;
|
|
border-color: #cbd5e1;
|
|
}
|
|
|
|
.btn-secondary:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.slide-captcha {
|
|
background: #f8fafc;
|
|
border-radius: 6px;
|
|
padding: 12px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.slide-track {
|
|
width: 100%;
|
|
height: 40px;
|
|
background: #e2e8f0;
|
|
border-radius: 4px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.slide-fill {
|
|
height: 100%;
|
|
background: linear-gradient(135deg, #4F9EDE 0%, #3A8BC9 100%);
|
|
width: 0;
|
|
transition: width 0.1s linear;
|
|
}
|
|
|
|
.slide-btn {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 0;
|
|
width: 48px;
|
|
height: 48px;
|
|
margin: -24px 0 0 -4px;
|
|
background: #fff;
|
|
border-radius: 6px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
cursor: grab;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: box-shadow 0.2s;
|
|
}
|
|
|
|
.slide-btn:hover {
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
}
|
|
|
|
.slide-btn.dragging {
|
|
cursor: grabbing;
|
|
box-shadow: 0 4px 16px rgba(79, 158, 222, 0.3);
|
|
}
|
|
|
|
.slide-icon {
|
|
width: 20px;
|
|
height: 20px;
|
|
fill: none;
|
|
stroke: #4F9EDE;
|
|
stroke-width: 2;
|
|
}
|
|
|
|
.slide-text {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 0;
|
|
right: 0;
|
|
text-align: center;
|
|
transform: translateY(-50%);
|
|
font-size: 13px;
|
|
color: #64748b;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.slide-success {
|
|
color: #22c55e;
|
|
}
|
|
|
|
.slide-error {
|
|
color: #ef4444;
|
|
}
|
|
|
|
.form-switch {
|
|
text-align: center;
|
|
margin-top: 16px;
|
|
font-size: 13px;
|
|
color: #64748b;
|
|
}
|
|
|
|
.form-switch a {
|
|
color: #4F9EDE;
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.form-switch a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.checkbox-group {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.checkbox-label {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 6px;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
color: #64748b;
|
|
}
|
|
|
|
.form-checkbox {
|
|
width: 14px;
|
|
height: 14px;
|
|
cursor: pointer;
|
|
margin-top: 3px;
|
|
}
|
|
|
|
.link-text {
|
|
color: #4F9EDE;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.link-text:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.form-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #1e293b;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.form-label {
|
|
display: block;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: #475569;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.form-label-inline {
|
|
display: inline-block;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: #475569;
|
|
white-space: nowrap;
|
|
text-align: right;
|
|
}
|
|
|
|
.input-wrapper {
|
|
position: relative;
|
|
}
|
|
|
|
.input-icon {
|
|
position: absolute;
|
|
left: 14px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 18px;
|
|
height: 18px;
|
|
fill: none;
|
|
stroke: #94a3b8;
|
|
}
|
|
|
|
.error-message {
|
|
color: #ef4444;
|
|
font-size: 12px;
|
|
margin-top: 4px;
|
|
display: none;
|
|
}
|
|
|
|
.error-message.show {
|
|
display: block;
|
|
}
|
|
|
|
.login-container {
|
|
min-height: calc(100vh - 64px);
|
|
display: flex;
|
|
}
|
|
|
|
.login-left {
|
|
flex: 1;
|
|
background: linear-gradient(135deg, #4F9EDE 0%, #3A8BC9 50%, #6366F1 100%);
|
|
padding: 60px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
color: #fff;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.login-right {
|
|
flex: 1;
|
|
background: #f8fafc;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 40px;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
body {
|
|
background: linear-gradient(160deg, #e8f4fd 0%, #f0f9ff 40%, #f8fafc 100%);
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.login-container {
|
|
flex-direction: column;
|
|
min-height: calc(100vh - 56px);
|
|
padding-top: 56px;
|
|
}
|
|
|
|
.login-left {
|
|
display: none;
|
|
}
|
|
|
|
.login-right {
|
|
flex: none;
|
|
width: 100%;
|
|
min-height: calc(100vh - 56px);
|
|
padding: 24px 16px;
|
|
background: transparent;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: center;
|
|
}
|
|
|
|
.form-card {
|
|
width: 100%;
|
|
max-width: 420px;
|
|
padding: 32px 24px;
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 24px rgba(79, 158, 222, 0.1), 0 1px 4px rgba(0, 0, 0, 0.04);
|
|
background: #ffffff;
|
|
animation: slideUp 0.45s cubic-bezier(0.22, 1, 0.36, 1);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.form-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 3px;
|
|
background: linear-gradient(90deg, #4F9EDE, #60A5FA, #4F9EDE);
|
|
background-size: 200% 100%;
|
|
animation: shimmer 2.5s linear infinite;
|
|
}
|
|
|
|
@keyframes shimmer {
|
|
0% { background-position: -200% 0; }
|
|
100% { background-position: 200% 0; }
|
|
}
|
|
|
|
@keyframes slideUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(24px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.form-title {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
color: #1e293b;
|
|
margin-bottom: 28px;
|
|
text-align: center;
|
|
letter-spacing: -0.3px;
|
|
}
|
|
|
|
/* 手机端表单项间距 */
|
|
#loginForm > div[style*="margin-bottom"],
|
|
#registerForm > div[style*="margin-bottom"] {
|
|
margin-bottom: 16px !important;
|
|
}
|
|
|
|
.form-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.form-label-inline {
|
|
width: auto !important;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: #334155;
|
|
text-align: left !important;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
/* 手机端表单行改为纵向布局 */
|
|
#loginForm > div > div:first-child,
|
|
#registerForm > div > div:first-child {
|
|
flex-direction: column !important;
|
|
align-items: stretch !important;
|
|
gap: 4px !important;
|
|
}
|
|
|
|
/* 输入容器占满宽度 */
|
|
#loginForm .input-wrapper,
|
|
#registerForm .input-wrapper,
|
|
#loginForm > div > div:first-child > div[style*="display: flex"],
|
|
#registerForm > div > div:first-child > div[style*="display: flex"] {
|
|
width: 100% !important;
|
|
flex: none !important;
|
|
max-width: 100% !important;
|
|
}
|
|
|
|
/* 验证码区域内部水平排列 */
|
|
#loginForm > div > div:first-child > div[style*="display: flex"],
|
|
#registerForm > div > div:first-child > div[style*="display: flex"] {
|
|
flex-direction: row !important;
|
|
align-items: center !important;
|
|
gap: 10px !important;
|
|
}
|
|
|
|
.input-wrapper {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.form-input {
|
|
width: 100%;
|
|
padding: 13px 14px 13px 44px;
|
|
font-size: 15px;
|
|
font-weight: 400;
|
|
border-radius: 12px;
|
|
border: 1.5px solid #e2e8f0;
|
|
background: #f8fafc;
|
|
color: #1e293b;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.form-input::placeholder {
|
|
color: #94a3b8;
|
|
font-weight: 400;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.form-input:focus {
|
|
outline: none;
|
|
border-color: #4F9EDE;
|
|
background: #fff;
|
|
box-shadow: 0 0 0 3px rgba(79, 158, 222, 0.1);
|
|
}
|
|
|
|
.form-input:focus + .input-icon {
|
|
stroke: #4F9EDE;
|
|
}
|
|
|
|
.input-icon {
|
|
position: absolute;
|
|
left: 14px;
|
|
width: 18px;
|
|
height: 18px;
|
|
stroke: #94a3b8;
|
|
transition: stroke 0.2s ease;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.captcha-wrapper {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.captcha-wrapper .form-input {
|
|
flex: 1;
|
|
padding-right: 14px;
|
|
}
|
|
|
|
.captcha-img {
|
|
width: 100px;
|
|
height: 44px;
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
border: 1.5px solid #e2e8f0;
|
|
transition: all 0.2s ease;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.captcha-img:hover {
|
|
border-color: #4F9EDE;
|
|
}
|
|
|
|
.btn-primary {
|
|
width: 100%;
|
|
padding: 14px;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
border-radius: 12px;
|
|
border: none;
|
|
background: linear-gradient(135deg, #4F9EDE 0%, #3A8BC9 100%);
|
|
color: #fff;
|
|
box-shadow: 0 4px 14px rgba(79, 158, 222, 0.3);
|
|
transition: all 0.2s ease;
|
|
cursor: pointer;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.btn-primary:hover:not(:disabled),
|
|
.btn-primary:active:not(:disabled) {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 6px 20px rgba(79, 158, 222, 0.4);
|
|
}
|
|
|
|
.btn-primary:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.btn-secondary {
|
|
padding: 12px 16px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
width: 110px !important;
|
|
border-radius: 10px;
|
|
background: #f1f5f9;
|
|
border: 1.5px solid #e2e8f0;
|
|
color: #475569;
|
|
transition: all 0.2s ease;
|
|
cursor: pointer;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.btn-secondary:hover:not(:disabled) {
|
|
background: #e2e8f0;
|
|
border-color: #cbd5e1;
|
|
}
|
|
|
|
.btn-secondary:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.code-input-wrapper {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.code-input-wrapper .form-input {
|
|
flex: 1;
|
|
}
|
|
|
|
#loginForm, #registerForm {
|
|
padding: 0;
|
|
}
|
|
|
|
.checkbox-group {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 8px;
|
|
margin-bottom: 20px;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.checkbox-group input[type="checkbox"] {
|
|
width: 16px;
|
|
height: 16px;
|
|
border-radius: 5px;
|
|
border: 1.5px solid #cbd5e1;
|
|
accent-color: #4F9EDE;
|
|
cursor: pointer;
|
|
margin-top: 2px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.checkbox-label {
|
|
font-size: 12px;
|
|
color: #64748b;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.checkbox-label a {
|
|
color: #4F9EDE;
|
|
font-weight: 500;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.checkbox-label a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.form-switch {
|
|
margin-top: 20px;
|
|
text-align: center;
|
|
font-size: 13px;
|
|
color: #64748b;
|
|
}
|
|
|
|
.form-switch a {
|
|
color: #4F9EDE;
|
|
font-weight: 600;
|
|
text-decoration: none;
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.form-switch a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.error-message {
|
|
font-size: 12px;
|
|
color: #ef4444;
|
|
margin-top: 4px;
|
|
padding-left: 2px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.login-right {
|
|
padding: 12px 16px;
|
|
align-items: center;
|
|
}
|
|
|
|
.form-card {
|
|
padding: 28px 20px;
|
|
border-radius: 20px;
|
|
box-shadow: 0 8px 32px rgba(79, 158, 222, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
}
|
|
|
|
.form-card::before {
|
|
height: 4px;
|
|
}
|
|
|
|
.form-title {
|
|
font-size: 22px;
|
|
margin-bottom: 28px;
|
|
letter-spacing: -0.5px;
|
|
}
|
|
|
|
/* 小屏幕表单项间距 */
|
|
#loginForm > div[style*="margin-bottom"],
|
|
#registerForm > div[style*="margin-bottom"] {
|
|
margin-bottom: 18px !important;
|
|
}
|
|
|
|
.form-input {
|
|
padding: 14px 14px 14px 44px;
|
|
font-size: 15px;
|
|
border-radius: 12px;
|
|
border: 1.5px solid #e2e8f0;
|
|
}
|
|
|
|
.form-input::placeholder {
|
|
font-size: 14px;
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.input-icon {
|
|
left: 14px;
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
|
|
.btn-primary {
|
|
padding: 15px;
|
|
font-size: 15px;
|
|
border-radius: 12px;
|
|
font-weight: 600;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.btn-secondary {
|
|
width: 110px !important;
|
|
font-size: 13px;
|
|
padding: 13px 12px;
|
|
border-radius: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.captcha-img {
|
|
width: 100px;
|
|
height: 46px;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.checkbox-label {
|
|
font-size: 12px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.form-switch {
|
|
font-size: 13px;
|
|
margin-top: 20px;
|
|
padding: 12px 0;
|
|
}
|
|
|
|
.form-label-inline {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: #334155;
|
|
}
|
|
|
|
.error-message {
|
|
font-size: 12px;
|
|
margin-top: 6px;
|
|
padding-left: 4px;
|
|
}
|
|
|
|
/* 优化验证码区域 */
|
|
#loginForm > div > div:first-child > div[style*="display: flex"],
|
|
#registerForm > div > div:first-child > div[style*="display: flex"] {
|
|
gap: 12px !important;
|
|
}
|
|
|
|
/* 优化注册表单标签宽度 */
|
|
#registerForm .form-label-inline {
|
|
min-width: 70px;
|
|
}
|
|
|
|
/* 优化复选框 */
|
|
.checkbox-group input[type="checkbox"] {
|
|
width: 18px;
|
|
height: 18px;
|
|
margin-top: 1px;
|
|
}
|
|
}
|
|
|
|
/* 超小屏幕优化 */
|
|
@media (max-width: 360px) {
|
|
.login-right {
|
|
padding: 10px 12px;
|
|
}
|
|
|
|
.form-card {
|
|
padding: 24px 16px;
|
|
border-radius: 16px;
|
|
}
|
|
|
|
.form-title {
|
|
font-size: 20px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.form-input {
|
|
padding: 12px 12px 12px 40px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.btn-primary {
|
|
padding: 13px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.captcha-img {
|
|
width: 90px;
|
|
height: 42px;
|
|
}
|
|
|
|
.btn-secondary {
|
|
width: 100px !important;
|
|
font-size: 12px;
|
|
padding: 12px 10px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<!-- 公共头部 -->
|
|
<div id="commonHeader"></div>
|
|
|
|
<!-- 登录主区域 -->
|
|
<div class="login-container">
|
|
<!-- 左侧展示区 -->
|
|
<div class="login-left">
|
|
<div style="position: absolute; top: 20px; right: 20px; width: 300px; height: 300px; background: rgba(255,255,255,0.05); border-radius: 50%;"></div>
|
|
<div style="position: absolute; bottom: 20px; left: 20px; width: 200px; height: 200px; background: rgba(255,255,255,0.05); border-radius: 50%;"></div>
|
|
|
|
<div style="position: relative; z-index: 1; max-width: 500px;">
|
|
<div class="animate-fadeInUp" style="margin-bottom: 24px;">
|
|
<div style="width: 80px; height: 80px; background: rgba(255,255,255,0.15); backdrop-filter: blur(10px); border-radius: 20px; display: flex; align-items: center; justify-content: center; margin-bottom: 20px; box-shadow: 0 8px 32px rgba(0,0,0,0.1);">
|
|
<img src="/static/images/logo.png" alt="简记memo" style="width: 48px; height: 48px; border-radius: 8px;" onerror="this.style.display='none'; this.parentElement.innerHTML='<svg style=\'width: 40px; height: 40px; fill: #fff;\' viewBox=\'0 0 24 24\'><path d=\'M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5\'/></svg>';">
|
|
</div>
|
|
<h1 style="font-size: 36px; font-weight: 700; margin-bottom: 12px;">简记memo</h1>
|
|
<p style="font-size: 16px; opacity: 0.9;">让记录更简单</p>
|
|
</div>
|
|
|
|
<div class="animate-fadeInUp delay-1" style="margin-bottom: 32px;">
|
|
<h2 style="font-size: 24px; font-weight: 600; margin-bottom: 16px;">为什么选择简记?</h2>
|
|
<ul style="list-style: none; display: flex; flex-direction: column; gap: 12px;">
|
|
<li style="display: flex; align-items: center; gap: 12px;">
|
|
<div style="width: 28px; height: 28px; background: rgba(255,255,255,0.15); border-radius: 8px; display: flex; align-items: center; justify-content: center;">✅</div>
|
|
<span style="font-size: 14px;">简洁优雅的界面设计</span>
|
|
</li>
|
|
<li style="display: flex; align-items: center; gap: 12px;">
|
|
<div style="width: 28px; height: 28px; background: rgba(255,255,255,0.15); border-radius: 8px; display: flex; align-items: center; justify-content: center;">📅</div>
|
|
<span style="font-size: 14px;">日历视图一目了然</span>
|
|
</li>
|
|
<li style="display: flex; align-items: center; gap: 12px;">
|
|
<div style="width: 28px; height: 28px; background: rgba(255,255,255,0.15); border-radius: 8px; display: flex; align-items: center; justify-content: center;">😊</div>
|
|
<span style="font-size: 14px;">记录每一天的心情</span>
|
|
</li>
|
|
<li style="display: flex; align-items: center; gap: 12px;">
|
|
<div style="width: 28px; height: 28px; background: rgba(255,255,255,0.15); border-radius: 8px; display: flex; align-items: center; justify-content: center;">💰</div>
|
|
<span style="font-size: 14px;">轻松管理收支账单</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="animate-fadeInUp delay-2">
|
|
<div style="display: flex; align-items: center; gap: 16px;">
|
|
<div class="animate-float" style="width: 48px; height: 48px; background: rgba(255,255,255,0.15); border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 24px;">📋</div>
|
|
<div class="animate-float" style="width: 48px; height: 48px; background: rgba(255,255,255,0.15); border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 24px; animation-delay: 0.5s;">😊</div>
|
|
<div class="animate-float" style="width: 48px; height: 48px; background: rgba(255,255,255,0.15); border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 24px; animation-delay: 1s;">💰</div>
|
|
<div class="animate-float" style="width: 48px; height: 48px; background: rgba(255,255,255,0.15); border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 24px; animation-delay: 1.5s;">🏆</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 右侧登录区 -->
|
|
<div class="login-right">
|
|
<div style="width: 100%; max-width: 580px;">
|
|
<!-- 登录表单 -->
|
|
<div id="loginForm" class="form-card animate-fadeInUp">
|
|
<h2 class="form-title">欢迎回来</h2>
|
|
|
|
<div style="margin-bottom: 16px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<label class="form-label-inline" style="width: 60px;">邮箱</label>
|
|
<div class="input-wrapper" style="flex: 1;">
|
|
<input type="email" id="loginEmail" placeholder="请输入邮箱地址" class="form-input">
|
|
<svg class="input-icon" viewBox="0 0 24 24">
|
|
<path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div id="loginEmailError" class="error-message"></div>
|
|
</div>
|
|
|
|
<div style="margin-bottom: 12px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<label class="form-label-inline" style="width: 60px;">密码</label>
|
|
<div class="input-wrapper" style="flex: 1;">
|
|
<input type="password" id="loginPassword" placeholder="请输入密码" class="form-input">
|
|
<svg class="input-icon" viewBox="0 0 24 24">
|
|
<path d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div id="loginPasswordError" class="error-message"></div>
|
|
</div>
|
|
|
|
<!-- 数字验证码 -->
|
|
<div style="margin-bottom: 16px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<label class="form-label-inline" style="width: 60px;">验证码</label>
|
|
<div style="flex: 1; display: flex; gap: 10px;">
|
|
<input type="text" id="loginCaptchaInput" placeholder="请输入验证码" class="form-input" style="flex: 1; min-width: 0;">
|
|
<img id="loginCaptchaImg" src="" alt="验证码" style="width: 90px; height: 40px; border-radius: 6px; cursor: pointer; flex-shrink: 0;" onclick="refreshLoginCaptcha()">
|
|
</div>
|
|
</div>
|
|
<div id="loginCaptchaError" class="error-message"></div>
|
|
</div>
|
|
|
|
<button id="loginBtn" class="btn-primary" disabled>登录</button>
|
|
|
|
<p class="form-switch">
|
|
还没有账号?
|
|
<a href="javascript:void(0)" onclick="switchToRegister()">立即注册</a>
|
|
<span style="margin: 0 8px; color: #e2e8f0;">|</span>
|
|
<a href="javascript:void(0)" onclick="switchToForgot()" style="color: #64748b;">忘记密码</a>
|
|
</p>
|
|
</div>
|
|
|
|
<!-- 注册表单 -->
|
|
<div id="registerForm" class="form-card animate-fadeInUp" style="display: none;">
|
|
<h2 class="form-title">创建账号</h2>
|
|
|
|
<div style="margin-bottom: 16px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<label class="form-label-inline" style="width: 70px;">邮箱</label>
|
|
<div class="input-wrapper" style="flex: 1;">
|
|
<input type="email" id="registerEmail" placeholder="请输入邮箱地址" class="form-input">
|
|
<svg class="input-icon" viewBox="0 0 24 24">
|
|
<path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div id="registerEmailError" class="error-message"></div>
|
|
</div>
|
|
|
|
<div style="margin-bottom: 12px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<label class="form-label-inline" style="width: 70px;">密码</label>
|
|
<div class="input-wrapper" style="flex: 1;">
|
|
<input type="password" id="registerPassword" placeholder="请输入密码(至少6位)" class="form-input">
|
|
<svg class="input-icon" viewBox="0 0 24 24">
|
|
<path d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div id="registerPasswordError" class="error-message"></div>
|
|
</div>
|
|
|
|
<div style="margin-bottom: 12px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<label class="form-label-inline" style="width: 70px;">确认密码</label>
|
|
<div class="input-wrapper" style="flex: 1;">
|
|
<input type="password" id="registerConfirmPassword" placeholder="请再次输入密码" class="form-input">
|
|
<svg class="input-icon" viewBox="0 0 24 24">
|
|
<path d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div id="registerConfirmPasswordError" class="error-message"></div>
|
|
</div>
|
|
|
|
<!-- 图形验证码 -->
|
|
<div style="margin-bottom: 16px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<label class="form-label-inline" style="width: 70px;">图形验证码</label>
|
|
<div style="flex: 1; display: flex; gap: 12px;">
|
|
<input type="text" id="registerCaptchaInput" placeholder="请输入验证码" class="form-input" style="flex: 2; min-width: 100px;">
|
|
<img id="registerCaptchaImg" src="" alt="验证码" style="width: 115px; height: 40px; border-radius: 6px; cursor: pointer; flex-shrink: 0; object-fit: cover;" onclick="refreshRegisterCaptcha()">
|
|
</div>
|
|
</div>
|
|
<div id="registerCaptchaError" class="error-message"></div>
|
|
</div>
|
|
|
|
<!-- 邮箱验证码 -->
|
|
<div style="margin-bottom: 16px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<label class="form-label-inline" style="width: 70px;">邮箱验证码</label>
|
|
<div style="flex: 1; display: flex; gap: 12px;">
|
|
<input type="text" id="registerCode" placeholder="请输入邮箱验证码" class="form-input" style="flex: 2; min-width: 100px;">
|
|
<button id="sendRegisterCodeBtn" class="btn-secondary" disabled style="flex-shrink: 0; width: 115px;">发送验证码</button>
|
|
</div>
|
|
</div>
|
|
<div id="registerCodeError" class="error-message"></div>
|
|
</div>
|
|
|
|
<div class="checkbox-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="agreeTerms" class="form-checkbox">
|
|
<span class="checkbox-text">
|
|
我已阅读并同意
|
|
<a href="/agreement" class="link-text">用户协议</a>
|
|
和
|
|
<a href="/privacy" class="link-text">隐私政策</a>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
<button id="registerBtn" class="btn-primary" disabled>注册</button>
|
|
|
|
<p class="form-switch">
|
|
已有账号?
|
|
<a href="javascript:void(0)" onclick="switchToLogin()">立即登录</a>
|
|
</p>
|
|
</div>
|
|
|
|
<!-- 找回密码表单 -->
|
|
<div id="forgotForm" class="form-card animate-fadeInUp" style="display: none;">
|
|
<h2 class="form-title">找回密码</h2>
|
|
|
|
<div style="margin-bottom: 16px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<label class="form-label-inline" style="width: 70px;">邮箱</label>
|
|
<div class="input-wrapper" style="flex: 1;">
|
|
<input type="email" id="forgotEmail" placeholder="请输入注册邮箱" class="form-input">
|
|
<svg class="input-icon" viewBox="0 0 24 24">
|
|
<path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div id="forgotEmailError" class="error-message"></div>
|
|
</div>
|
|
|
|
<div style="margin-bottom: 12px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<label class="form-label-inline" style="width: 70px;">新密码</label>
|
|
<div class="input-wrapper" style="flex: 1;">
|
|
<input type="password" id="forgotPassword" placeholder="请输入新密码(至少6位)" class="form-input">
|
|
<svg class="input-icon" viewBox="0 0 24 24">
|
|
<path d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div id="forgotPasswordError" class="error-message"></div>
|
|
</div>
|
|
|
|
<div style="margin-bottom: 12px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<label class="form-label-inline" style="width: 70px;">确认密码</label>
|
|
<div class="input-wrapper" style="flex: 1;">
|
|
<input type="password" id="forgotConfirmPassword" placeholder="请再次输入新密码" class="form-input">
|
|
<svg class="input-icon" viewBox="0 0 24 24">
|
|
<path d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div id="forgotConfirmPasswordError" class="error-message"></div>
|
|
</div>
|
|
|
|
<!-- 图形验证码 -->
|
|
<div style="margin-bottom: 16px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<label class="form-label-inline" style="width: 70px;">图形验证码</label>
|
|
<div style="flex: 1; display: flex; gap: 12px;">
|
|
<input type="text" id="forgotCaptchaInput" placeholder="请输入验证码" class="form-input" style="flex: 2; min-width: 100px;">
|
|
<img id="forgotCaptchaImg" src="" alt="验证码" style="width: 115px; height: 40px; border-radius: 6px; cursor: pointer; flex-shrink: 0; object-fit: cover;" onclick="refreshForgotCaptcha()">
|
|
</div>
|
|
</div>
|
|
<div id="forgotCaptchaError" class="error-message"></div>
|
|
</div>
|
|
|
|
<!-- 邮箱验证码 -->
|
|
<div style="margin-bottom: 16px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<label class="form-label-inline" style="width: 70px;">邮箱验证码</label>
|
|
<div style="flex: 1; display: flex; gap: 12px;">
|
|
<input type="text" id="forgotCode" placeholder="请输入邮箱验证码" class="form-input" style="flex: 2; min-width: 100px;">
|
|
<button id="sendForgotCodeBtn" class="btn-secondary" disabled style="flex-shrink: 0; width: 115px;">发送验证码</button>
|
|
</div>
|
|
</div>
|
|
<div id="forgotCodeError" class="error-message"></div>
|
|
</div>
|
|
|
|
<button id="forgotBtn" class="btn-primary" disabled>重置密码</button>
|
|
|
|
<p class="form-switch">
|
|
想起密码了?
|
|
<a href="javascript:void(0)" onclick="switchToLogin()">立即登录</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 公共底部 -->
|
|
<div id="commonFooter"></div>
|
|
</div>
|
|
|
|
<script src="https://unpkg.com/vue@2.6.14/dist/vue.min.js"></script>
|
|
<script src="https://unpkg.com/element-ui@2.15.14/lib/index.js"></script>
|
|
<script src="/static/js/config.js"></script>
|
|
<script src="/static/js/common-components.js"></script>
|
|
<script src="/static/js/toast.js"></script>
|
|
<script>
|
|
let loginCaptchaValid = false;
|
|
let registerCaptchaValid = false;
|
|
let forgotCaptchaValid = false;
|
|
|
|
Vue.use(ELEMENT);
|
|
|
|
const app = new Vue({
|
|
el: '#app'
|
|
});
|
|
|
|
function showToast(message, type = 'success') {
|
|
if (typeof app.$message !== 'undefined') {
|
|
app.$message({
|
|
type: type,
|
|
message: message
|
|
});
|
|
} else {
|
|
$toast.error(message);
|
|
}
|
|
}
|
|
|
|
function checkLoginStatus() {
|
|
const userInfo = UserStore.get();
|
|
const token = userInfo && userInfo.token ? userInfo.token : localStorage.getItem('user_token');
|
|
if (token) {
|
|
window.location.href = '/web/calendar';
|
|
}
|
|
}
|
|
|
|
checkLoginStatus();
|
|
|
|
let loginCaptchaId = '';
|
|
let registerCaptchaId = '';
|
|
let forgotCaptchaId = '';
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
document.getElementById('commonHeader').innerHTML = renderCommonHeader('/login');
|
|
document.getElementById('commonFooter').innerHTML = renderCommonFooter();
|
|
initCommonComponents();
|
|
|
|
loadLoginCaptcha();
|
|
loadRegisterCaptcha();
|
|
loadForgotCaptcha();
|
|
|
|
document.getElementById('loginBtn').addEventListener('click', handleLogin);
|
|
document.getElementById('registerBtn').addEventListener('click', handleRegister);
|
|
document.getElementById('forgotBtn').addEventListener('click', handleForgot);
|
|
document.getElementById('sendRegisterCodeBtn').addEventListener('click', sendRegisterCode);
|
|
document.getElementById('sendForgotCodeBtn').addEventListener('click', sendForgotCode);
|
|
|
|
document.getElementById('loginCaptchaInput').addEventListener('input', validateLoginForm);
|
|
document.getElementById('registerEmail').addEventListener('input', validateRegisterCaptcha);
|
|
document.getElementById('registerCaptchaInput').addEventListener('input', validateRegisterCaptcha);
|
|
document.getElementById('registerPassword').addEventListener('input', clearPasswordError);
|
|
document.getElementById('registerConfirmPassword').addEventListener('input', clearConfirmPasswordError);
|
|
document.getElementById('registerCaptchaInput').addEventListener('input', clearCaptchaError);
|
|
document.getElementById('registerEmail').addEventListener('input', validateRegisterForm);
|
|
document.getElementById('registerPassword').addEventListener('input', validateRegisterForm);
|
|
document.getElementById('registerConfirmPassword').addEventListener('input', validateRegisterForm);
|
|
document.getElementById('registerCaptchaInput').addEventListener('input', validateRegisterForm);
|
|
document.getElementById('registerCode').addEventListener('input', validateRegisterForm);
|
|
|
|
// 找回密码表单事件
|
|
document.getElementById('forgotEmail').addEventListener('input', validateForgotCaptcha);
|
|
document.getElementById('forgotCaptchaInput').addEventListener('input', validateForgotCaptcha);
|
|
document.getElementById('forgotPassword').addEventListener('input', validateForgotForm);
|
|
document.getElementById('forgotConfirmPassword').addEventListener('input', validateForgotForm);
|
|
document.getElementById('forgotCaptchaInput').addEventListener('input', validateForgotForm);
|
|
document.getElementById('forgotCode').addEventListener('input', validateForgotForm);
|
|
});
|
|
|
|
function loadLoginCaptcha() {
|
|
fetch('/api/web/captcha/digit', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.code === 200) {
|
|
loginCaptchaId = data.data.captchaId;
|
|
document.getElementById('loginCaptchaImg').src = data.data.image;
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadRegisterCaptcha() {
|
|
fetch('/api/web/captcha/digit', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.code === 200) {
|
|
registerCaptchaId = data.data.captchaId;
|
|
document.getElementById('registerCaptchaImg').src = data.data.image;
|
|
}
|
|
});
|
|
}
|
|
|
|
function refreshLoginCaptcha() {
|
|
loadLoginCaptcha();
|
|
document.getElementById('loginCaptchaInput').value = '';
|
|
}
|
|
|
|
function refreshRegisterCaptcha() {
|
|
loadRegisterCaptcha();
|
|
document.getElementById('registerCaptchaInput').value = '';
|
|
}
|
|
|
|
function validateLoginForm() {
|
|
const email = document.getElementById('loginEmail').value;
|
|
const password = document.getElementById('loginPassword').value;
|
|
const captcha = document.getElementById('loginCaptchaInput').value;
|
|
const btn = document.getElementById('loginBtn');
|
|
|
|
btn.disabled = !(email && password && captcha && captcha.length >= 4);
|
|
}
|
|
|
|
function validateRegisterForm() {
|
|
const email = document.getElementById('registerEmail').value;
|
|
const password = document.getElementById('registerPassword').value;
|
|
const confirmPassword = document.getElementById('registerConfirmPassword').value;
|
|
const code = document.getElementById('registerCode').value;
|
|
const btn = document.getElementById('registerBtn');
|
|
|
|
// 注:图形验证码在发送邮箱验证码时已验证,此处不再校验
|
|
btn.disabled = !(email && password && confirmPassword && password === confirmPassword && code);
|
|
}
|
|
|
|
function switchToRegister() {
|
|
document.getElementById('loginForm').style.display = 'none';
|
|
document.getElementById('forgotForm').style.display = 'none';
|
|
document.getElementById('registerForm').style.display = 'block';
|
|
resetRegisterCaptcha();
|
|
}
|
|
|
|
function switchToLogin() {
|
|
document.getElementById('registerForm').style.display = 'none';
|
|
document.getElementById('forgotForm').style.display = 'none';
|
|
document.getElementById('loginForm').style.display = 'block';
|
|
resetLoginCaptcha();
|
|
}
|
|
|
|
function switchToForgot() {
|
|
document.getElementById('loginForm').style.display = 'none';
|
|
document.getElementById('registerForm').style.display = 'none';
|
|
document.getElementById('forgotForm').style.display = 'block';
|
|
resetForgotCaptcha();
|
|
}
|
|
|
|
function resetLoginCaptcha() {
|
|
loginCaptchaValid = false;
|
|
document.getElementById('loginBtn').disabled = true;
|
|
document.getElementById('loginCaptchaInput').value = '';
|
|
loadLoginCaptcha();
|
|
}
|
|
|
|
function refreshRegisterCaptcha() {
|
|
loadRegisterCaptcha();
|
|
}
|
|
|
|
function resetRegisterCaptcha() {
|
|
registerCaptchaValid = false;
|
|
document.getElementById('sendRegisterCodeBtn').disabled = true;
|
|
document.getElementById('registerCaptchaInput').value = '';
|
|
document.getElementById('registerCode').value = '';
|
|
document.getElementById('registerCode').disabled = true;
|
|
loadRegisterCaptcha();
|
|
}
|
|
|
|
function validateRegisterCaptcha() {
|
|
const captcha = document.getElementById('registerCaptchaInput').value;
|
|
const email = document.getElementById('registerEmail').value;
|
|
|
|
// 清除邮箱错误提示(当用户修改邮箱时)
|
|
document.getElementById('registerEmailError').textContent = '';
|
|
document.getElementById('registerEmailError').classList.remove('show');
|
|
|
|
if (captcha && captcha.length >= 4 && email) {
|
|
document.getElementById('sendRegisterCodeBtn').disabled = false;
|
|
} else {
|
|
document.getElementById('sendRegisterCodeBtn').disabled = true;
|
|
}
|
|
}
|
|
|
|
function sendRegisterCode() {
|
|
const email = document.getElementById('registerEmail').value;
|
|
const captcha = document.getElementById('registerCaptchaInput').value;
|
|
|
|
clearErrors(['registerEmailError', 'registerCaptchaError', 'registerCodeError']);
|
|
|
|
if (!email) {
|
|
showError('registerEmailError', '请输入邮箱地址');
|
|
return;
|
|
}
|
|
|
|
if (!captcha || captcha.length < 4) {
|
|
showError('registerCaptchaError', '请输入图形验证码');
|
|
return;
|
|
}
|
|
|
|
const btn = document.getElementById('sendRegisterCodeBtn');
|
|
btn.disabled = true;
|
|
|
|
fetch('/api/web/send-code', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
email: email,
|
|
type: 'register',
|
|
captchaId: registerCaptchaId,
|
|
captcha: captcha
|
|
})
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.code === 200) {
|
|
showToast('验证码已发送到您的邮箱', 'success');
|
|
document.getElementById('registerCode').disabled = false;
|
|
startCountdown(btn);
|
|
} else {
|
|
if (data.msg.includes('验证码') || data.msg.includes('captcha')) {
|
|
showError('registerCaptchaError', data.msg);
|
|
refreshRegisterCaptcha();
|
|
} else if (data.msg.includes('邮箱') || data.msg.includes('email')) {
|
|
showError('registerEmailError', data.msg);
|
|
} else {
|
|
showToast(data.msg, 'error');
|
|
}
|
|
btn.disabled = false;
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('发送验证码失败:', err);
|
|
showToast('发送验证码失败,请稍后重试', 'error');
|
|
btn.disabled = false;
|
|
});
|
|
}
|
|
|
|
function startCountdown(btn) {
|
|
let seconds = 60;
|
|
btn.textContent = `${seconds}秒后重发`;
|
|
|
|
const timer = setInterval(() => {
|
|
seconds--;
|
|
if (seconds <= 0) {
|
|
clearInterval(timer);
|
|
btn.textContent = '发送验证码';
|
|
btn.disabled = false;
|
|
} else {
|
|
btn.textContent = `${seconds}秒后重发`;
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
function handleLogin() {
|
|
const email = document.getElementById('loginEmail').value;
|
|
const password = document.getElementById('loginPassword').value;
|
|
const captcha = document.getElementById('loginCaptchaInput').value;
|
|
|
|
clearErrors(['loginEmailError', 'loginPasswordError', 'loginCaptchaError']);
|
|
|
|
if (!email) {
|
|
showError('loginEmailError', '请输入邮箱地址');
|
|
return;
|
|
}
|
|
|
|
if (!password) {
|
|
showError('loginPasswordError', '请输入密码');
|
|
return;
|
|
}
|
|
|
|
if (!captcha || captcha.length < 4) {
|
|
showError('loginCaptchaError', '请输入验证码');
|
|
return;
|
|
}
|
|
|
|
fetch('/api/web/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
email,
|
|
password,
|
|
captchaId: loginCaptchaId,
|
|
captcha: captcha
|
|
})
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.code === 200) {
|
|
const avatar = data.data.avatar || '/static/avatar/default.png';
|
|
localStorage.setItem('user_token', data.data.token);
|
|
localStorage.setItem('user_nickname', data.data.nickname || '用户');
|
|
localStorage.setItem('user_avatar', avatar);
|
|
localStorage.setItem('user_email', data.data.email || '');
|
|
// 存储token过期时间
|
|
if (data.data.expiresAt) {
|
|
localStorage.setItem('user_token_expires', data.data.expiresAt);
|
|
}
|
|
// 同步到 UserStore,供头部组件判断登录状态
|
|
UserStore.set({ token: data.data.token, nickname: data.data.nickname || '用户', avatar: avatar, email: data.data.email || '' });
|
|
window.location.href = '/web/calendar';
|
|
} else {
|
|
showError('loginCaptchaError', data.msg);
|
|
refreshLoginCaptcha();
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('登录失败:', err);
|
|
showToast('登录失败,请稍后重试', 'error');
|
|
refreshLoginCaptcha();
|
|
});
|
|
}
|
|
|
|
function handleRegister() {
|
|
const email = document.getElementById('registerEmail').value;
|
|
const password = document.getElementById('registerPassword').value;
|
|
const confirmPassword = document.getElementById('registerConfirmPassword').value;
|
|
const code = document.getElementById('registerCode').value;
|
|
const agreeTerms = document.getElementById('agreeTerms').checked;
|
|
|
|
clearErrors(['registerEmailError', 'registerPasswordError', 'registerConfirmPasswordError', 'registerCaptchaError']);
|
|
|
|
if (!email) {
|
|
showError('registerEmailError', '请输入邮箱地址');
|
|
return;
|
|
}
|
|
|
|
if (!password) {
|
|
showError('registerPasswordError', '请输入密码');
|
|
return;
|
|
}
|
|
|
|
if (password.length < 6) {
|
|
showError('registerPasswordError', '密码至少需要6位');
|
|
return;
|
|
}
|
|
|
|
if (!confirmPassword) {
|
|
showError('registerConfirmPasswordError', '请输入确认密码');
|
|
return;
|
|
}
|
|
|
|
if (password !== confirmPassword) {
|
|
showError('registerConfirmPasswordError', '两次输入的密码不一致');
|
|
return;
|
|
}
|
|
|
|
if (!code) {
|
|
showError('registerCodeError', '请输入邮箱验证码');
|
|
return;
|
|
}
|
|
|
|
if (!agreeTerms) {
|
|
showToast('请先同意用户协议和隐私政策', 'warning');
|
|
return;
|
|
}
|
|
|
|
// 注:图形验证码在发送邮箱验证码时已验证,此处不再发送
|
|
fetch('/api/web/register', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
email,
|
|
password,
|
|
code
|
|
})
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.code === 200) {
|
|
const avatar = data.data.avatar || '/static/avatar/default.png';
|
|
localStorage.setItem('user_token', data.data.token);
|
|
localStorage.setItem('user_nickname', data.data.nickname || '用户');
|
|
localStorage.setItem('user_avatar', avatar);
|
|
localStorage.setItem('user_email', data.data.email || '');
|
|
// 存储token过期时间
|
|
if (data.data.expiresAt) {
|
|
localStorage.setItem('user_token_expires', data.data.expiresAt);
|
|
}
|
|
// 同步到 UserStore,供头部组件判断登录状态
|
|
UserStore.set({ token: data.data.token, nickname: data.data.nickname || '用户', avatar: avatar, email: data.data.email || '' });
|
|
window.location.href = '/web/calendar';
|
|
} else {
|
|
showError('registerCaptchaError', data.msg);
|
|
refreshRegisterCaptcha();
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('注册失败:', err);
|
|
showToast('注册失败,请稍后重试', 'error');
|
|
refreshRegisterCaptcha();
|
|
});
|
|
}
|
|
|
|
function showError(elementId, message) {
|
|
const element = document.getElementById(elementId);
|
|
element.textContent = message;
|
|
element.classList.add('show');
|
|
}
|
|
|
|
function clearErrors(elementIds) {
|
|
elementIds.forEach(id => {
|
|
const element = document.getElementById(id);
|
|
element.textContent = '';
|
|
element.classList.remove('show');
|
|
});
|
|
}
|
|
|
|
function clearPasswordError() {
|
|
document.getElementById('registerPasswordError').textContent = '';
|
|
document.getElementById('registerPasswordError').classList.remove('show');
|
|
}
|
|
|
|
function clearConfirmPasswordError() {
|
|
document.getElementById('registerConfirmPasswordError').textContent = '';
|
|
document.getElementById('registerConfirmPasswordError').classList.remove('show');
|
|
}
|
|
|
|
function clearCaptchaError() {
|
|
document.getElementById('registerCaptchaError').textContent = '';
|
|
document.getElementById('registerCaptchaError').classList.remove('show');
|
|
}
|
|
|
|
// ==================== 找回密码相关函数 ====================
|
|
function loadForgotCaptcha() {
|
|
fetch('/api/web/captcha/digit', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.code === 200) {
|
|
forgotCaptchaId = data.data.captchaId;
|
|
document.getElementById('forgotCaptchaImg').src = data.data.image;
|
|
}
|
|
});
|
|
}
|
|
|
|
function refreshForgotCaptcha() {
|
|
loadForgotCaptcha();
|
|
document.getElementById('forgotCaptchaInput').value = '';
|
|
}
|
|
|
|
function resetForgotCaptcha() {
|
|
forgotCaptchaValid = false;
|
|
document.getElementById('sendForgotCodeBtn').disabled = true;
|
|
document.getElementById('forgotCaptchaInput').value = '';
|
|
document.getElementById('forgotCode').value = '';
|
|
document.getElementById('forgotCode').disabled = true;
|
|
loadForgotCaptcha();
|
|
}
|
|
|
|
function validateForgotCaptcha() {
|
|
const captcha = document.getElementById('forgotCaptchaInput').value;
|
|
const email = document.getElementById('forgotEmail').value;
|
|
|
|
document.getElementById('forgotEmailError').textContent = '';
|
|
document.getElementById('forgotEmailError').classList.remove('show');
|
|
|
|
if (captcha && captcha.length >= 4 && email) {
|
|
document.getElementById('sendForgotCodeBtn').disabled = false;
|
|
} else {
|
|
document.getElementById('sendForgotCodeBtn').disabled = true;
|
|
}
|
|
}
|
|
|
|
function validateForgotForm() {
|
|
const email = document.getElementById('forgotEmail').value;
|
|
const password = document.getElementById('forgotPassword').value;
|
|
const confirmPassword = document.getElementById('forgotConfirmPassword').value;
|
|
const code = document.getElementById('forgotCode').value;
|
|
const captcha = document.getElementById('forgotCaptchaInput').value;
|
|
const btn = document.getElementById('forgotBtn');
|
|
|
|
btn.disabled = !(email && password && confirmPassword && password === confirmPassword && code && captcha && captcha.length >= 4);
|
|
}
|
|
|
|
function sendForgotCode() {
|
|
const email = document.getElementById('forgotEmail').value;
|
|
const captcha = document.getElementById('forgotCaptchaInput').value;
|
|
|
|
clearErrors(['forgotEmailError', 'forgotCaptchaError', 'forgotCodeError']);
|
|
|
|
if (!email) {
|
|
showError('forgotEmailError', '请输入邮箱地址');
|
|
return;
|
|
}
|
|
|
|
if (!captcha || captcha.length < 4) {
|
|
showError('forgotCaptchaError', '请输入图形验证码');
|
|
return;
|
|
}
|
|
|
|
const btn = document.getElementById('sendForgotCodeBtn');
|
|
btn.disabled = true;
|
|
|
|
fetch('/api/web/send-code', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
email: email,
|
|
type: 'reset_password',
|
|
captchaId: forgotCaptchaId,
|
|
captcha: captcha
|
|
})
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.code === 200) {
|
|
showToast('验证码已发送到您的邮箱', 'success');
|
|
document.getElementById('forgotCode').disabled = false;
|
|
startCountdown(btn);
|
|
} else {
|
|
if (data.msg.includes('验证码') || data.msg.includes('captcha')) {
|
|
showError('forgotCaptchaError', data.msg);
|
|
refreshForgotCaptcha();
|
|
} else if (data.msg.includes('邮箱') || data.msg.includes('email')) {
|
|
showError('forgotEmailError', data.msg);
|
|
} else {
|
|
showToast(data.msg, 'error');
|
|
}
|
|
btn.disabled = false;
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('发送验证码失败:', err);
|
|
showToast('发送验证码失败,请稍后重试', 'error');
|
|
btn.disabled = false;
|
|
});
|
|
}
|
|
|
|
function handleForgot() {
|
|
const email = document.getElementById('forgotEmail').value;
|
|
const password = document.getElementById('forgotPassword').value;
|
|
const confirmPassword = document.getElementById('forgotConfirmPassword').value;
|
|
const code = document.getElementById('forgotCode').value;
|
|
const captcha = document.getElementById('forgotCaptchaInput').value;
|
|
|
|
clearErrors(['forgotEmailError', 'forgotPasswordError', 'forgotConfirmPasswordError', 'forgotCaptchaError']);
|
|
|
|
if (!email) {
|
|
showError('forgotEmailError', '请输入邮箱地址');
|
|
return;
|
|
}
|
|
|
|
if (!password) {
|
|
showError('forgotPasswordError', '请输入新密码');
|
|
return;
|
|
}
|
|
|
|
if (password.length < 6) {
|
|
showError('forgotPasswordError', '密码至少需要6位');
|
|
return;
|
|
}
|
|
|
|
if (!confirmPassword) {
|
|
showError('forgotConfirmPasswordError', '请输入确认密码');
|
|
return;
|
|
}
|
|
|
|
if (password !== confirmPassword) {
|
|
showError('forgotConfirmPasswordError', '两次输入的密码不一致');
|
|
return;
|
|
}
|
|
|
|
if (!code) {
|
|
showError('forgotCodeError', '请输入邮箱验证码');
|
|
return;
|
|
}
|
|
|
|
if (!captcha || captcha.length < 4) {
|
|
showError('forgotCaptchaError', '请输入图形验证码');
|
|
return;
|
|
}
|
|
|
|
fetch('/api/web/reset-password', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
email,
|
|
password,
|
|
code,
|
|
captchaId: forgotCaptchaId,
|
|
captcha: captcha
|
|
})
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.code === 200) {
|
|
showToast('密码重置成功,请使用新密码登录', 'success');
|
|
switchToLogin();
|
|
} else {
|
|
showError('forgotCaptchaError', data.msg);
|
|
refreshForgotCaptcha();
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('重置密码失败:', err);
|
|
showToast('重置密码失败,请稍后重试', 'error');
|
|
refreshForgotCaptcha();
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|