package gui;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.table.DefaultTableModel;
import enrollment.Enrollment;
import enrollment.EnrollmentDao;
import enrollment.EnrollmentDaoImpl;
public class EnrollmentManagementPanel extends JPanel {
private final EnrollmentDao enrollmentDao = new EnrollmentDaoImpl();
private final DefaultTableModel model = new DefaultTableModel(
new String[]{"学生ID", "课程代码", "选课时间", "成绩", "等级", "状态", "是否补考", "评语"}, 0);
private final JTable table = new JTable(model);
// 表单字段
private final JTextField txtStudentId = new JTextField(8);
private final JTextField txtCourseCode = new JTextField(10);
private final JTextField txtEnrollmentTime = new JTextField(16); // YYYY-MM-DD HH:MM:SS
private final JTextField txtGrade = new JTextField(6); // 可为空
private final JLabel lblGradeLevel = new JLabel("N/A");
private final JComboBox<String> cmbStatus = new JComboBox<>(new String[]{"已选课", "已退课", "已评分"});
private final JCheckBox chkMakeUpExam = new JCheckBox("是", true);
private final JCheckBox chkMakeUpExam1 = new JCheckBox("否", false);
private final JTextArea txtComment = new JTextArea(1, 20);
private final JScrollPane commentScrollPane = new JScrollPane(txtComment);
public EnrollmentManagementPanel() {
setLayout(new BorderLayout());
// 表格区域
add(new JScrollPane(table), BorderLayout.CENTER);
// 表单输入区
//JPanel form = new JPanel(new GridLayout(5, 6, 10, 10));
//form.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
// 创建主表单面板,使用更灵活的 GridBagLayout 替代 GridLayout
JPanel form = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
gbc.fill = GridBagConstraints.HORIZONTAL;
String[] fields = {"学生ID", "课程代码", "选课时间", "成绩", "等级", "状态", "是否补考", "评语"};
/* 0-学生ID 1-课程代码 2-选课时间 3-成绩 4-null(等级只读) 5-null(下拉框) 6-null(单选) 7-null(文本区) */
JTextField[] fieldArr = {
txtStudentId, txtCourseCode, txtEnrollmentTime, txtGrade, null, null, null, null
};
for (int i = 0; i < fields.length; i++) {
gbc.gridx = 0; gbc.gridy = i;
gbc.anchor = GridBagConstraints.EAST;
form.add(new JLabel(fields[i] + ":"), gbc);
gbc.gridx = 1; gbc.weightx = 1.0;
switch (fields[i]) {
case "等级" -> form.add(lblGradeLevel, gbc); // 只读标签
case "状态" -> form.add(cmbStatus, gbc);
case "是否补考" -> {
JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0));
p.add(chkMakeUpExam);
form.add(p, gbc);
}
case "评语" -> form.add(commentScrollPane, gbc);
default -> form.add(fieldArr[i], gbc); // 用成员控件,不再 new!
}
}
//UIManager.put("Label.font", new Font("微软雅黑", Font.PLAIN, 14));
//UIManager.put("TextField.font", new Font("微软雅黑", Font.PLAIN, 14));
/*form.add(new JLabel("学生ID:", SwingConstants.RIGHT)); form.add(txtStudentId);
form.add(new JLabel("课程代码:", SwingConstants.RIGHT)); form.add(txtCourseCode);
form.add(new JLabel("选课时间:", SwingConstants.RIGHT)); form.add(txtEnrollmentTime);
form.add(new JLabel("成绩:", SwingConstants.RIGHT)); form.add(txtGrade);
form.add(new JLabel("等级:", SwingConstants.RIGHT)); form.add(lblGradeLevel);
form.add(new JLabel("状态:", SwingConstants.RIGHT)); form.add(cmbStatus);
form.add(new JLabel("是否补考:", SwingConstants.RIGHT)); form.add(chkMakeUpExam);
form.add(new JLabel("评语:", SwingConstants.RIGHT)); form.add(commentScrollPane);*/
// 按钮组
JButton btnAdd = new JButton("添加选课");
JButton btnUpdateGrade = new JButton("更新成绩");
JButton btnDelete = new JButton("删除记录");
JButton btnFindByStudent = new JButton("按学生查");
JButton btnByCourse = new JButton("按课程查");
JButton btnRefresh = new JButton("刷新全部");
JButton btnClear = new JButton("清空");
JPanel btnPanel = new JPanel();
btnPanel.add(btnAdd); btnPanel.add(btnUpdateGrade); btnPanel.add(btnDelete);
btnPanel.add(btnFindByStudent); btnPanel.add(btnByCourse);
btnPanel.add(btnRefresh); btnPanel.add(btnClear);
form.add(btnPanel);
add(form, BorderLayout.SOUTH);
// ==================== 事件绑定 ====================
btnAdd.addActionListener(e -> addEnrollment());
btnUpdateGrade.addActionListener(e -> updateGrade());
btnDelete.addActionListener(e -> deleteEnrollment());
btnFindByStudent.addActionListener(e -> findByStudentId());
btnByCourse.addActionListener(e -> findByCourseCode());
btnRefresh.addActionListener(e -> refreshTable());
btnClear.addActionListener(e -> clearForm());
// 成绩变化时实时更新等级显示
txtGrade.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
@Override
public void insertUpdate(javax.swing.event.DocumentEvent e) { updateGradeLevel(); }
@Override
public void removeUpdate(javax.swing.event.DocumentEvent e) { updateGradeLevel(); }
@Override
public void changedUpdate(javax.swing.event.DocumentEvent e) { updateGradeLevel(); }
});
// 表格点击 → 回填表单
table.getSelectionModel().addListSelectionListener(evt -> {
if (!evt.getValueIsAdjusting()) fillFormFromTable();
});
// 初始化加载所有数据
refreshTable();
}
// ==================== 核心操作方法 ====================
/**
* 添加选课记录
*/
private void addEnrollment() {
try {
Enrollment e = getFormEnrollment();
enrollmentDao.insert(e);
model.addRow(new Object[]{
e.getStudentId(), e.getCourseCode(),
e.getEnrollmentTime(),
formatDouble(e.getGrade()), e.getGradeLevel(),
e.getStatus(), e.isMakeUpExam() ? "✅" : "❌",
e.getComment()
});
JOptionPane.showMessageDialog(this, "✅ 添加成功!");
clearForm();
} catch (Exception ex) {
showError("❌ 添加失败:" + ex.getMessage());
}
}
/**
* 更新成绩(独立操作,更安全)
*/
private void updateGrade() {
int row = table.getSelectedRow();
if (row < 0) {
showInfo("请先选择一条记录来更新成绩!");
return;
}
try {
int studentId = (int) model.getValueAt(row, 0);
String courseCode = (String) model.getValueAt(row, 1);
Double grade = parseDouble(txtGrade.getText(), "成绩");
enrollmentDao.updateGrade(studentId, courseCode, grade);
// 更新表格
model.setValueAt(formatDouble(grade), row, 3);
model.setValueAt(getGradeLevel(grade), row, 4);
model.setValueAt("已评分", row, 5);
model.setValueAt(grade < 60 ? "✅" : "❌", row, 6); // 补考标记
showInfo("✅ 成绩更新成功,等级:" + getGradeLevel(grade) + "," + (grade >= 60 ? "已通过" : "需补考"));
} catch (Exception ex) {
showError("❌ 更新成绩失败:" + ex.getMessage());
}
}
/**
* 删除选课记录(按学生+课程)
*/
private void deleteEnrollment() {
int row = table.getSelectedRow();
if (row < 0) {
showInfo("请先选择要删除的选课记录!");
return;
}
int studentId = (int) model.getValueAt(row, 0);
String courseCode = (String) model.getValueAt(row, 1);
int confirm = JOptionPane.showConfirmDialog(this,
"确定删除 学生ID=" + studentId + " 的课程 " + courseCode + " 的记录?", "确认删除", JOptionPane.YES_NO_OPTION);
if (confirm != JOptionPane.YES_OPTION) return;
try {
enrollmentDao.delete(studentId, courseCode);
model.removeRow(row);
JOptionPane.showMessageDialog(this, "✅ 删除成功!");
} catch (Exception ex) {
showError("❌ 删除失败:" + ex.getMessage());
}
}
/**
* 按学生 ID 查询其所有选课
*/
private void findByStudentId() {
try {
int id = Integer.parseInt(txtStudentId.getText().trim());
var list = enrollmentDao.findByStudentId(id);
model.setRowCount(0);
for (Enrollment e : list) {
addEnrollmentToTable(e);
}
showInfo("共加载 " + list.size() + " 条该学生的选课记录");
} catch (NumberFormatException e) {
showError("学生ID必须是数字");
} catch (Exception ex) {
showError("查询失败:" + ex.getMessage());
}
}
/**
* 按课程代码查询所有选课学生
*/
private void findByCourseCode() {
String code = txtCourseCode.getText().trim();
if (code.isEmpty()) {
showInfo("请输入课程代码!");
return;
}
try {
var list = enrollmentDao.findByCourseCode(code);
model.setRowCount(0);
for (Enrollment e : list) {
addEnrollmentToTable(e);
}
showInfo("共加载 " + list.size() + " 名学生选修此课程");
} catch (Exception ex) {
showError("查询失败:" + ex.getMessage());
}
}
/**
* 刷新:重新加载所有选课记录
*/
private void refreshTable() {
model.setRowCount(0);
try {
List<Enrollment> all = enrollmentDao.findByStudentId(0); // 不支持直接 findAll? 手动查全部
// 这里模拟 findAll —— 如果数据库量大建议分页
String sql = "SELECT DISTINCT student_id FROM enrollments";
java.sql.Connection conn = util.DBConnection.getConnection();
java.sql.Statement stmt = conn.createStatement();
java.sql.ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
int sid = rs.getInt("student_id");
var list = enrollmentDao.findByStudentId(sid);
for (Enrollment e : list) {
addEnrollmentToTable(e);
}
}
conn.close();
} catch (Exception ex) {
showError("加载失败:" + ex.getMessage());
}
}
/**
* 填充表单:从表格选中行
*/
private void fillFormFromTable() {
int row = table.getSelectedRow();
if (row < 0) return;
txtStudentId.setText(model.getValueAt(row, 0).toString());
txtCourseCode.setText(model.getValueAt(row, 1).toString());
txtEnrollmentTime.setText(model.getValueAt(row, 2).toString());
Object gradeVal = model.getValueAt(row, 3);
txtGrade.setText(gradeVal == null || "null".equals(gradeVal) ? "" : gradeVal.toString());
cmbStatus.setSelectedItem(model.getValueAt(row, 5));
chkMakeUpExam.setSelected("✅".equals(model.getValueAt(row, 6)));
txtComment.setText(model.getValueAt(row, 7) == null ? "" : model.getValueAt(row, 7).toString());
}
/**
* 清空表单
*/
private void clearForm() {
txtStudentId.setText("");
txtCourseCode.setText("");
txtEnrollmentTime.setText("");
txtGrade.setText("");
lblGradeLevel.setText("N/A");
cmbStatus.setSelectedIndex(0);
chkMakeUpExam.setSelected(false);
txtComment.setText("");
}
// ==================== 工具方法 ====================
/**
* 从表单读取数据构建 Enrollment 对象
*/
/*private LocalDateTime parseDateTime(String text, String field) throws Exception {
try {
return LocalDateTime.parse(text);
} catch (Exception e) {
throw new Exception(field + "格式应为 yyyy-MM-dd HH:mm:ss,当前输入:" + text);
}
}*/
/**
* 安全解析字符串为 LocalDateTime
* 支持多种常见格式,自动补全缺失的时间部分(如只有日期时,默认时间为 00:00:00)
*
* @param text 输入的时间字符串
* @param field 字段名称(用于错误提示)
* @return 解析成功的 LocalDateTime 对象
* @throws Exception 如果所有格式都无法匹配,抛出带详细信息的异常
*/
private LocalDateTime parseDateTime(String text, String field) throws Exception {
if (text == null || text.trim().isEmpty()) {
throw new Exception(field + "不能为空");
}
text = text.trim();
// 定义支持的格式模式
String[] patterns = {
"yyyy-MM-dd HH:mm:ss", // 2025-04-05 14:30:00
"yyyy/MM/dd HH:mm:ss", // 2025/04/05 14:30:00
"yyyy-MM-dd HH:mm", // 2025-04-05 14:30
"yyyy/MM/dd HH:mm", // 2025/04/05 14:30
"yyyy-MM-dd", // 2025-04-05 → 默认时间 00:00:00
"yyyy/MM/dd" // 2025/04/05
};
// 尝试每一种格式
for (String pattern : patterns) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
LocalDateTime dateTime = LocalDateTime.parse(text, formatter);
// 如果格式中不含时间(只有日期),我们已经通过 parse 得到的是当天 00:00:00,无需额外处理
return dateTime;
} catch (DateTimeParseException ignored) {
// 继续尝试下一个格式
}
}
// 所有格式都失败了,给出友好错误提示
throw new Exception(
field + "格式不正确。支持的格式示例:" +
"\n 2025-04-05 14:30:00" +
"\n 2025/04/05 14:30:00" +
"\n 2025-04-05 14:30" +
"\n 2025-04-05" +
"\n当前输入:" + text
);
}
private Enrollment getFormEnrollment() throws Exception {
Enrollment e = new Enrollment();
e.setStudentId(parseInt(txtStudentId.getText(), "学生ID"));
e.setCourseCode(txtCourseCode.getText().trim());
if (e.getCourseCode().isEmpty()) throw new IllegalArgumentException("课程代码不能为空");
String timeStr = txtEnrollmentTime.getText().trim();
e.setEnrollmentTime(timeStr.isEmpty() ? LocalDateTime.now() : parseDateTime(timeStr, "选课时间"));
String gradeText = txtGrade.getText().trim();
if (!gradeText.isEmpty()) {
double grade = parseDouble(gradeText, "成绩");
e.setGrade(grade);
e.CalculateGradeLevel(grade);
e.setStatus("已评分");
} else {
e.setGrade(null);
e.CalculateGradeLevel(null);
e.setStatus((String) cmbStatus.getSelectedItem());
}
e.setMakeUpExam(chkMakeUpExam.isSelected());
e.setComment(txtComment.getText().trim());
return e;
}
/**
* 将单个选课加入表格
*/
private void addEnrollmentToTable(Enrollment e) {
model.addRow(new Object[]{
e.getStudentId(), e.getCourseCode(),
e.getEnrollmentTime(),
formatDouble(e.getGrade()), e.getGradeLevel(),
e.getStatus(),
e.isMakeUpExam() ? "✅" : "❌",
e.getComment()
});
}
/**
* 安全解析整数
*/
private int parseInt(String text, String field) throws NumberFormatException {
if (text == null || text.trim().isEmpty())
throw new NumberFormatException(field + "不能为空");
return Integer.parseInt(text.trim());
}
/**
* 安全解析双精度
*/
private Double parseDouble(String text, String field) throws NumberFormatException {
if (text == null || text.trim().isEmpty()) return null;
double val = Double.parseDouble(text.trim());
if (val < 0 || val > 100)
throw new IllegalArgumentException(field + "应在 0~100 之间");
return val;
}
/**
* 格式化成绩显示
*/
private String formatDouble(Double d) {
return d == null ? "未评分" : String.format("%.1f", d);
}
/**
* 手动计算等级(同步前端显示)
*/
private void updateGradeLevel() {
try {
String text = txtGrade.getText().trim();
if (text.isEmpty()) {
lblGradeLevel.setText("N/A");
return;
}
double grade = Double.parseDouble(text);
lblGradeLevel.setText(getGradeLevel(grade));
} catch (Exception e) {
lblGradeLevel.setText("错误");
}
}
/**
* 获取等级(A/B/C/D/F)
*/
private String getGradeLevel(Double grade) {
if (grade == null) return "N/A";
return switch ((int)(grade / 10)) {
case 10, 9 -> "A";
case 8 -> "B";
case 7 -> "C";
case 6 -> "D";
default -> "F";
};
}
/**
* 显示错误消息
*/
private void showError(String message) {
JOptionPane.showMessageDialog(this, message, "错误", JOptionPane.ERROR_MESSAGE);
}
/**
* 显示提示信息
*/
private void showInfo(String message) {
JOptionPane.showMessageDialog(this, message, "提示", JOptionPane.INFORMATION_MESSAGE);
}
}
这段代码应该放在哪’
最新发布