feat(gui,login): 完成登录逻辑

This commit is contained in:
Hydrogen
2025-08-17 21:18:48 +08:00
parent 52231ccb22
commit 37ef4029b4
7 changed files with 40 additions and 722 deletions

View File

@@ -113,6 +113,7 @@ dependencies {
implementation 'jflac:jflac:1.3'
implementation 'com.github.axet:TarsosDSP:2.4'
implementation 'org.json:json:20231013'
implementation 'org.casbin:casdoor-java-sdk:1.37.0'
}
configurations.all {

View File

@@ -14,8 +14,9 @@ import com.axis.innovators.box.register.RegistrationTopic;
import com.axis.innovators.box.tools.*;
import com.axis.innovators.box.util.PythonResult;
import com.axis.innovators.box.util.Tray;
import com.axis.innovators.box.util.UserLocalInformation;
import com.axis.innovators.box.verification.UserTags;
import com.axis.innovators.box.verification.LoginResult;
import com.axis.innovators.box.verification.CasdoorServer;
import com.axis.innovators.box.verification.LoginData;
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import com.formdev.flatlaf.themes.FlatMacLightLaf;
import com.sun.management.HotSpotDiagnosticMXBean;
@@ -31,6 +32,7 @@ import org.apache.logging.log4j.core.appender.FileAppender;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.api.dog.agent.VirtualMachine;
import org.casbin.casdoor.entity.User;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -58,7 +60,8 @@ public class AxisInnovatorsBox {
private static final Logger logger = LogManager.getLogger(AxisInnovatorsBox.class);
private static final String VERSIONS = "0.1.2";
private static final String[] AUTHOR = new String[]{
"tzdwindows 7"
"tzdwindows 7",
"lyxyz5223",
};
/** 我是总任务数 **/
@@ -77,10 +80,11 @@ public class AxisInnovatorsBox {
private final RegistrationTopic registrationTopic = new RegistrationTopic(this);
private final List<WindowsJDialog> windowsJDialogList = new ArrayList<>();
private final StateManager stateManager = new StateManager();
private UserTags userTags;
private final boolean isDebug;
private static DebugWindow debugWindow;
private LoginData loginData;
public AxisInnovatorsBox(String[] args, boolean isDebug) {
this.args = args;
this.isDebug = isDebug;
@@ -88,6 +92,37 @@ public class AxisInnovatorsBox {
organizingCrashReports(throwable instanceof Exception ?
(Exception) throwable : new Exception(throwable));
});
// 加载登录信息,如果没有,弹出登录弹窗,后续可以删掉默认弹出
// TODO: login window
try {
StateManager stateManager = new StateManager();
String token = stateManager.getState("loginToken");
if (token == null || token.isEmpty()) {
LoginResult loginResult = CasdoorLoginWindow.showLoginDialogAndGetLoginResult();
if (loginResult == null) {
// 用户取消登录
JOptionPane.showMessageDialog(null, "取消登录", "登录",
JOptionPane.INFORMATION_MESSAGE);
} else if (loginResult.success()) {
loginData = loginResult.loginData();
stateManager.saveState("loginToken", loginResult.token());
logger.info("Login result: token: " + loginResult.token() + ", user: " + loginResult.user());
JOptionPane.showMessageDialog(null, "登录成功", "登录",
JOptionPane.INFORMATION_MESSAGE);
} else {
// 登录失败,弹出错误提醒,这里只是输出登录错误信息
logger.error("Login error: " + loginResult.message());
JOptionPane.showMessageDialog(null, "登录失败: \n" + loginResult.message(), "登录失败", JOptionPane.ERROR_MESSAGE);
}
} else {
CasdoorServer casdoorServer = new CasdoorServer();
User user = casdoorServer.parseJwtToken(token);
loginData = new LoginData(token, user);
}
} catch (Exception e) {
logger.error("Failed to load login information", e);
}
}
/**
@@ -959,14 +994,6 @@ public class AxisInnovatorsBox {
main.thread = new Thread(() -> {
try {
UserLocalInformation userLocalInformation = new UserLocalInformation(main);
main.userTags = userLocalInformation.getUserTags();
if (main.userTags == null) {
// 登录窗口
main.userTags = LoginWindow.createAndShow();
userLocalInformation.setUserTags(main.userTags);
}
// 主任务1加载插件
logger.info("Loaded plugins Started");
main.progressBarManager.updateMainProgress(++main.completedTasks);
@@ -1092,13 +1119,6 @@ public class AxisInnovatorsBox {
return AUTHOR;
}
/**
* 获取用户标签
* @return 用户标签
*/
public UserTags getUserTags() {
return userTags;
}
/**
* 获取状态管理器

View File

@@ -1,47 +0,0 @@
package com.axis.innovators.box.util;
import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.verification.OnlineVerification;
import com.axis.innovators.box.verification.UserTags;
import com.axis.innovators.box.verification.VerificationService;
/**
* 用于存储用户信息
* @author tzdwindows 7
*/
public class UserLocalInformation {
private final AxisInnovatorsBox main;
public UserLocalInformation(AxisInnovatorsBox main){
this.main = main;
}
/**
* 设置用户信息
* @param userTags 用户信息
*/
public void setUserTags(UserTags userTags){
OnlineVerification onlineVerification = userTags.getUser();
main.getStateManager().saveState("password", onlineVerification.password);
main.getStateManager().saveState("verification", onlineVerification.onlineVerification);
}
/**
* 获取用户信息
* @return 用户信息
*/
public UserTags getUserTags(){
String verification = main.getStateManager().getState("verification");
String password = main.getStateManager().getState("password");
if (verification == null || password == null){
return null;
}
OnlineVerification onlineVerification = OnlineVerification.validateLogin(
verification,
password);
if (onlineVerification == null){
return null;
}
return VerificationService.determineUserType(onlineVerification);
}
}

View File

@@ -1,33 +0,0 @@
package com.axis.innovators.box.verification;
/**
* 在线验证用户身份
* 用于报错用户的验证信息
* @author tzdwindows 7
*/
public class OnlineVerification {
public String onlineVerification;
public String password;
/* 我是错误信息,要返回错误请修改我 */
public static String errorMessage = "用户不存在";
/**
* 验证登录
* @param identifier 账号
* @param password 密码
*/
OnlineVerification(String identifier, String password){
this.onlineVerification = identifier;
this.password = password;
}
/**
* 验证登录
* @param identifier 账号
* @param password 密码
* @return 验证结果,如果返回null则表示验证失败使用errorMessage获取验证失败的原因
*/
public static OnlineVerification validateLogin(String identifier, String password){
return new OnlineVerification(identifier, password);
}
}

View File

@@ -1,46 +0,0 @@
package com.axis.innovators.box.verification;
/**
* 用户标签组
* @author tzdwindows 7
*/
public enum UserTags {
/**
* 没有登录的标签
*/
None,
/**
* 普通用户标签
*/
RegularUsers,
/**
* 管理员标签
*/
AdminUsers,
/**
* VIP用户标签
*/
VipUsers,
/**
* SVip用户标签
*/
SVipUsers,
/**
* 企业用户标签
*/
EnterpriseUsers;
private OnlineVerification onlineVerification;
/**
* 设置用户组信息
* @param onlineVerification 用户验证结果信息
*/
void setUser(OnlineVerification onlineVerification) {
this.onlineVerification = onlineVerification;
}
public OnlineVerification getUser() {
return onlineVerification;
}
}

View File

@@ -1,38 +0,0 @@
package com.axis.innovators.box.verification;
/**
* @author tzdwindows 7
*/
public class VerificationService {
/**
* 确定用户类型
* @param identifier 用户
* @return 用户类型
*/
public static UserTags determineUserType(OnlineVerification identifier) {
UserTags userTags = UserTags.RegularUsers;
userTags.setUser(identifier);
return userTags;
}
/**
* 发送密码重置链接给用户
* @param text
* @return
*/
public static boolean sendPasswordReset(String text) {
return true;
}
/**
* 注册用户
* @param text
* @param text1
* @param pwd
* @return
*/
public static boolean registerUser(String text, String text1, String pwd) {
return true;
}
}

View File

@@ -1,539 +0,0 @@
package com.axis.innovators.box.window;
import com.axis.innovators.box.verification.OnlineVerification;
import com.axis.innovators.box.verification.UserTags;
import com.axis.innovators.box.verification.VerificationService;
import com.formdev.flatlaf.FlatDarculaLaf;
import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.atomic.AtomicReference;
/**
* 重构后的现代化单窗口登录/注册/找回密码界面(带平滑切换动画)
* 保留原有验证逻辑接口调用OnlineVerification / VerificationService
*
* 说明:
* - 单窗口JDialog内使用滑动动画切换视图仿微软登录体验
* - 所有子界面(登录/注册/找回密码)都在同一容器中切换,不再弹新窗口。
* - 按钮与输入框固定宽度,避免被挤压变形。
*
* 注意:需要 flatlaf 依赖以呈现更现代的外观。
*/
public class LoginWindow {
private static final AtomicReference<UserTags> loginResult = new AtomicReference<>(UserTags.None);
private final JDialog dialog;
private final JLayeredPane layeredPane;
private final int DIALOG_WIDTH = 460;
private final int DIALOG_HEIGHT = 560;
// 登录面板中的控件需要在类域以便访问
private JTextField loginEmailField;
private JPasswordField loginPasswordField;
public static UserTags createAndShow() throws InterruptedException, InvocationTargetException {
AtomicReference<UserTags> result = new AtomicReference<>(UserTags.None);
SwingUtilities.invokeAndWait(() -> {
LoginWindow window = new LoginWindow();
window.dialog.setVisible(true);
result.set(loginResult.get());
if (result.get() == UserTags.None) {
System.exit(0);
}
});
return result.get();
}
public LoginWindow() {
setupLookAndFeel();
dialog = new JDialog((Frame) null, "AXIS 安全认证", true);
dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
dialog.setSize(DIALOG_WIDTH, DIALOG_HEIGHT);
dialog.setResizable(false);
dialog.setLocationRelativeTo(null);
// 根容器:深色背景并居中卡片
JPanel root = new JPanel(new GridBagLayout());
root.setBackground(new Color(0x202225));
root.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
dialog.setContentPane(root);
// 卡片容器(居中)
JPanel cardWrapper = new JPanel(null) {
@Override
public Dimension getPreferredSize() {
return new Dimension(DIALOG_WIDTH - 40, DIALOG_HEIGHT - 40);
}
};
cardWrapper.setOpaque(false);
cardWrapper.setPreferredSize(new Dimension(DIALOG_WIDTH - 40, DIALOG_HEIGHT - 40));
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
root.add(cardWrapper, gbc);
// 分层面板用于动画
layeredPane = new JLayeredPane();
layeredPane.setBounds(0, 0, DIALOG_WIDTH - 40, DIALOG_HEIGHT - 40);
cardWrapper.add(layeredPane);
// 卡片背景(圆角)
JPanel backgroundCard = new JPanel();
backgroundCard.setBackground(new Color(0x2A2E31));
backgroundCard.setBorder(new RoundBorder(16, new Color(0x3A3F42)));
backgroundCard.setBounds(0, 0, layeredPane.getWidth(), layeredPane.getHeight());
backgroundCard.setLayout(null);
layeredPane.add(backgroundCard, Integer.valueOf(0));
// 创建三个面板宽度等于容器宽度初始将登录面板放置在0位置
JPanel loginPanel = buildLoginPanel();
JPanel registerPanel = buildRegisterPanel();
JPanel forgotPanel = buildForgotPanel();
int w = layeredPane.getWidth();
int h = layeredPane.getHeight();
loginPanel.setBounds(0, 0, w, h);
registerPanel.setBounds(w, 0, w, h); // 右侧预置
forgotPanel.setBounds(w * 2, 0, w, h);
layeredPane.add(loginPanel, Integer.valueOf(1));
layeredPane.add(registerPanel, Integer.valueOf(1));
layeredPane.add(forgotPanel, Integer.valueOf(1));
dialog.pack();
// ensure layered sizes match after pack
SwingUtilities.invokeLater(() -> {
layeredPane.setBounds(0, 0, cardWrapper.getWidth(), cardWrapper.getHeight());
backgroundCard.setBounds(0, 0, layeredPane.getWidth(), layeredPane.getHeight());
int nw = layeredPane.getWidth(), nh = layeredPane.getHeight();
loginPanel.setBounds(0, 0, nw, nh);
registerPanel.setBounds(nw, 0, nw, nh);
forgotPanel.setBounds(nw * 2, 0, nw, nh);
});
}
// 切换动画direction = 1 表示向左滑动进入下一页(当前 -> 右侧),-1 表示向右滑动进入上一页
private void slideTo(int targetIndex) {
Component[] comps = layeredPane.getComponents();
// 每个面板宽度
int w = layeredPane.getWidth();
// 当前最左边的x位置找到最左的那个主要面板
// 我们采用简单方式:目标面板应该位于 x = targetIndex * w 0,1,2
int targetX = -targetIndex * w; // 我们会将所有面板整体平移,使目标显示在 x=0
// 获取当前 offset (使用第一个面板的 x 来代表整体偏移)
int startOffset = 0;
// find current offset by checking bounds of first panel (assume index 0 is login)
if (comps.length > 0) {
startOffset = comps[0].getX();
}
int start = startOffset;
int end = targetX;
int duration = 300; // ms
int fps = 60;
int delay = 1000 / fps;
int steps = Math.max(1, duration / delay);
final int[] step = {0};
Timer timer = new Timer(delay, null);
timer.addActionListener((ActionEvent e) -> {
step[0]++;
double t = (double) step[0] / steps;
// ease in-out cubic
double tt = t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
int cur = (int) Math.round(start + (end - start) * tt);
// 平移所有在 layeredPane 中(除背景)组件
for (Component c : layeredPane.getComponents()) {
if (c instanceof JPanel && c.isVisible()) {
// 计算原始索引根据名字
String name = c.getName();
// 我们之前把panel放在 x = idx * w ; 现在把它设置为 idx*w + cur
int idx = 0;
if ("login".equals(name)) idx = 0;
else if ("register".equals(name)) idx = 1;
else if ("forgot".equals(name)) idx = 2;
c.setLocation(idx * w + cur, 0);
}
}
layeredPane.repaint();
if (step[0] >= steps) {
timer.stop();
}
});
timer.setInitialDelay(0);
timer.start();
}
private JPanel buildLoginPanel() {
JPanel p = new JPanel(null);
p.setOpaque(false);
p.setName("login");
int w = DIALOG_WIDTH - 40;
int h = DIALOG_HEIGHT - 40;
// 标题区
JLabel appTitle = new JLabel("AXIS");
appTitle.setFont(new Font("微软雅黑", Font.BOLD, 28));
appTitle.setForeground(Color.WHITE);
appTitle.setBounds(28, 20, w - 56, 36);
JLabel subtitle = new JLabel("安全认证 — 请登录以继续");
subtitle.setFont(new Font("微软雅黑", Font.PLAIN, 12));
subtitle.setForeground(new Color(0xA7AEB5));
subtitle.setBounds(28, 56, w - 56, 18);
p.add(appTitle);
p.add(subtitle);
// 卡片内控件起始 y
int startY = 96;
int fieldW = Math.min(360, w - 56);
int fieldX = (w - fieldW) / 2;
// Email
JLabel emailLabel = new JLabel("账号");
emailLabel.setForeground(new Color(0x99A0A7));
emailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
emailLabel.setBounds(fieldX, startY, fieldW, 18);
loginEmailField = new JTextField();
styleInput(loginEmailField);
loginEmailField.setBounds(fieldX, startY + 22, fieldW, 44);
loginEmailField.putClientProperty("JTextField.placeholderText", "邮箱或手机号");
// Password
JLabel pwdLabel = new JLabel("密码");
pwdLabel.setForeground(new Color(0x99A0A7));
pwdLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
pwdLabel.setBounds(fieldX, startY + 22 + 44 + 12, fieldW, 18);
loginPasswordField = new JPasswordField();
styleInput(loginPasswordField);
loginPasswordField.setBounds(fieldX, startY + 22 + 44 + 12 + 20, fieldW - 48, 44);
loginPasswordField.putClientProperty("JTextField.placeholderText", "请输入密码");
// eye toggle
JToggleButton eyeBtn = new JToggleButton("显示");
eyeBtn.setFont(new Font("微软雅黑", Font.PLAIN, 12));
eyeBtn.setFocusable(false);
eyeBtn.setBorderPainted(false);
eyeBtn.setContentAreaFilled(true);
eyeBtn.setBackground(new Color(0x39424A));
eyeBtn.setForeground(Color.WHITE);
eyeBtn.setBounds(fieldX + fieldW - 44, startY + 22 + 44 + 12 + 20, 44, 44);
eyeBtn.addActionListener(e -> {
if (eyeBtn.isSelected()) loginPasswordField.setEchoChar((char) 0);
else loginPasswordField.setEchoChar('•');
});
// 登录按钮(占满宽度)
JButton loginBtn = new JButton("立即登录");
stylePrimaryButton(loginBtn);
loginBtn.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 22, fieldW, 48);
loginBtn.addActionListener(e -> doLogin());
// 链接区域(注册 / 忘记密码) — 点击切换到对应面板
JButton toRegister = createTextLink("注册账号");
toRegister.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 22 + 60, 120, 20);
toRegister.addActionListener(e -> slideTo(1));
JButton toForgot = createTextLink("忘记密码");
toForgot.setBounds(fieldX + fieldW - 120, startY + 22 + 44 + 12 + 20 + 44 + 22 + 60, 120, 20);
toForgot.addActionListener(e -> slideTo(2));
// footer
JLabel footer = new JLabel("使用你的 AXIS 账户进行登录。");
footer.setForeground(new Color(0x8F969C));
footer.setFont(new Font("微软雅黑", Font.PLAIN, 11));
footer.setBounds(fieldX, h - 44, fieldW, 18);
p.add(emailLabel);
p.add(loginEmailField);
p.add(pwdLabel);
p.add(loginPasswordField);
p.add(eyeBtn);
p.add(loginBtn);
p.add(toRegister);
p.add(toForgot);
p.add(footer);
return p;
}
private JPanel buildRegisterPanel() {
JPanel p = new JPanel(null);
p.setOpaque(false);
p.setName("register");
int w = DIALOG_WIDTH - 40;
int h = DIALOG_HEIGHT - 40;
JLabel title = new JLabel("创建账号");
title.setFont(new Font("微软雅黑", Font.BOLD, 24));
title.setForeground(Color.WHITE);
title.setBounds(28, 20, w - 56, 36);
JLabel desc = new JLabel("快速创建你的 AXIS 帐号");
desc.setFont(new Font("微软雅黑", Font.PLAIN, 12));
desc.setForeground(new Color(0xA7AEB5));
desc.setBounds(28, 56, w - 56, 18);
p.add(title);
p.add(desc);
int startY = 96;
int fieldW = Math.min(360, w - 56);
int fieldX = (w - fieldW) / 2;
// 用户名
JLabel nameLabel = new JLabel("用户名");
nameLabel.setForeground(new Color(0x99A0A7));
nameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
nameLabel.setBounds(fieldX, startY, fieldW, 18);
JTextField nameField = new JTextField();
styleInput(nameField);
nameField.setBounds(fieldX, startY + 22, fieldW, 44);
// 邮箱
JLabel emailLabel = new JLabel("邮箱");
emailLabel.setForeground(new Color(0x99A0A7));
emailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
emailLabel.setBounds(fieldX, startY + 22 + 44 + 12, fieldW, 18);
JTextField emailField = new JTextField();
styleInput(emailField);
emailField.setBounds(fieldX, startY + 22 + 44 + 12 + 20, fieldW, 44);
// 密码
JLabel pwdLabel = new JLabel("密码");
pwdLabel.setForeground(new Color(0x99A0A7));
pwdLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
pwdLabel.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 12, fieldW, 18);
JPasswordField pwdField = new JPasswordField();
styleInput(pwdField);
pwdField.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 12 + 20, fieldW, 44);
// 确认密码
JLabel confirmLabel = new JLabel("确认密码");
confirmLabel.setForeground(new Color(0x99A0A7));
confirmLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
confirmLabel.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 12 + 20 + 44 + 12, fieldW, 18);
JPasswordField confirmField = new JPasswordField();
styleInput(confirmField);
confirmField.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 12 + 20 + 44 + 12 + 20, fieldW, 44);
// 注册按钮
JButton regBtn = new JButton("创建账号");
stylePrimaryButton(regBtn);
regBtn.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 12 + 20 + 44 + 12 + 20 + 44 + 18, fieldW, 48);
regBtn.addActionListener(e -> {
String name = nameField.getText().trim();
String email = emailField.getText().trim();
String pwd = new String(pwdField.getPassword());
String confirm = new String(confirmField.getPassword());
if (name.isEmpty() || email.isEmpty() || pwd.isEmpty() || confirm.isEmpty()) {
JOptionPane.showMessageDialog(dialog, "请完整填写注册信息", "注册错误", JOptionPane.ERROR_MESSAGE);
return;
}
if (!pwd.equals(confirm)) {
JOptionPane.showMessageDialog(dialog, "两次输入的密码不一致", "注册错误", JOptionPane.ERROR_MESSAGE);
return;
}
boolean success = VerificationService.registerUser(name, email, pwd);
if (success) {
JOptionPane.showMessageDialog(dialog, "注册成功,请登录", "注册成功", JOptionPane.INFORMATION_MESSAGE);
slideTo(0); // 回到登录页面
} else {
JOptionPane.showMessageDialog(dialog, "注册失败,请检查信息", "注册错误", JOptionPane.ERROR_MESSAGE);
}
});
// 返回登录链接
JButton back = createTextLink("返回登录");
back.setBounds(fieldX, regBtn.getY() + regBtn.getHeight() + 12, 120, 20);
back.addActionListener(e -> slideTo(0));
p.add(nameLabel);
p.add(nameField);
p.add(emailLabel);
p.add(emailField);
p.add(pwdLabel);
p.add(pwdField);
p.add(confirmLabel);
p.add(confirmField);
p.add(regBtn);
p.add(back);
return p;
}
private JPanel buildForgotPanel() {
JPanel p = new JPanel(null);
p.setOpaque(false);
p.setName("forgot");
int w = DIALOG_WIDTH - 40;
int h = DIALOG_HEIGHT - 40;
JLabel title = new JLabel("找回密码");
title.setFont(new Font("微软雅黑", Font.BOLD, 24));
title.setForeground(Color.WHITE);
title.setBounds(28, 20, w - 56, 36);
JLabel desc = new JLabel("通过注册邮箱重置密码");
desc.setFont(new Font("微软雅黑", Font.PLAIN, 12));
desc.setForeground(new Color(0xA7AEB5));
desc.setBounds(28, 56, w - 56, 18);
p.add(title);
p.add(desc);
int startY = 110;
int fieldW = Math.min(360, w - 56);
int fieldX = (w - fieldW) / 2;
JLabel emailLabel = new JLabel("注册邮箱");
emailLabel.setForeground(new Color(0x99A0A7));
emailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
emailLabel.setBounds(fieldX, startY, fieldW, 18);
JTextField emailField = new JTextField();
styleInput(emailField);
emailField.setBounds(fieldX, startY + 22, fieldW, 44);
JButton sendBtn = new JButton("发送重置邮件");
stylePrimaryButton(sendBtn);
sendBtn.setBounds(fieldX, startY + 22 + 44 + 22, fieldW, 48);
sendBtn.addActionListener(e -> {
String email = emailField.getText().trim();
if (email.isEmpty()) {
JOptionPane.showMessageDialog(dialog, "请输入注册邮箱", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
if (VerificationService.sendPasswordReset(email)) {
JOptionPane.showMessageDialog(dialog, "重置邮件已发送,请查收", "成功", JOptionPane.INFORMATION_MESSAGE);
slideTo(0);
} else {
JOptionPane.showMessageDialog(dialog, "发送失败或邮箱未注册", "失败", JOptionPane.ERROR_MESSAGE);
}
});
JButton back = createTextLink("返回登录");
back.setBounds(fieldX, sendBtn.getY() + sendBtn.getHeight() + 12, 120, 20);
back.addActionListener(e -> slideTo(0));
p.add(emailLabel);
p.add(emailField);
p.add(sendBtn);
p.add(back);
return p;
}
private void doLogin() {
String email = loginEmailField.getText().trim();
String password = new String(loginPasswordField.getPassword()).trim();
if (email.isEmpty() || password.isEmpty()) {
JOptionPane.showMessageDialog(dialog, "请输入完整的登录信息", "验证错误", JOptionPane.ERROR_MESSAGE);
return;
}
OnlineVerification onlineVerification = OnlineVerification.validateLogin(email, password);
if (onlineVerification == null) {
String err = OnlineVerification.errorMessage != null && !OnlineVerification.errorMessage.trim().isEmpty()
? OnlineVerification.errorMessage
: "验证失败,请重试";
JOptionPane.showMessageDialog(dialog, err, "验证错误", JOptionPane.ERROR_MESSAGE);
return;
}
loginResult.set(VerificationService.determineUserType(onlineVerification));
dialog.dispose();
}
// 通用输入框样式
private void styleInput(JComponent comp) {
comp.setFont(new Font("微软雅黑", Font.PLAIN, 14));
comp.setBackground(new Color(0x2F3336));
comp.setForeground(new Color(0xE8ECEF));
comp.setBorder(BorderFactory.createCompoundBorder(
new RoundBorder(8, new Color(0x3A3F42)),
BorderFactory.createEmptyBorder(10, 12, 10, 12)
));
if (comp instanceof JTextComponent) ((JTextComponent) comp).setCaretColor(new Color(0x9AA0A6));
}
// 主要操作按钮样式(主色)
private void stylePrimaryButton(AbstractButton b) {
b.setFont(new Font("微软雅黑", Font.BOLD, 14));
b.setForeground(Color.WHITE);
b.setBackground(new Color(0x2B79D0));
b.setBorderPainted(false);
b.setFocusPainted(false);
b.setOpaque(true);
b.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
private JButton createTextLink(String text) {
JButton btn = new JButton(text);
btn.setFont(new Font("微软雅黑", Font.PLAIN, 12));
btn.setForeground(new Color(0x79A6FF));
btn.setBorderPainted(false);
btn.setContentAreaFilled(false);
btn.setFocusPainted(false);
btn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
return btn;
}
private void setupLookAndFeel() {
try {
UIManager.setLookAndFeel(new FlatDarculaLaf());
UIManager.put("Component.arc", 12);
UIManager.put("TextComponent.arc", 12);
UIManager.put("Button.arc", 10);
UIManager.put("Panel.background", new Color(0x202225));
UIManager.put("TextComponent.background", new Color(0x2F3336));
UIManager.put("TextComponent.foreground", new Color(0xE8ECEF));
} catch (UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
}
// 圆角边框
private static class RoundBorder extends LineBorder {
private final int radius;
public RoundBorder(int radius, Color color) {
super(color, 1);
this.radius = radius;
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(lineColor);
g2.drawRoundRect(x, y, width - 1, height - 1, radius, radius);
g2.dispose();
}
}
}