採用 **JPanel 卡片切換** 模式:主視窗 bookStoreApp 持有 JFrame,透過 setContentPane(new XxxPanel(frame)) 實現面板無縫切換。每個面板持有主視窗引用以便回調切換。
package cn.hncu.bookStore.user.ui;
import java.util.List;
import javax.swing.*;
import cn.hncu.bookStore.user.business.factory.UserEBIFactory;
import cn.hncu.bookStore.user.vo.UserModel;
public class listPanel extends JPanel {
private static final long serialVersionUID = 1L;
private JFrame mainFrame;
private JList
public class AddPanel extends JPanel {
private JFrame mainFrame;
private JTextField tfdName, tfdUuid;
private JPasswordField tfdPwd, tfdPwd2;
private JComboBox combType;
private JButton btnAdd, btnCancel;
public AddPanel(JFrame mainFrame) {
this.mainFrame = mainFrame;
initComponents();
loadTypes();
}
private void loadTypes() {
combType.removeAllItems();
for (UserTypeEnum e : UserTypeEnum.values()) combType.addItem(e.getName());
}
private void initComponents() {
// ... 佈局代碼省略 (類似 listPanel,含標籤、輸入框、下拉選單、按鈕)
btnAdd.addActionListener(this::addAction);
btnCancel.addActionListener(e -> backToList());
}
private void addAction(ActionEvent evt) {
String name = tfdName.getText().trim();
String uuid = tfdUuid.getText().trim(); // 實務應由系統自動生成,這裡為演示保留手動
String pwd = new String(tfdPwd.getPassword());
String pwd2 = new String(tfdPwd2.getPassword());
if (!pwd.equals(pwd2)) { showMsg("密碼不一致"); return; }
UserModel um = new UserModel();
um.setName(name);
um.setUuid(uuid);
um.setPwd(pwd);
um.setType(UserTypeEnum.getTypeByName(combType.getSelectedItem().toString()));
if (UserEBIFactory.getUserEBI().create(um)) {
backToList();
} else {
showMsg("用戶已存在 (UUID 衝突)");
}
}
private void backToList() {
mainFrame.setContentPane(new listPanel(mainFrame));
mainFrame.validate();
}
}
public class DeletePanel extends JPanel {
private JFrame mainFrame;
private String uuid;
private UserEBI ebi = UserEBIFactory.getUserEBI();
// 元件:顯示用戶資訊 (唯讀)、確認刪除/取消按鈕
public DeletePanel(JFrame mainFrame, String uuid) {
this.mainFrame = mainFrame; this.uuid = uuid;
initComponents();
loadUserData();
}
private void loadUserData() {
UserModel um = ebi.getSingle(uuid);
// 填充各欄位並設為不可編輯
tfdUuid.setText(uuid); tfdUuid.setEnabled(false);
tfdName.setText(um.getName()); tfdName.setEnabled(false);
// ... 密碼、類型同理
}
private void deleteAction(ActionEvent evt) {
if (ebi.delete(uuid)) backToList();
else showMsg("刪除失敗,用戶可能已不存在");
}
}
public class UpdatePanel extends JPanel {
private JFrame mainFrame;
private String uuid;
private UserEBI ebi = UserEBIFactory.getUserEBI();
// 元件:可編輯姓名、密碼、類型 (UUID 唯讀)
public UpdatePanel(JFrame mainFrame, String uuid) {
this.mainFrame = mainFrame; this.uuid = uuid;
initComponents();
loadUserData();
}
private void loadUserData() {
UserModel um = ebi.getSingle(uuid);
tfdUuid.setText(uuid); tfdUuid.setEnabled(false);
tfdName.setText(um.getName());
tfdPwd.setText(um.getPwd()); tfdPwd2.setText(um.getPwd());
combType.setSelectedIndex(um.getType() - 1); // 枚舉索引對應
}
private void updateAction(ActionEvent evt) {
String name = tfdName.getText().trim();
String pwd = new String(tfdPwd.getPassword());
String pwd2 = new String(tfdPwd2.getPassword());
if (!pwd.equals(pwd2)) { showMsg("密碼不一致"); return; }
UserModel um = new UserModel();
um.setUuid(uuid);
um.setName(name);
um.setPwd(pwd);
um.setType(UserTypeEnum.getTypeByName(combType.getSelectedItem().toString()));
if (ebi.update(um)) backToList();
else showMsg("更新失敗");
}
}
public class QueryPanel extends JPanel {
private JFrame mainFrame;
private JTextField tfdName, tfdUuid;
private JComboBox combType;
private JButton btnQuery, btnCancel;
public QueryPanel(JFrame mainFrame) {
this.mainFrame = mainFrame;
initComponents();
loadTypes();
}
private void loadTypes() {
combType.addItem("請選擇"); // 預設項
for (UserTypeEnum e : UserTypeEnum.values()) combType.addItem(e.getName());
}
private void queryAction(ActionEvent evt) {
UserQueryModel qm = new UserQueryModel();
qm.setName(tfdName.getText().trim());
qm.setUuid(tfdUuid.getText().trim());
if (combType.getSelectedIndex() > 0) { // 非「請選擇」
qm.setType(UserTypeEnum.getTypeByName(combType.getSelectedItem().toString()));
}
List list = UserEBIFactory.getUserEBI().getByCondition(qm);
mainFrame.setContentPane(new listPanel(mainFrame, list));
mainFrame.validate();
}
}
| 模式 | 實作方式 | 效益 |
|---|---|---|
| 卡片切換 | JFrame.setContentPane(new Panel(frame)) | 單視窗多面板、狀態隔離、無需 CardLayout 複雜管理 |
| 依賴注入 (手動) | 面板建構子傳入 mainFrame (JFrame) | 面板間解耦、易測試、避免單例污染 |
| 業務邏輯分離 | UI 僅調用 UserEBIFactory.getUserEBI() | UI 不含業務規則、DAO 細節完全隱藏 |
| 模型驅動 UI | JList 直接顯示 UserModel.toString() | 資料與視圖綁定簡單、自動刷新 |
| 輸入驗證 | 密碼雙輸入比對、UUID 衝突檢查 | 前端攔截基礎錯誤、減少業務層異常 |
SwingWorker 背景執行。null 佈局 (setBounds) 易上手,但**生產必須用** BorderLayout/GridBagLayout/MigLayout 實現自適應。removeNotify() 或自定義 dispose() 清理。UIConstants 類別集中管理。user 模組完整結構: ├── vo/ # UserModel, UserQueryModel (Serializable) ├── common/ # UserTypeEnum (權限枚舉 + 反向查找) ├── dao/ │ ├── dao/ # UserDAO (介面) │ ├── impl/ # UserDAOFileImpl (序列化檔案實作) │ └── factory/ # UserDAOFactory ├── business/ │ ├── ebi/ # UserEBI (業務介面) │ ├── ebo/ # UserEBO (業務實作 + UUID 整合) │ └── factory/ # UserEBIFactory └── ui/ # listPanel, AddPanel, UpdatePanel, DeletePanel, QueryPanel
用戶模組展示了 **Swing 面板卡片切換**、**分層架構 (UI/EBI/DAO/VO)**、**枚舉驅動權限**、**檔案序列化持久化**、**工廠模式解耦** 的完整教學級實作。雖簡化了密碼加密、併發控制、異常處理等生產細節,但架構骨架清晰、擴展性強,是進階學習 Java 桌面/企業級開發的優秀範本。