Преглед изворни кода

feat:完善所有功能的页面编写和服务端API编写

yangyi пре 1 месец
родитељ
комит
226d0c64a8
35 измењених фајлова са 1529 додато и 414 уклоњено
  1. 5 4
      client/src/main/java/space/anyi/client/App.java
  2. 4 7
      client/src/main/java/space/anyi/client/controller/LoginController.java
  3. 607 129
      client/src/main/java/space/anyi/client/controller/MainController.java
  4. 7 13
      client/src/main/java/space/anyi/client/controller/RegisterController.java
  5. 4 0
      client/src/main/java/space/anyi/client/service/BookService.java
  6. 11 0
      client/src/main/java/space/anyi/client/service/TransactionService.java
  7. 6 0
      client/src/main/java/space/anyi/client/service/UserService.java
  8. 14 2
      client/src/main/java/space/anyi/client/util/HttpClientUtil.java
  9. 322 4
      client/src/main/resources/css/style.css
  10. 146 0
      client/src/main/resources/fxml/admin.fxml
  11. 60 0
      client/src/main/resources/fxml/browse.fxml
  12. 20 8
      client/src/main/resources/fxml/login.fxml
  13. 16 231
      client/src/main/resources/fxml/main.fxml
  14. 44 0
      client/src/main/resources/fxml/profile.fxml
  15. 35 12
      client/src/main/resources/fxml/register.fxml
  16. 69 0
      client/src/main/resources/fxml/seller.fxml
  17. 57 0
      client/src/main/resources/fxml/stats.fxml
  18. 28 0
      client/src/main/resources/fxml/trans.fxml
  19. 4 0
      common/pom.xml
  20. 3 0
      common/src/main/java/space/anyi/common/dto/LoginRequest.java
  21. 3 0
      common/src/main/java/space/anyi/common/dto/RegisterRequest.java
  22. 14 0
      common/src/main/java/space/anyi/common/dto/UserTransactionDTO.java
  23. 1 0
      pom.xml
  24. 4 0
      server/pom.xml
  25. 5 0
      server/src/main/java/space/anyi/server/controller/BookController.java
  26. 6 0
      server/src/main/java/space/anyi/server/controller/TransactionController.java
  27. 2 0
      server/src/main/java/space/anyi/server/controller/UserController.java
  28. 3 0
      server/src/main/java/space/anyi/server/mapper/BookMapper.java
  29. 7 0
      server/src/main/java/space/anyi/server/mapper/TransactionRecordMapper.java
  30. 1 0
      server/src/main/java/space/anyi/server/service/BookService.java
  31. 2 0
      server/src/main/java/space/anyi/server/service/TransactionService.java
  32. 5 0
      server/src/main/java/space/anyi/server/service/impl/BookServiceImpl.java
  33. 9 1
      server/src/main/java/space/anyi/server/service/impl/TransactionServiceImpl.java
  34. 2 2
      server/src/main/java/space/anyi/server/service/impl/UserServiceImpl.java
  35. 3 1
      sql/campus_book_trade.sql

+ 5 - 4
client/src/main/java/space/anyi/client/App.java

@@ -18,28 +18,29 @@ public class App extends Application {
         stage.show();
     }
 
-    private static void addBootstrapFX(Scene scene) {
+    private static void addStylesheets(Scene scene) {
         scene.getStylesheets().add("org/kordamp/bootstrapfx/bootstrapfx.css");
+        scene.getStylesheets().add("/css/style.css");
     }
 
     public static void showLoginScene() throws Exception {
         FXMLLoader loader = new FXMLLoader(App.class.getResource("/fxml/login.fxml"));
         Scene scene = new Scene(loader.load());
-        addBootstrapFX(scene);
+        addStylesheets(scene);
         primaryStage.setScene(scene);
     }
 
     public static void showRegisterScene() throws Exception {
         FXMLLoader loader = new FXMLLoader(App.class.getResource("/fxml/register.fxml"));
         Scene scene = new Scene(loader.load());
-        addBootstrapFX(scene);
+        addStylesheets(scene);
         primaryStage.setScene(scene);
     }
 
     public static void showMainScene() throws Exception {
         FXMLLoader loader = new FXMLLoader(App.class.getResource("/fxml/main.fxml"));
         Scene scene = new Scene(loader.load());
-        addBootstrapFX(scene);
+        addStylesheets(scene);
         primaryStage.setScene(scene);
     }
 

+ 4 - 7
client/src/main/java/space/anyi/client/controller/LoginController.java

@@ -4,7 +4,7 @@ import javafx.fxml.FXML;
 import javafx.scene.control.PasswordField;
 import javafx.scene.control.TextField;
 import javafx.scene.control.Alert;
-import javafx.scene.control.TextArea;
+import javafx.scene.control.Label;
 import space.anyi.client.App;
 import space.anyi.client.service.UserService;
 import space.anyi.client.util.LoginUserHolder;
@@ -54,12 +54,9 @@ public class LoginController {
         Alert alert = new Alert(Alert.AlertType.WARNING);
         alert.setTitle("提示");
         alert.setHeaderText(null);
-        TextArea textArea = new TextArea(message);
-        textArea.setEditable(false);
-        textArea.setWrapText(true);
-        textArea.setPrefWidth(360);
-        textArea.setPrefHeight(80);
-        alert.getDialogPane().setContent(textArea);
+        Label label = new Label(message);
+        label.setWrapText(true);
+        alert.getDialogPane().setContent(label);
         alert.showAndWait();
     }
 }

Разлика између датотеке није приказан због своје велике величине
+ 607 - 129
client/src/main/java/space/anyi/client/controller/MainController.java


+ 7 - 13
client/src/main/java/space/anyi/client/controller/RegisterController.java

@@ -4,7 +4,7 @@ import javafx.fxml.FXML;
 import javafx.scene.control.PasswordField;
 import javafx.scene.control.TextField;
 import javafx.scene.control.Alert;
-import javafx.scene.control.TextArea;
+import javafx.scene.control.Label;
 import space.anyi.client.App;
 import space.anyi.client.service.UserService;
 import space.anyi.common.dto.R;
@@ -70,12 +70,9 @@ public class RegisterController {
         Alert alert = new Alert(Alert.AlertType.WARNING);
         alert.setTitle("提示");
         alert.setHeaderText(null);
-        TextArea textArea = new TextArea(message);
-        textArea.setEditable(false);
-        textArea.setWrapText(true);
-        textArea.setPrefWidth(360);
-        textArea.setPrefHeight(80);
-        alert.getDialogPane().setContent(textArea);
+        Label label = new Label(message);
+        label.setWrapText(true);
+        alert.getDialogPane().setContent(label);
         alert.showAndWait();
     }
 
@@ -83,12 +80,9 @@ public class RegisterController {
         Alert alert = new Alert(Alert.AlertType.INFORMATION);
         alert.setTitle("成功");
         alert.setHeaderText(null);
-        TextArea textArea = new TextArea(message);
-        textArea.setEditable(false);
-        textArea.setWrapText(true);
-        textArea.setPrefWidth(360);
-        textArea.setPrefHeight(80);
-        alert.getDialogPane().setContent(textArea);
+        Label label = new Label(message);
+        label.setWrapText(true);
+        alert.getDialogPane().setContent(label);
         alert.showAndWait();
     }
 }

+ 4 - 0
client/src/main/java/space/anyi/client/service/BookService.java

@@ -51,6 +51,10 @@ public class BookService {
         return HttpClientUtil.get("/api/book/" + bookId + "/sales", new TypeReference<R<List<SalesRecordDTO>>>() {});
     }
 
+    public R<Integer> getSalesSummary(Integer sellerId) {
+        return HttpClientUtil.get("/api/book/sales-summary?sellerId=" + sellerId, new TypeReference<R<Integer>>() {});
+    }
+
     public R<List<Book>> getPendingBooks() {
         return HttpClientUtil.get("/api/book/pending", new TypeReference<R<List<Book>>>() {});
     }

+ 11 - 0
client/src/main/java/space/anyi/client/service/TransactionService.java

@@ -1,7 +1,9 @@
 package space.anyi.client.service;
 
 import com.fasterxml.jackson.core.type.TypeReference;
+import space.anyi.common.dto.PageResult;
 import space.anyi.common.dto.R;
+import space.anyi.common.dto.UserTransactionDTO;
 import space.anyi.common.entity.TransactionRecord;
 import space.anyi.client.util.HttpClientUtil;
 import java.util.List;
@@ -26,6 +28,15 @@ public class TransactionService {
         return HttpClientUtil.get("/api/transaction/user/" + userId, new TypeReference<R<List<TransactionRecord>>>() {});
     }
 
+    public R<List<UserTransactionDTO>> getByUserWithDetail(Integer userId) {
+        return HttpClientUtil.get("/api/transaction/user/" + userId + "/detail", new TypeReference<R<List<UserTransactionDTO>>>() {});
+    }
+
+    public R<PageResult<TransactionRecord>> listAll(int page, int size) {
+        return HttpClientUtil.get("/api/transaction?page=" + page + "&size=" + size,
+                new TypeReference<R<PageResult<TransactionRecord>>>() {});
+    }
+
     public R<TransactionRecord> update(Integer id, TransactionRecord record) {
         return HttpClientUtil.put("/api/transaction/" + id, record, TransactionRecord.class);
     }

+ 6 - 0
client/src/main/java/space/anyi/client/service/UserService.java

@@ -1,6 +1,7 @@
 package space.anyi.client.service;
 
 import com.fasterxml.jackson.core.type.TypeReference;
+import space.anyi.common.dto.PageResult;
 import space.anyi.common.dto.R;
 import space.anyi.common.dto.LoginRequest;
 import space.anyi.common.dto.RegisterRequest;
@@ -24,6 +25,11 @@ public class UserService {
         return HttpClientUtil.get("/api/user/" + id, User.class);
     }
 
+    public R<PageResult<User>> listAll(int page, int size) {
+        return HttpClientUtil.get("/api/user?page=" + page + "&size=" + size,
+                new TypeReference<R<PageResult<User>>>() {});
+    }
+
     public R<User> update(Integer id, User user) {
         return HttpClientUtil.put("/api/user/" + id, user, User.class);
     }

+ 14 - 2
client/src/main/java/space/anyi/client/util/HttpClientUtil.java

@@ -62,10 +62,15 @@ public class HttpClientUtil {
 
             HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
             return mapper.readValue(response.body(), mapper.getTypeFactory().constructParametricType(R.class, responseType));
+        } catch (java.net.ConnectException e) {
+            R<T> r = new R<>();
+            r.setCode(500);
+            r.setMessage("无法连接到服务器,请检查服务是否已启动");
+            return r;
         } catch (Exception e) {
             R<T> r = new R<>();
             r.setCode(500);
-            r.setMessage("Connection error: " + e.getMessage());
+            r.setMessage("请求失败:" + e.getMessage());
             return r;
         }
     }
@@ -80,16 +85,23 @@ public class HttpClientUtil {
 
             HttpRequest request = switch (method) {
                 case "GET" -> builder.GET().build();
+                case "DELETE" -> builder.method("DELETE", HttpRequest.BodyPublishers.noBody()).build();
                 case "POST" -> builder.POST(HttpRequest.BodyPublishers.ofString(jsonBody)).build();
+                case "PUT" -> builder.PUT(HttpRequest.BodyPublishers.ofString(jsonBody)).build();
                 default -> throw new IllegalArgumentException("Unsupported method: " + method);
             };
 
             HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
             return mapper.readValue(response.body(), typeRef);
+        } catch (java.net.ConnectException e) {
+            R<T> r = new R<>();
+            r.setCode(500);
+            r.setMessage("无法连接到服务器,请检查服务是否已启动");
+            return r;
         } catch (Exception e) {
             R<T> r = new R<>();
             r.setCode(500);
-            r.setMessage("Connection error: " + e.getMessage());
+            r.setMessage("请求失败:" + e.getMessage());
             return r;
         }
     }

+ 322 - 4
client/src/main/resources/css/style.css

@@ -1,13 +1,331 @@
+/* ── 品牌色覆盖 ── */
 .root {
-    -fx-font-family: "System";
-    -fx-font-size: 13px;
+    -fx-primary: #4F6CF7;
+    -fx-primary-hover: #3B5DE7;
+    -fx-primary-light: #EEF2FF;
+    -fx-success: #10B981;
+    -fx-warning: #F59E0B;
+    -fx-danger: #EF4444;
+    -fx-info: #3B82F6;
+    -fx-secondary: #6C63FF;
+    -fx-bg: #F5F7FA;
+    -fx-bg-card: #FFFFFF;
+    -fx-text: #0F172A;
+    -fx-text-secondary: #64748B;
+    -fx-text-muted: #94A3B8;
+    -color-border: #E2E8F0;
+    -fx-shadow: dropshadow(gaussian, rgba(0,0,0,0.06), 6, 0, 0, 2);
+    -fx-shadow-lg: dropshadow(gaussian, rgba(0,0,0,0.1), 12, 0, 0, 4);
+}
+
+/* ── 全局 ── */
+.root {
+    -fx-background-color: -fx-bg;
+    -fx-font-family: "Microsoft YaHei", "PingFang SC", "System";
+    -fx-font-size: 14px;
 }
 
 .button {
     -fx-cursor: hand;
+    -fx-background-radius: 6px;
+    -fx-padding: 8px 20px;
+    -fx-font-size: 14px;
 }
 
+/* ── Navbar ── */
+.navbar {
+    -fx-background-color: -fx-bg-card;
+    -fx-padding: 0 8px;
+    -fx-min-height: 52px;
+    -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.06), 4, 0, 0, 2);
+}
+.navbar .hyperlink {
+    -fx-font-size: 14px;
+    -fx-text-fill: -fx-text-secondary;
+    -fx-border-width: 0;
+    -fx-padding: 16px 18px;
+    -fx-background-color: transparent;
+    -fx-border-color: transparent;
+    -fx-underline: false;
+}
+.navbar .hyperlink:hover {
+    -fx-text-fill: -fx-primary;
+    -fx-background-color: -fx-primary-light;
+}
+.navbar .hyperlink.nav-active {
+    -fx-text-fill: -fx-primary;
+    -fx-font-weight: bold;
+    -fx-border-color: transparent transparent -fx-primary transparent;
+    -fx-border-width: 0 0 2px 0;
+}
+.navbar .btn-danger {
+    -fx-background-color: transparent;
+    -fx-text-fill: -fx-text-secondary;
+    -fx-border-color: -color-border;
+    -fx-border-radius: 6px;
+}
+.navbar .btn-danger:hover {
+    -fx-background-color: #FEE2E2;
+    -fx-text-fill: -fx-danger;
+    -fx-border-color: transparent;
+}
+/* ── TableView ── */
 .table-view {
-    -fx-border-color: #bdc3c7;
-    -fx-border-radius: 3;
+    -fx-background-color: transparent;
+    -fx-background-radius: 8px;
+    -fx-border-color: -color-border;
+    -fx-border-radius: 8px;
+    -fx-padding: 0;
+    -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.04), 4, 0, 0, 1);
+}
+.table-view .column-header-background {
+    -fx-background-color: #F8FAFC;
+    -fx-background-radius: 8px 8px 0 0;
+}
+.table-view .column-header {
+    -fx-background-color: transparent;
+    -fx-border-width: 0;
+    -fx-padding: 12px 16px;
+    -fx-font-weight: bold;
+    -fx-text-fill: -fx-text-secondary;
+    -fx-font-size: 13px;
+}
+.table-view .column-header .label {
+    -fx-text-fill: -fx-text-secondary;
+    -fx-font-weight: bold;
+}
+.table-view .table-row-cell {
+    -fx-background-color: transparent;
+    -fx-padding: 4px 0;
+    -fx-border-color: transparent transparent #F1F5F9 transparent;
+    -fx-table-cell-border-color: transparent;
+}
+.table-view .table-row-cell:odd {
+    -fx-background-color: #FAFBFC;
+}
+.table-view .table-row-cell:hover {
+    -fx-background-color: -fx-primary-light;
+}
+.table-view .table-row-cell:selected {
+    -fx-background-color: -fx-primary-light;
+    -fx-text-fill: -fx-text;
+}
+.table-view .table-cell {
+    -fx-padding: 8px 16px;
+    -fx-border-width: 0;
+}
+
+/* ── TitledPane ── */
+.titled-pane {
+    -fx-background-color: -fx-bg-card;
+    -fx-background-radius: 10px;
+    -fx-border-color: -color-border;
+    -fx-border-radius: 10px;
+    -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.04), 4, 0, 0, 1);
+}
+.titled-pane > .title {
+    -fx-background-color: transparent;
+    -fx-padding: 14px 20px;
+    -fx-font-size: 15px;
+    -fx-font-weight: bold;
+}
+
+/* ── ScrollPane ── */
+.scroll-pane {
+    -fx-background-color: transparent;
+    -fx-border-width: 0;
+}
+.scroll-pane .viewport {
+    -fx-background-color: transparent;
+}
+
+/* ── Progress Bar ── */
+.progress-bar {
+    -fx-background-color: #F1F5F9;
+    -fx-background-radius: 20px;
+    -fx-min-height: 8px;
+    -fx-max-height: 8px;
+}
+.progress-bar .bar {
+    -fx-background-color: -fx-primary;
+    -fx-background-radius: 20px;
+}
+
+/* ── Label 变体颜色 ── */
+.label-success {
+    -fx-background-color: #D1FAE5;
+    -fx-text-fill: #065F46;
+}
+.label-warning {
+    -fx-background-color: #FEF3C7;
+    -fx-text-fill: #92400E;
+}
+.label-danger {
+    -fx-background-color: #FEE2E2;
+    -fx-text-fill: #991B1B;
+}
+.label-info {
+    -fx-background-color: #DBEAFE;
+    -fx-text-fill: #1E40AF;
+}
+.label-primary {
+    -fx-background-color: #EEF2FF;
+    -fx-text-fill: -fx-primary;
+}
+
+/* ── KPI 卡片 ── */
+.kpi-card {
+    -fx-background-color: -fx-bg-card;
+    -fx-background-radius: 10px;
+    -fx-padding: 20px 24px;
+    -fx-border-color: -color-border;
+    -fx-border-radius: 10px;
+    -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.04), 6, 0, 0, 2);
+}
+.kpi-card .kpi-value {
+    -fx-font-size: 28px;
+    -fx-font-weight: bold;
+}
+.kpi-card .kpi-label {
+    -fx-text-fill: -fx-text-secondary;
+    -fx-font-size: 13px;
+}
+
+/* ── 图书卡片网格 ── */
+.book-card {
+    -fx-background-color: -fx-bg-card;
+    -fx-background-radius: 10px;
+    -fx-padding: 16px;
+    -fx-border-color: -color-border;
+    -fx-border-radius: 10px;
+    -fx-min-width: 200;
+    -fx-max-width: 220;
+    -fx-spacing: 8;
+    -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.04), 4, 0, 0, 1);
+    -fx-cursor: hand;
+}
+.book-card:hover {
+    -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.12), 12, 0, 0, 4);
+    -fx-translate-y: -2px;
+}
+.book-card .book-cover {
+    -fx-background-radius: 8px;
+    -fx-min-height: 120;
+    -fx-max-height: 120;
+    -fx-alignment: center;
+}
+.book-card .book-title {
+    -fx-font-size: 15px;
+    -fx-font-weight: bold;
+    -fx-text-fill: -fx-text;
+}
+.book-card .book-author {
+    -fx-font-size: 13px;
+    -fx-text-fill: -fx-text-secondary;
+}
+.book-card .book-price {
+    -fx-font-size: 20px;
+    -fx-font-weight: bold;
+    -fx-text-fill: -fx-primary;
+}
+.book-card .book-original-price {
+    -fx-font-size: 13px;
+    -fx-text-fill: -fx-text-muted;
+    -fx-strikethrough: true;
+}
+.book-card .book-stock {
+    -fx-font-size: 12px;
+}
+.book-card .book-status {
+    -fx-background-radius: 4px;
+    -fx-padding: 2px 8px;
+    -fx-font-size: 11px;
+}
+
+/* ── 详情面板 ── */
+.detail-panel {
+    -fx-background-color: -fx-bg-card;
+    -fx-background-radius: 10px;
+    -fx-padding: 24px;
+    -fx-border-color: -color-border;
+    -fx-border-radius: 10px;
+    -fx-spacing: 12;
+}
+.detail-panel .detail-title {
+    -fx-font-size: 22px;
+    -fx-font-weight: bold;
+}
+.detail-panel .detail-price {
+    -fx-font-size: 24px;
+    -fx-font-weight: bold;
+    -fx-text-fill: -fx-primary;
+}
+.detail-panel .detail-label {
+    -fx-text-fill: -fx-text-secondary;
+    -fx-font-size: 13px;
+}
+
+/* ── 空状态 ── */
+.empty-state {
+    -fx-alignment: center;
+    -fx-padding: 60px 20px;
+    -fx-spacing: 16;
+}
+.empty-state .empty-icon {
+    -fx-font-size: 48px;
+    -fx-text-fill: -fx-text-muted;
+}
+.empty-state .empty-text {
+    -fx-font-size: 16px;
+    -fx-text-fill: -fx-text-secondary;
+}
+
+/* ── Form 表单 ── */
+.form-label {
+    -fx-font-size: 14px;
+    -fx-font-weight: bold;
+    -fx-text-fill: -fx-text;
+    -fx-alignment: CENTER_LEFT;
+}
+.form-control {
+    -fx-background-radius: 6px;
+    -fx-border-radius: 6px;
+    -fx-pref-height: 38px;
+    -fx-padding: 8px 14px;
+    -fx-border-color: -color-border;
+    -fx-background-color: -fx-bg-card;
+}
+.form-control:focused {
+    -fx-border-color: -fx-primary;
+    -fx-effect: dropshadow(gaussian, rgba(79,108,247,0.15), 4, 0, 0, 0);
+}
+
+/* ── 分割线 ── */
+.separator {
+    -fx-background-color: -color-border;
+}
+
+/* ── 统计进度条行 ── */
+.stat-row {
+    -fx-padding: 6px 0;
+    -fx-alignment: center-left;
+}
+.stat-row .stat-name {
+    -fx-font-size: 14px;
+}
+.stat-row .stat-bar-bg {
+    -fx-background-color: #F1F5F9;
+    -fx-background-radius: 20px;
+    -fx-min-height: 8px;
+    -fx-max-height: 8px;
+}
+.stat-row .stat-bar-fill {
+    -fx-background-radius: 20px;
+    -fx-min-height: 8px;
+    -fx-max-height: 8px;
+}
+.stat-row .stat-value {
+    -fx-font-size: 14px;
+    -fx-font-weight: bold;
+    -fx-text-fill: -fx-text;
 }

+ 146 - 0
client/src/main/resources/fxml/admin.fxml

@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.geometry.*?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<ScrollPane fx:id="adminPane" xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1" visible="false" managed="false" fitToWidth="true">
+    <VBox spacing="16">
+        <padding><Insets top="16" right="16" bottom="16" left="16"/></padding>
+
+        <VBox styleClass="well" spacing="12">
+            <HBox spacing="10" alignment="CENTER_LEFT">
+                <Label text="👥 用户管理" style="-fx-font-size: 16px; -fx-font-weight: bold;" HBox.hgrow="ALWAYS"/>
+                <Button text="刷新" onAction="#handleLoadAllUsers" styleClass="btn,btn-secondary"/>
+                <Button text="+ 添加" onAction="#handleToggleUserForm" styleClass="btn,btn-success"/>
+            </HBox>
+            <TableView fx:id="userTable" prefHeight="160">
+                <columns>
+                    <TableColumn fx:id="userIdCol" text="ID" prefWidth="40"/>
+                    <TableColumn fx:id="userNameCol" text="用户名" prefWidth="100"/>
+                    <TableColumn fx:id="userNicknameCol" text="昵称" prefWidth="100"/>
+                    <TableColumn fx:id="userDeptCol" text="院系" prefWidth="120"/>
+                    <TableColumn fx:id="userCreditCol" text="信用" prefWidth="60"/>
+                    <TableColumn fx:id="userRoleCol" text="角色" prefWidth="60"/>
+                    <TableColumn fx:id="userSellerCol" text="卖家" prefWidth="60"/>
+                    <TableColumn text="操作" prefWidth="220"/>
+                </columns>
+            </TableView>
+            <HBox spacing="8">
+                <Button text="编辑选中" onAction="#handleEditUser" styleClass="btn,btn-warning"/>
+                <Button text="重置密码" onAction="#handleResetPassword" styleClass="btn,btn-secondary"/>
+                <Button text="修改信用分" onAction="#handleEditCredit" styleClass="btn,btn-info"/>
+                <Button text="删除选中" onAction="#handleDeleteUser" styleClass="btn,btn-danger"/>
+            </HBox>
+            <HBox spacing="8" alignment="CENTER">
+                <Button fx:id="adminUserPrevBtn" text="← 上一页" onAction="#handleAdminUserPrev" styleClass="btn,btn-secondary"/>
+                <Label fx:id="adminUserPageLabel" text="第 1 页"/>
+                <Button fx:id="adminUserNextBtn" text="下一页 →" onAction="#handleAdminUserNext" styleClass="btn,btn-secondary"/>
+            </HBox>
+            <TitledPane fx:id="userFormPane" text="添加用户" expanded="false" animated="true">
+                <GridPane hgap="16" vgap="10">
+                    <padding><Insets top="12" right="16" bottom="12" left="16"/></padding>
+                    <columnConstraints>
+                        <ColumnConstraints prefWidth="70"/>
+                        <ColumnConstraints hgrow="ALWAYS" prefWidth="200"/>
+                        <ColumnConstraints prefWidth="30"/>
+                        <ColumnConstraints prefWidth="70"/>
+                        <ColumnConstraints hgrow="ALWAYS" prefWidth="200"/>
+                    </columnConstraints>
+                    <Label text="用户名" GridPane.rowIndex="0" GridPane.columnIndex="0"/>
+                    <TextField fx:id="adminUsername" styleClass="form-control" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
+                    <Label text="密码" GridPane.rowIndex="0" GridPane.columnIndex="3"/>
+                    <PasswordField fx:id="adminPassword" styleClass="form-control" GridPane.rowIndex="0" GridPane.columnIndex="4"/>
+                    <Label text="昵称" GridPane.rowIndex="1" GridPane.columnIndex="0"/>
+                    <TextField fx:id="adminNickname" styleClass="form-control" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
+                    <Label text="邮箱" GridPane.rowIndex="1" GridPane.columnIndex="3"/>
+                    <TextField fx:id="adminEmail" styleClass="form-control" GridPane.rowIndex="1" GridPane.columnIndex="4"/>
+                    <Label text="电话" GridPane.rowIndex="2" GridPane.columnIndex="0"/>
+                    <TextField fx:id="adminPhone" styleClass="form-control" GridPane.rowIndex="2" GridPane.columnIndex="1"/>
+                    <Label text="院系" GridPane.rowIndex="2" GridPane.columnIndex="3"/>
+                    <TextField fx:id="adminDept" styleClass="form-control" GridPane.rowIndex="2" GridPane.columnIndex="4"/>
+                    <Label text="角色" GridPane.rowIndex="3" GridPane.columnIndex="0"/>
+                    <ComboBox fx:id="adminRole" styleClass="form-control" GridPane.rowIndex="3" GridPane.columnIndex="1"/>
+                    <Label text="卖家" GridPane.rowIndex="3" GridPane.columnIndex="3"/>
+                    <CheckBox fx:id="adminSeller" GridPane.rowIndex="3" GridPane.columnIndex="4"/>
+                    <HBox spacing="10" GridPane.rowIndex="4" GridPane.columnIndex="4" alignment="CENTER_RIGHT">
+                        <Button text="取消" onAction="#handleCancelUserForm" styleClass="btn,btn-secondary"/>
+                        <Button fx:id="saveUserBtn" text="保存" onAction="#handleSaveUser" styleClass="btn,btn-success"/>
+                    </HBox>
+                </GridPane>
+            </TitledPane>
+        </VBox>
+
+        <VBox styleClass="well" spacing="12">
+            <HBox spacing="10" alignment="CENTER_LEFT">
+                <Label text="📚 书籍管理" style="-fx-font-size: 16px; -fx-font-weight: bold;" HBox.hgrow="ALWAYS"/>
+                <Button text="刷新" onAction="#handleLoadAllBooksAdmin" styleClass="btn,btn-secondary"/>
+            </HBox>
+            <TableView fx:id="adminBookTable" prefHeight="160">
+                <columns>
+                    <TableColumn fx:id="adminBookIdCol" text="ID" prefWidth="40"/>
+                    <TableColumn fx:id="adminBookTitleCol" text="书名" prefWidth="160"/>
+                    <TableColumn fx:id="adminBookAuthorCol" text="作者" prefWidth="100"/>
+                    <TableColumn fx:id="adminBookCategoryCol" text="分类" prefWidth="70"/>
+                    <TableColumn fx:id="adminBookPriceCol" text="售价" prefWidth="80"/>
+                    <TableColumn fx:id="adminBookStockCol" text="库存" prefWidth="60"/>
+                    <TableColumn fx:id="adminBookSellerCol" text="卖家" prefWidth="50"/>
+                </columns>
+            </TableView>
+            <Button text="删除选中书籍" onAction="#handleDeleteBookAdmin" styleClass="btn,btn-danger"/>
+        </VBox>
+
+        <VBox styleClass="well" spacing="12">
+            <HBox spacing="10" alignment="CENTER_LEFT">
+                <Label text="⏳ 待审核书籍" style="-fx-font-size: 16px; -fx-font-weight: bold;" HBox.hgrow="ALWAYS"/>
+                <Button text="刷新" onAction="#handleLoadPendingBooks" styleClass="btn,btn-secondary"/>
+            </HBox>
+            <TableView fx:id="pendingBookTable" prefHeight="150">
+                <columns>
+                    <TableColumn fx:id="pendingBookIdCol" text="ID" prefWidth="40"/>
+                    <TableColumn fx:id="pendingBookTitleCol" text="书名" prefWidth="180"/>
+                    <TableColumn fx:id="pendingBookAuthorCol" text="作者" prefWidth="120"/>
+                    <TableColumn fx:id="pendingBookSellerCol" text="卖家" prefWidth="60"/>
+                    <TableColumn fx:id="pendingBookPriceCol" text="售价" prefWidth="80"/>
+                </columns>
+            </TableView>
+            <HBox spacing="8">
+                <Button text="通过" onAction="#handleApproveBook" prefWidth="120" styleClass="btn,btn-success"/>
+                <Button text="拒绝" onAction="#handleRejectBook" prefWidth="120" styleClass="btn,btn-danger"/>
+            </HBox>
+        </VBox>
+
+        <VBox styleClass="well" spacing="12">
+            <HBox spacing="10" alignment="CENTER_LEFT">
+                <Label text="💳 交易管理" style="-fx-font-size: 16px; -fx-font-weight: bold;" HBox.hgrow="ALWAYS"/>
+                <Button text="刷新" onAction="#handleLoadAllTransactions" styleClass="btn,btn-secondary"/>
+            </HBox>
+            <TableView fx:id="allTransTable" prefHeight="160">
+                <columns>
+                    <TableColumn fx:id="allTransIdCol" text="ID" prefWidth="50"/>
+                    <TableColumn fx:id="allTransBookIdCol" text="书籍" prefWidth="60"/>
+                    <TableColumn fx:id="allTransBuyerIdCol" text="买家" prefWidth="60"/>
+                    <TableColumn fx:id="allTransNumCol" text="数量" prefWidth="50"/>
+                    <TableColumn fx:id="allTransPriceCol" text="总价" prefWidth="100"/>
+                    <TableColumn fx:id="allTransTimeCol" text="时间" prefWidth="180"/>
+                </columns>
+            </TableView>
+            <HBox spacing="8" alignment="CENTER">
+                <Button fx:id="adminTransPrevBtn" text="← 上一页" onAction="#handleAdminTransPrev" styleClass="btn,btn-secondary"/>
+                <Label fx:id="adminTransPageLabel" text="第 1 页"/>
+                <Button fx:id="adminTransNextBtn" text="下一页 →" onAction="#handleAdminTransNext" styleClass="btn,btn-secondary"/>
+            </HBox>
+            <Button text="删除选中交易" onAction="#handleDeleteTransaction" styleClass="btn,btn-danger"/>
+        </VBox>
+
+        <VBox styleClass="well" spacing="12">
+            <HBox spacing="10" alignment="CENTER_LEFT">
+                <Label text="🤖 AI 分析" style="-fx-font-size: 16px; -fx-font-weight: bold;" HBox.hgrow="ALWAYS"/>
+                <Button text="生成分析报告" onAction="#handleGenerateAnalysis" styleClass="btn,btn-primary"/>
+            </HBox>
+            <TextArea fx:id="analysisResult" editable="false" wrapText="true" prefHeight="200" styleClass="form-control"/>
+        </VBox>
+
+        <Label text=" " prefHeight="20"/>
+    </VBox>
+</ScrollPane>

+ 60 - 0
client/src/main/resources/fxml/browse.fxml

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.geometry.*?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<VBox fx:id="browsePane" xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1">
+    <HBox spacing="10" styleClass="well" style="-fx-background-radius: 0; -fx-border: none; -fx-effect: none;">
+        <padding><Insets top="12" right="16" bottom="12" left="16"/></padding>
+        <TextField fx:id="searchField" promptText="搜索书名、作者..." styleClass="form-control" HBox.hgrow="ALWAYS"/>
+        <ComboBox fx:id="categoryCombo" prefWidth="140" styleClass="form-control"/>
+        <Button text="搜索" onAction="#handleSearch" styleClass="btn,btn-primary" prefWidth="90"/>
+    </HBox>
+    <SplitPane dividerPositions="0.68" VBox.vgrow="ALWAYS">
+        <ScrollPane fitToWidth="true" hbarPolicy="NEVER" VBox.vgrow="ALWAYS">
+            <TilePane fx:id="bookCardGrid" prefTileWidth="210" prefTileHeight="240" hgap="16" vgap="16" styleClass="well" style="-fx-background-radius: 0; -fx-border: none; -fx-effect: none;">
+                <padding><Insets top="16" right="16" bottom="16" left="16"/></padding>
+            </TilePane>
+        </ScrollPane>
+        <VBox fx:id="detailPanel" spacing="16" styleClass="detail-panel" visible="false" managed="false">
+            <padding><Insets top="20" right="16" bottom="20" left="16"/></padding>
+            <HBox alignment="CENTER_LEFT" spacing="8">
+                <Label fx:id="detailTitle" text="选择一本书" styleClass="detail-title" HBox.hgrow="ALWAYS"/>
+                <Button text="✕" onAction="#handleCloseDetail" styleClass="btn,btn-secondary" style="-fx-padding: 4px 10px; -fx-font-size: 14px;"/>
+            </HBox>
+            <HBox spacing="8">
+                <Label fx:id="detailCategory" styleClass="label,label-primary" text="-"/>
+                <Label fx:id="detailStock" styleClass="label" text="-"/>
+            </HBox>
+            <Label text="作者" styleClass="detail-label"/>
+            <Label fx:id="detailAuthor" text="-"/>
+            <Separator/>
+            <HBox spacing="12" alignment="CENTER_LEFT">
+                <VBox>
+                    <Label text="原价" styleClass="detail-label"/>
+                    <Label fx:id="detailOriginalPrice" styleClass="book-original-price" text="-"/>
+                </VBox>
+                <VBox>
+                    <Label text="售价" styleClass="detail-label"/>
+                    <Label fx:id="detailPrice" styleClass="detail-price" text="-"/>
+                </VBox>
+            </HBox>
+            <Separator/>
+            <HBox spacing="8" alignment="CENTER_LEFT">
+                <Label text="数量" styleClass="detail-label"/>
+                <TextField fx:id="quantityField" text="1" styleClass="form-control" maxWidth="80"/>
+            </HBox>
+            <Button text="立即购买" onAction="#handlePurchase" maxWidth="Infinity" styleClass="btn,btn-success" prefHeight="42"/>
+        </VBox>
+    </SplitPane>
+    <HBox spacing="10" alignment="CENTER" styleClass="well" style="-fx-background-radius: 0; -fx-border: none; -fx-effect: none;">
+        <padding><Insets top="8" right="16" bottom="8" left="16"/></padding>
+        <Button fx:id="prevPageBtn" text="‹ 上一页" onAction="#handlePrevPage" styleClass="btn,btn-secondary" disable="true"/>
+        <Label fx:id="pageInfoLabel" text="第 1 页 / 共 1 页" style="-fx-font-size: 13px; -fx-padding: 0 16px;"/>
+        <Button fx:id="nextPageBtn" text="下一页 ›" onAction="#handleNextPage" styleClass="btn,btn-secondary" disable="true"/>
+        <Region HBox.hgrow="ALWAYS"/>
+        <Label text="每页:" style="-fx-font-size: 13px;"/>
+        <ComboBox fx:id="pageSizeCombo" prefWidth="70"/>
+    </HBox>
+</VBox>

+ 20 - 8
client/src/main/resources/fxml/login.fxml

@@ -4,11 +4,23 @@
 <?import javafx.scene.control.*?>
 <?import javafx.scene.layout.*?>
 
-<VBox alignment="CENTER" spacing="15" xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1" fx:controller="space.anyi.client.controller.LoginController" prefWidth="400" prefHeight="350">
-    <padding><Insets top="40" right="40" bottom="40" left="40"/></padding>
-    <Label text="校园书市" style="-fx-font-size: 22px; -fx-font-weight: bold;"/>
-    <TextField fx:id="usernameField" promptText="用户名" maxWidth="300"/>
-    <PasswordField fx:id="passwordField" promptText="密码" maxWidth="300"/>
-    <Button text="登录" onAction="#handleLogin" maxWidth="300" prefWidth="300" styleClass="btn,btn-primary"/>
-    <Hyperlink text="没有账号?立即注册" onAction="#handleRegister"/>
-</VBox>
+<StackPane xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1" fx:controller="space.anyi.client.controller.LoginController" prefWidth="500" prefHeight="500">
+    <VBox alignment="CENTER" spacing="0">
+        <VBox styleClass="well" maxWidth="380" spacing="20" alignment="CENTER">
+            <padding><Insets top="36" right="36" bottom="36" left="36"/></padding>
+            <Label text="📚" style="-fx-font-size: 40px;"/>
+            <Label text="校园二手书市" style="-fx-font-size: 22px; -fx-font-weight: bold;"/>
+            <Label text="登录你的账号" style="-fx-font-size: 14px;" styleClass="text-muted"/>
+            <HBox spacing="12" alignment="CENTER_LEFT" maxWidth="300">
+                <Label text="用户名" styleClass="form-label" minWidth="50"/>
+                <TextField fx:id="usernameField" promptText="请输入用户名" styleClass="form-control" HBox.hgrow="ALWAYS"/>
+            </HBox>
+            <HBox spacing="12" alignment="CENTER_LEFT" maxWidth="300">
+                <Label text="密码" styleClass="form-label" minWidth="50"/>
+                <PasswordField fx:id="passwordField" promptText="请输入密码" styleClass="form-control" HBox.hgrow="ALWAYS"/>
+            </HBox>
+            <Button text="登录" onAction="#handleLogin" maxWidth="300" prefWidth="300" styleClass="btn,btn-primary"/>
+            <Hyperlink text="没有账号?立即注册" onAction="#handleRegister" style="-fx-font-size: 13px;"/>
+        </VBox>
+    </VBox>
+</StackPane>

+ 16 - 231
client/src/main/resources/fxml/main.fxml

@@ -4,240 +4,25 @@
 <?import javafx.scene.control.*?>
 <?import javafx.scene.layout.*?>
 
-<BorderPane xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1" fx:controller="space.anyi.client.controller.MainController" prefWidth="1000" prefHeight="700">
+<BorderPane xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1" fx:controller="space.anyi.client.controller.MainController" prefWidth="1100" prefHeight="720">
     <top>
-        <HBox alignment="CENTER_LEFT" spacing="10" style="-fx-background-color: #2c3e50; -fx-padding: 10 15;">
-            <Label fx:id="welcomeLabel" text="欢迎" style="-fx-text-fill: white; -fx-font-size: 16px;" HBox.hgrow="ALWAYS"/>
-            <Button text="退出登录" onAction="#handleLogout" styleClass="btn,btn-danger"/>
+        <HBox alignment="CENTER_LEFT" styleClass="navbar" spacing="0">
+            <padding><Insets left="8" right="20"/></padding>
+            <Hyperlink fx:id="navBrowse" text="📖 浏览书籍" onAction="#handleNavBrowse" style="-fx-padding: 16px 18px; -fx-font-size: 14px;"/>
+            <Hyperlink fx:id="navTrans" text="📄 我的交易" onAction="#handleNavTrans" style="-fx-padding: 16px 18px; -fx-font-size: 14px;"/>
+            <Hyperlink fx:id="navSeller" text="🏪 卖家中心" onAction="#handleNavSeller" style="-fx-padding: 16px 18px; -fx-font-size: 14px;"/>
+            <Hyperlink fx:id="navStats" text="📊 数据统计" onAction="#handleNavStats" style="-fx-padding: 16px 18px; -fx-font-size: 14px;"/>
+            <Hyperlink fx:id="navAdmin" text="⚙️ 管理" onAction="#handleNavAdmin" style="-fx-padding: 16px 18px; -fx-font-size: 14px;"/>
+            <Region HBox.hgrow="ALWAYS"/>
+            <MenuButton fx:id="userMenu" style="-fx-font-size: 13px; -fx-padding: 0 12px;">
+                <items>
+                    <MenuItem text="👤 个人信息" onAction="#handleNavProfile"/>
+                    <MenuItem text="退出登录" onAction="#handleLogout"/>
+                </items>
+            </MenuButton>
         </HBox>
     </top>
     <center>
-        <TabPane>
-            <Tab text="浏览书籍" closable="false">
-                <VBox spacing="10">
-                    <padding><Insets top="10" right="10" bottom="10" left="10"/></padding>
-                    <HBox spacing="10">
-                        <TextField fx:id="searchField" promptText="搜索书名/作者..." HBox.hgrow="ALWAYS"/>
-                        <ComboBox fx:id="categoryCombo" prefWidth="150"/>
-                        <Button text="搜索" onAction="#handleSearch" styleClass="btn,btn-info"/>
-                    </HBox>
-                    <SplitPane dividerPositions="0.6" VBox.vgrow="ALWAYS">
-                        <TableView fx:id="bookTable">
-                            <columns>
-                                <TableColumn fx:id="bookIdCol" text="编号" prefWidth="50"/>
-                                <TableColumn fx:id="titleCol" text="书名" prefWidth="200"/>
-                                <TableColumn fx:id="authorCol" text="作者" prefWidth="120"/>
-                                <TableColumn fx:id="categoryCol" text="分类" prefWidth="100"/>
-                                <TableColumn fx:id="priceCol" text="价格" prefWidth="80"/>
-                                <TableColumn fx:id="stockCol" text="库存" prefWidth="60"/>
-                            </columns>
-                        </TableView>
-                        <VBox spacing="10" alignment="TOP_CENTER">
-                            <padding><Insets top="10"/></padding>
-                            <Label text="书籍详情"
-                                   style="-fx-font-weight: bold; -fx-font-size: 16px;"/>
-                            <Label fx:id="detailTitle" text="-"/>
-                            <Label fx:id="detailAuthor" text="-"/>
-                            <Label fx:id="detailCategory" text="-"/>
-                            <Label fx:id="detailPrice" text="-"/>
-                            <Label fx:id="detailStock" text="-"/>
-                            <TextField fx:id="quantityField" promptText="数量" maxWidth="100"/>
-                            <Button text="购买" onAction="#handlePurchase" styleClass="btn,btn-success"/>
-                        </VBox>
-                    </SplitPane>
-                </VBox>
-            </Tab>
-            <Tab text="我的交易" closable="false">
-                <VBox spacing="10">
-                    <padding><Insets top="10" right="10" bottom="10" left="10"/></padding>
-                    <Button text="刷新" onAction="#handleLoadTransactions" styleClass="btn,btn-secondary"/>
-                    <TableView fx:id="transTable" VBox.vgrow="ALWAYS">
-                        <columns>
-                            <TableColumn fx:id="transIdCol" text="交易编号" prefWidth="80"/>
-                            <TableColumn fx:id="transBookIdCol" text="书籍编号" prefWidth="80"/>
-                            <TableColumn fx:id="transNumCol" text="数量" prefWidth="80"/>
-                            <TableColumn fx:id="transPriceCol" text="总价" prefWidth="100"/>
-                            <TableColumn fx:id="transTimeCol" text="时间" prefWidth="200"/>
-                        </columns>
-                    </TableView>
-                </VBox>
-            </Tab>
-            <Tab text="个人信息" closable="false">
-                <VBox spacing="12" maxWidth="400">
-                    <padding><Insets top="20" right="20" bottom="20" left="20"/></padding>
-                    <Label text="个人信息"
-                           style="-fx-font-weight: bold; -fx-font-size: 16px;"/>
-                    <HBox spacing="10"><Label text="用户名:" prefWidth="100"/><TextField fx:id="profileUsername" editable="false"/></HBox>
-                    <HBox spacing="10"><Label text="昵称:" prefWidth="100"/><TextField fx:id="profileNickname"/></HBox>
-                    <HBox spacing="10"><Label text="邮箱:" prefWidth="100"/><TextField fx:id="profileEmail"/></HBox>
-                    <HBox spacing="10"><Label text="电话:" prefWidth="100"/><TextField fx:id="profilePhone"/></HBox>
-                    <HBox spacing="10"><Label text="院系:" prefWidth="100"/><TextField fx:id="profileDept"/></HBox>
-                    <HBox spacing="10"><Label text="信用分:" prefWidth="100"/><Label fx:id="profileCredit"/></HBox>
-                    <Button text="保存修改" onAction="#handleUpdateProfile" styleClass="btn,btn-primary"/>
-                </VBox>
-            </Tab>
-            <Tab fx:id="sellerTab" text="卖家中心" closable="false">
-                <VBox spacing="10">
-                    <padding><Insets top="10" right="10" bottom="10" left="10"/></padding>
-                    <Label text="我的书籍" style="-fx-font-weight: bold;"/>
-                    <Button text="刷新" onAction="#handleLoadMyBooks" styleClass="btn,btn-secondary"/>
-                    <TableView fx:id="myBookTable" VBox.vgrow="ALWAYS">
-                        <columns>
-                            <TableColumn fx:id="myBookIdCol" text="编号" prefWidth="50"/>
-                            <TableColumn fx:id="myBookTitleCol" text="书名" prefWidth="250"/>
-                            <TableColumn fx:id="myBookPriceCol" text="价格" prefWidth="80"/>
-                            <TableColumn fx:id="myBookStockCol" text="库存" prefWidth="60"/>
-                        </columns>
-                    </TableView>
-                    <HBox spacing="10">
-                        <Button text="编辑选中" onAction="#handleEditBook" styleClass="btn,btn-warning"/>
-                        <Button text="查看销售记录" onAction="#handleViewSales" styleClass="btn,btn-info"/>
-                        <Button text="删除选中" onAction="#handleDeleteBook" styleClass="btn,btn-danger"/>
-                    </HBox>
-                    <TitledPane fx:id="bookFormPane" text="添加新书" expanded="false">
-                        <GridPane hgap="10" vgap="8">
-                            <padding><Insets top="10" right="10" bottom="10" left="10"/></padding>
-                            <Label text="书名" GridPane.rowIndex="0" GridPane.columnIndex="0"/><TextField fx:id="newTitle" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
-                            <Label text="作者" GridPane.rowIndex="1" GridPane.columnIndex="0"/><TextField fx:id="newAuthor" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
-                            <Label text="分类" GridPane.rowIndex="2" GridPane.columnIndex="0"/><TextField fx:id="newCategory" GridPane.rowIndex="2" GridPane.columnIndex="1"/>
-                            <Label text="原价" GridPane.rowIndex="3" GridPane.columnIndex="0"/><TextField fx:id="newOriginalPrice" GridPane.rowIndex="3" GridPane.columnIndex="1"/>
-                            <Label text="售价" GridPane.rowIndex="4" GridPane.columnIndex="0"/><TextField fx:id="newSellingPrice" GridPane.rowIndex="4" GridPane.columnIndex="1"/>
-                            <Label text="库存" GridPane.rowIndex="5" GridPane.columnIndex="0"/><TextField fx:id="newStock" GridPane.rowIndex="5" GridPane.columnIndex="1"/>
-                            <Button fx:id="saveBookBtn" text="添加书籍" onAction="#handleAddBook" GridPane.rowIndex="6" GridPane.columnIndex="1" styleClass="btn,btn-success"/>
-                        </GridPane>
-                    </TitledPane>
-                </VBox>
-            </Tab>
-            <Tab text="数据统计" closable="false">
-                <ScrollPane fitToWidth="true">
-                    <VBox spacing="15">
-                        <padding><Insets top="15" right="15" bottom="15" left="15"/></padding>
-                        <Button text="刷新数据" onAction="#handleLoadStatistics" maxWidth="200" styleClass="btn,btn-secondary"/>
-                        <TitledPane text="总览" expanded="true">
-                            <HBox spacing="40">
-                                <padding><Insets top="10" right="10" bottom="10" left="10"/></padding>
-                                <VBox spacing="5">
-                                    <Label text="交易总金额" style="-fx-font-weight: bold;"/>
-                                    <Label fx:id="statTotalAmount" text="-" style="-fx-font-size: 20px; -fx-text-fill: #27ae60;"/>
-                                </VBox>
-                                <VBox spacing="5">
-                                    <Label text="交易总笔数" style="-fx-font-weight: bold;"/>
-                                    <Label fx:id="statTotalCount" text="-" style="-fx-font-size: 20px;"/>
-                                </VBox>
-                            </HBox>
-                        </TitledPane>
-                        <TitledPane text="热门院系(销量前三)" expanded="true">
-                            <TableView fx:id="deptTable" prefHeight="150">
-                                <columns>
-                                    <TableColumn fx:id="deptNameCol" text="院系" prefWidth="250"/>
-                                    <TableColumn fx:id="deptSalesCol" text="销售额" prefWidth="200"/>
-                                </columns>
-                            </TableView>
-                        </TitledPane>
-                        <TitledPane text="热门书籍(前五)" expanded="true">
-                            <TableView fx:id="hotBookTable" prefHeight="150">
-                                <columns>
-                                    <TableColumn fx:id="hotBookIdCol" text="编号" prefWidth="50"/>
-                                    <TableColumn fx:id="hotBookTitleCol" text="书名" prefWidth="250"/>
-                                    <TableColumn fx:id="hotBookAuthorCol" text="作者" prefWidth="120"/>
-                                    <TableColumn fx:id="hotBookSoldCol" text="销量" prefWidth="80"/>
-                                </columns>
-                            </TableView>
-                        </TitledPane>
-                        <TitledPane text="各院系平均折扣率" expanded="true">
-                            <TableView fx:id="discountTable" prefHeight="150">
-                                <columns>
-                                    <TableColumn fx:id="discountDeptCol" text="院系" prefWidth="250"/>
-                                    <TableColumn fx:id="discountRateCol" text="平均折扣率" prefWidth="200"/>
-                                </columns>
-                            </TableView>
-                        </TitledPane>
-                    </VBox>
-                </ScrollPane>
-            </Tab>
-            <Tab fx:id="adminTab" text="管理" closable="false">
-                <ScrollPane fitToWidth="true">
-                    <VBox spacing="10">
-                        <padding><Insets top="10" right="10" bottom="10" left="10"/></padding>
-                        <Label text="用户管理" style="-fx-font-weight: bold; -fx-font-size: 16px;"/>
-                        <Button text="刷新用户" onAction="#handleLoadAllUsers" styleClass="btn,btn-secondary"/>
-                        <TableView fx:id="userTable" prefHeight="150">
-                            <columns>
-                                <TableColumn fx:id="userIdCol" text="编号" prefWidth="50"/>
-                                <TableColumn fx:id="userNameCol" text="用户名" prefWidth="120"/>
-                                <TableColumn fx:id="userNicknameCol" text="昵称" prefWidth="120"/>
-                                <TableColumn fx:id="userDeptCol" text="院系" prefWidth="150"/>
-                                <TableColumn fx:id="userCreditCol" text="信用分" prefWidth="60"/>
-                                <TableColumn fx:id="userRoleCol" text="角色" prefWidth="60"/>
-                                <TableColumn fx:id="userSellerCol" text="卖家" prefWidth="60"/>
-                            </columns>
-                        </TableView>
-                        <HBox spacing="10">
-                            <Button text="编辑选中" onAction="#handleEditUser" styleClass="btn,btn-warning"/>
-                            <Button text="重置密码" onAction="#handleResetPassword" styleClass="btn,btn-secondary"/>
-                            <Button text="修改信用分" onAction="#handleEditCredit" styleClass="btn,btn-info"/>
-                            <Button text="删除选中用户" onAction="#handleDeleteUser" styleClass="btn,btn-danger"/>
-                        </HBox>
-                        <TitledPane fx:id="userFormPane" text="添加用户" expanded="false">
-                            <GridPane hgap="10" vgap="8">
-                                <padding><Insets top="10" right="10" bottom="10" left="10"/></padding>
-                                <Label text="用户名" GridPane.rowIndex="0" GridPane.columnIndex="0"/><TextField fx:id="adminUsername" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
-                                <Label text="密码" GridPane.rowIndex="1" GridPane.columnIndex="0"/><PasswordField fx:id="adminPassword" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
-                                <Label text="昵称" GridPane.rowIndex="2" GridPane.columnIndex="0"/><TextField fx:id="adminNickname" GridPane.rowIndex="2" GridPane.columnIndex="1"/>
-                                <Label text="邮箱" GridPane.rowIndex="3" GridPane.columnIndex="0"/><TextField fx:id="adminEmail" GridPane.rowIndex="3" GridPane.columnIndex="1"/>
-                                <Label text="电话" GridPane.rowIndex="4" GridPane.columnIndex="0"/><TextField fx:id="adminPhone" GridPane.rowIndex="4" GridPane.columnIndex="1"/>
-                                <Label text="院系" GridPane.rowIndex="5" GridPane.columnIndex="0"/><TextField fx:id="adminDept" GridPane.rowIndex="5" GridPane.columnIndex="1"/>
-                                <Button fx:id="saveUserBtn" text="保存" onAction="#handleSaveUser" GridPane.rowIndex="6" GridPane.columnIndex="1" styleClass="btn,btn-success"/>
-                            </GridPane>
-                        </TitledPane>
-                        <Label text="书籍管理" style="-fx-font-weight: bold; -fx-font-size: 16px;"/>
-                        <Button text="刷新书籍" onAction="#handleLoadAllBooksAdmin" styleClass="btn,btn-secondary"/>
-                        <TableView fx:id="adminBookTable" prefHeight="150">
-                            <columns>
-                                <TableColumn fx:id="adminBookIdCol" text="编号" prefWidth="50"/>
-                                <TableColumn fx:id="adminBookTitleCol" text="书名" prefWidth="200"/>
-                                <TableColumn fx:id="adminBookAuthorCol" text="作者" prefWidth="120"/>
-                                <TableColumn fx:id="adminBookCategoryCol" text="分类" prefWidth="80"/>
-                                <TableColumn fx:id="adminBookPriceCol" text="售价" prefWidth="80"/>
-                                <TableColumn fx:id="adminBookStockCol" text="库存" prefWidth="60"/>
-                                <TableColumn fx:id="adminBookSellerCol" text="卖家ID" prefWidth="60"/>
-                            </columns>
-                        </TableView>
-                        <Button text="删除选中书籍" onAction="#handleDeleteBookAdmin" styleClass="btn,btn-danger"/>
-                        <Label text="待审核书籍" style="-fx-font-weight: bold; -fx-font-size: 16px;"/>
-                        <Button text="刷新" onAction="#handleLoadPendingBooks" styleClass="btn,btn-secondary"/>
-                        <TableView fx:id="pendingBookTable" prefHeight="150">
-                            <columns>
-                                <TableColumn fx:id="pendingBookIdCol" text="编号" prefWidth="50"/>
-                                <TableColumn fx:id="pendingBookTitleCol" text="书名" prefWidth="200"/>
-                                <TableColumn fx:id="pendingBookAuthorCol" text="作者" prefWidth="120"/>
-                                <TableColumn fx:id="pendingBookSellerCol" text="卖家ID" prefWidth="60"/>
-                                <TableColumn fx:id="pendingBookPriceCol" text="售价" prefWidth="80"/>
-                            </columns>
-                        </TableView>
-                        <HBox spacing="10">
-                            <Button text="通过" onAction="#handleApproveBook" styleClass="btn,btn-success"/>
-                            <Button text="拒绝" onAction="#handleRejectBook" styleClass="btn,btn-danger"/>
-                        </HBox>
-                        <Label text="全部交易" style="-fx-font-weight: bold; -fx-font-size: 16px;"/>
-                        <Button text="刷新交易" onAction="#handleLoadAllTransactions" styleClass="btn,btn-secondary"/>
-                        <TableView fx:id="allTransTable" prefHeight="150">
-                            <columns>
-                                <TableColumn fx:id="allTransIdCol" text="交易编号" prefWidth="60"/>
-                                <TableColumn fx:id="allTransBookIdCol" text="书籍编号" prefWidth="60"/>
-                                <TableColumn fx:id="allTransBuyerIdCol" text="买家编号" prefWidth="60"/>
-                                <TableColumn fx:id="allTransNumCol" text="数量" prefWidth="40"/>
-                                <TableColumn fx:id="allTransPriceCol" text="价格" prefWidth="100"/>
-                                <TableColumn fx:id="allTransTimeCol" text="时间" prefWidth="200"/>
-                            </columns>
-                        </TableView>
-                        <Button text="删除选中交易" onAction="#handleDeleteTransaction" styleClass="btn,btn-danger"/>
-                        <Label text="AI 分析" style="-fx-font-weight: bold; -fx-font-size: 16px;"/>
-                        <Button text="生成分析报告" onAction="#handleGenerateAnalysis" styleClass="btn,btn-primary"/>
-                        <TextArea fx:id="analysisResult" editable="false" wrapText="true" prefHeight="250"/>
-                    </VBox>
-                </ScrollPane>
-            </Tab>
-        </TabPane>
+        <StackPane fx:id="contentArea"/>
     </center>
 </BorderPane>

+ 44 - 0
client/src/main/resources/fxml/profile.fxml

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.geometry.*?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<VBox fx:id="profilePane" xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1" visible="false" managed="false" alignment="TOP_CENTER">
+    <padding><Insets top="32" right="16" bottom="32" left="16"/></padding>
+    <VBox styleClass="well" maxWidth="480" spacing="16">
+        <padding><Insets top="24" right="28" bottom="24" left="28"/></padding>
+        <HBox spacing="16" alignment="CENTER_LEFT">
+            <Label text="👤" style="-fx-font-size: 36px;"/>
+            <VBox spacing="4">
+                <Label fx:id="profileNickname" text="-" style="-fx-font-size: 18px; -fx-font-weight: bold;"/>
+                <Label fx:id="profileDept" text="-" styleClass="text-muted" style="-fx-font-size: 13px;"/>
+            </VBox>
+            <Region HBox.hgrow="ALWAYS"/>
+            <VBox alignment="CENTER_RIGHT" spacing="2">
+                <Label text="信用分" styleClass="detail-label"/>
+                <Label fx:id="profileCredit" text="-"/>
+            </VBox>
+        </HBox>
+        <Separator/>
+        <GridPane hgap="16" vgap="10">
+            <columnConstraints>
+                <ColumnConstraints prefWidth="80"/>
+                <ColumnConstraints hgrow="ALWAYS"/>
+            </columnConstraints>
+            <Label text="用户名" GridPane.rowIndex="0" GridPane.columnIndex="0"/>
+            <TextField fx:id="profileUsername" editable="false" styleClass="form-control" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
+            <Label text="昵称" GridPane.rowIndex="1" GridPane.columnIndex="0"/>
+            <TextField fx:id="profileNicknameField" styleClass="form-control" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
+            <Label text="邮箱" GridPane.rowIndex="2" GridPane.columnIndex="0"/>
+            <TextField fx:id="profileEmail" styleClass="form-control" GridPane.rowIndex="2" GridPane.columnIndex="1"/>
+            <Label text="电话" GridPane.rowIndex="3" GridPane.columnIndex="0"/>
+            <TextField fx:id="profilePhone" styleClass="form-control" GridPane.rowIndex="3" GridPane.columnIndex="1"/>
+            <Label text="院系" GridPane.rowIndex="4" GridPane.columnIndex="0"/>
+            <TextField fx:id="profileDeptField" styleClass="form-control" GridPane.rowIndex="4" GridPane.columnIndex="1"/>
+            <Label text="卖家" GridPane.rowIndex="5" GridPane.columnIndex="0"/>
+            <CheckBox fx:id="profileSellerChk" text="申请成为卖家" GridPane.rowIndex="5" GridPane.columnIndex="1"/>
+        </GridPane>
+        <Button text="保存修改" onAction="#handleUpdateProfile" maxWidth="Infinity" styleClass="btn,btn-primary"/>
+    </VBox>
+</VBox>

+ 35 - 12
client/src/main/resources/fxml/register.fxml

@@ -4,15 +4,38 @@
 <?import javafx.scene.control.*?>
 <?import javafx.scene.layout.*?>
 
-<VBox alignment="CENTER" spacing="12" xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1" fx:controller="space.anyi.client.controller.RegisterController" prefWidth="400" prefHeight="500">
-    <padding><Insets top="30" right="40" bottom="30" left="40"/></padding>
-    <Label text="创建账号" style="-fx-font-size: 20px; -fx-font-weight: bold;"/>
-    <TextField fx:id="usernameField" promptText="用户名 *" maxWidth="300"/>
-    <PasswordField fx:id="passwordField" promptText="密码 *" maxWidth="300"/>
-    <TextField fx:id="nicknameField" promptText="昵称" maxWidth="300"/>
-    <TextField fx:id="emailField" promptText="邮箱" maxWidth="300"/>
-    <TextField fx:id="phoneField" promptText="电话" maxWidth="300"/>
-    <TextField fx:id="deptField" promptText="院系" maxWidth="300"/>
-    <Button text="注册" onAction="#handleRegister" maxWidth="300" prefWidth="300" styleClass="btn,btn-primary"/>
-    <Hyperlink text="已有账号?去登录" onAction="#handleBack"/>
-</VBox>
+<StackPane xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1" fx:controller="space.anyi.client.controller.RegisterController" prefWidth="500" prefHeight="600">
+    <VBox alignment="CENTER" spacing="0">
+        <VBox styleClass="well" maxWidth="380" spacing="14" alignment="CENTER">
+            <padding><Insets top="32" right="36" bottom="32" left="36"/></padding>
+            <Label text="创建账号" style="-fx-font-size: 22px; -fx-font-weight: bold;"/>
+            <Label text="加入校园二手书市" style="-fx-font-size: 14px;" styleClass="text-muted"/>
+            <HBox spacing="12" alignment="CENTER_LEFT" maxWidth="300">
+                <Label text="用户名" styleClass="form-label" minWidth="50"/>
+                <TextField fx:id="usernameField" promptText="请输入用户名" styleClass="form-control" HBox.hgrow="ALWAYS"/>
+            </HBox>
+            <HBox spacing="12" alignment="CENTER_LEFT" maxWidth="300">
+                <Label text="密码" styleClass="form-label" minWidth="50"/>
+                <PasswordField fx:id="passwordField" promptText="请输入密码" styleClass="form-control" HBox.hgrow="ALWAYS"/>
+            </HBox>
+            <HBox spacing="12" alignment="CENTER_LEFT" maxWidth="300">
+                <Label text="昵称" styleClass="form-label" minWidth="50"/>
+                <TextField fx:id="nicknameField" promptText="请输入昵称" styleClass="form-control" HBox.hgrow="ALWAYS"/>
+            </HBox>
+            <HBox spacing="12" alignment="CENTER_LEFT" maxWidth="300">
+                <Label text="邮箱" styleClass="form-label" minWidth="50"/>
+                <TextField fx:id="emailField" promptText="请输入邮箱" styleClass="form-control" HBox.hgrow="ALWAYS"/>
+            </HBox>
+            <HBox spacing="12" alignment="CENTER_LEFT" maxWidth="300">
+                <Label text="电话" styleClass="form-label" minWidth="50"/>
+                <TextField fx:id="phoneField" promptText="请输入电话" styleClass="form-control" HBox.hgrow="ALWAYS"/>
+            </HBox>
+            <HBox spacing="12" alignment="CENTER_LEFT" maxWidth="300">
+                <Label text="院系" styleClass="form-label" minWidth="50"/>
+                <TextField fx:id="deptField" promptText="请输入院系" styleClass="form-control" HBox.hgrow="ALWAYS"/>
+            </HBox>
+            <Button text="注册" onAction="#handleRegister" maxWidth="300" prefWidth="300" styleClass="btn,btn-primary"/>
+            <Hyperlink text="已有账号?去登录" onAction="#handleBack" style="-fx-font-size: 13px;"/>
+        </VBox>
+    </VBox>
+</StackPane>

+ 69 - 0
client/src/main/resources/fxml/seller.fxml

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.geometry.*?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<VBox fx:id="sellerPane" xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1" visible="false" managed="false">
+    <padding><Insets top="16" right="16" bottom="16" left="16"/></padding>
+    <HBox spacing="24" styleClass="well">
+        <padding><Insets top="12" right="20" bottom="12" left="20"/></padding>
+        <VBox alignment="CENTER" spacing="4">
+            <Label text="在售" styleClass="detail-label"/>
+            <Label fx:id="sellerOnSaleCount" text="0" style="-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: -fx-primary;"/>
+        </VBox>
+        <Separator orientation="VERTICAL"/>
+        <VBox alignment="CENTER" spacing="4">
+            <Label text="总库存" styleClass="detail-label"/>
+            <Label fx:id="sellerStockCount" text="0" style="-fx-font-size: 20px; -fx-font-weight: bold;"/>
+        </VBox>
+        <Separator orientation="VERTICAL"/>
+        <VBox alignment="CENTER" spacing="4">
+            <Label text="已售" styleClass="detail-label"/>
+            <Label fx:id="sellerSoldCount" text="0" style="-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: -fx-success;"/>
+        </VBox>
+    </HBox>
+    <HBox spacing="10" alignment="CENTER_LEFT">
+        <Label text="我的书籍" style="-fx-font-size: 16px; -fx-font-weight: bold;" HBox.hgrow="ALWAYS"/>
+        <Button text="刷新" onAction="#handleLoadMyBooks" styleClass="btn,btn-secondary"/>
+        <Button text="+ 添加新书" onAction="#handleToggleBookForm" styleClass="btn,btn-success"/>
+    </HBox>
+    <TableView fx:id="myBookTable" VBox.vgrow="ALWAYS" prefHeight="200">
+        <columns>
+            <TableColumn fx:id="myBookIdCol" text="编号" prefWidth="50"/>
+            <TableColumn fx:id="myBookTitleCol" text="书名" prefWidth="180"/>
+            <TableColumn fx:id="myBookPriceCol" text="售价" prefWidth="80"/>
+            <TableColumn fx:id="myBookStockCol" text="库存" prefWidth="60"/>
+            <TableColumn fx:id="myBookStatusCol" text="状态" prefWidth="80"/>
+            <TableColumn fx:id="myBookActionCol" text="操作" prefWidth="200"/>
+        </columns>
+    </TableView>
+    <TitledPane fx:id="bookFormPane" text="添加新书" expanded="false" animated="true">
+        <GridPane hgap="16" vgap="10">
+            <padding><Insets top="16" right="20" bottom="16" left="20"/></padding>
+            <columnConstraints>
+                <ColumnConstraints prefWidth="70"/>
+                <ColumnConstraints hgrow="ALWAYS" prefWidth="200"/>
+                <ColumnConstraints prefWidth="30"/>
+                <ColumnConstraints prefWidth="70"/>
+                <ColumnConstraints hgrow="ALWAYS" prefWidth="200"/>
+            </columnConstraints>
+            <Label text="书名" GridPane.rowIndex="0" GridPane.columnIndex="0"/>
+            <TextField fx:id="newTitle" styleClass="form-control" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
+            <Label text="作者" GridPane.rowIndex="0" GridPane.columnIndex="3"/>
+            <TextField fx:id="newAuthor" styleClass="form-control" GridPane.rowIndex="0" GridPane.columnIndex="4"/>
+            <Label text="分类" GridPane.rowIndex="1" GridPane.columnIndex="0"/>
+            <TextField fx:id="newCategory" styleClass="form-control" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
+            <Label text="原价" GridPane.rowIndex="1" GridPane.columnIndex="3"/>
+            <TextField fx:id="newOriginalPrice" styleClass="form-control" GridPane.rowIndex="1" GridPane.columnIndex="4"/>
+            <Label text="售价" GridPane.rowIndex="2" GridPane.columnIndex="0"/>
+            <TextField fx:id="newSellingPrice" styleClass="form-control" GridPane.rowIndex="2" GridPane.columnIndex="1"/>
+            <Label text="库存" GridPane.rowIndex="2" GridPane.columnIndex="3"/>
+            <TextField fx:id="newStock" styleClass="form-control" GridPane.rowIndex="2" GridPane.columnIndex="4"/>
+            <HBox spacing="10" GridPane.rowIndex="3" GridPane.columnIndex="4" alignment="CENTER_RIGHT">
+                <Button text="取消" onAction="#handleCancelBookForm" styleClass="btn,btn-secondary"/>
+                <Button fx:id="saveBookBtn" text="添加书籍" onAction="#handleAddBook" styleClass="btn,btn-success"/>
+            </HBox>
+        </GridPane>
+    </TitledPane>
+</VBox>

+ 57 - 0
client/src/main/resources/fxml/stats.fxml

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.geometry.*?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<ScrollPane fx:id="statsPane" xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1" visible="false" managed="false" fitToWidth="true">
+    <VBox spacing="16">
+        <padding><Insets top="16" right="16" bottom="16" left="16"/></padding>
+        <HBox spacing="10" alignment="CENTER_LEFT">
+            <Label text="数据洞察" style="-fx-font-size: 18px; -fx-font-weight: bold;" HBox.hgrow="ALWAYS"/>
+            <Button text="刷新数据" onAction="#handleLoadStatistics" styleClass="btn,btn-secondary"/>
+        </HBox>
+        <HBox spacing="16">
+            <VBox styleClass="kpi-card" HBox.hgrow="ALWAYS">
+                <Label text="交易总金额" styleClass="kpi-label"/>
+                <Label fx:id="statTotalAmount" text="-" styleClass="kpi-value" style="-fx-text-fill: -fx-success;"/>
+            </VBox>
+            <VBox styleClass="kpi-card" HBox.hgrow="ALWAYS">
+                <Label text="交易总笔数" styleClass="kpi-label"/>
+                <Label fx:id="statTotalCount" text="-" styleClass="kpi-value"/>
+            </VBox>
+        </HBox>
+        <VBox styleClass="well" spacing="12">
+            <Label text="🔥 热门院系 TOP 3" style="-fx-font-size: 16px; -fx-font-weight: bold;"/>
+            <TableView fx:id="deptTable" prefHeight="140">
+                <columns>
+                    <TableColumn fx:id="deptRankCol" text="排名" prefWidth="60"/>
+                    <TableColumn fx:id="deptNameCol" text="院系" prefWidth="200"/>
+                    <TableColumn fx:id="deptSalesCol" text="销售额" prefWidth="150"/>
+                </columns>
+            </TableView>
+        </VBox>
+        <VBox styleClass="well" spacing="12">
+            <Label text="📚 畅销书籍 TOP 5" style="-fx-font-size: 16px; -fx-font-weight: bold;"/>
+            <TableView fx:id="hotBookTable" prefHeight="160">
+                <columns>
+                    <TableColumn fx:id="hotBookRankCol" text="排名" prefWidth="60"/>
+                    <TableColumn fx:id="hotBookIdCol" text="编号" prefWidth="50"/>
+                    <TableColumn fx:id="hotBookTitleCol" text="书名" prefWidth="200"/>
+                    <TableColumn fx:id="hotBookAuthorCol" text="作者" prefWidth="120"/>
+                    <TableColumn fx:id="hotBookSoldCol" text="销量" prefWidth="80"/>
+                </columns>
+            </TableView>
+        </VBox>
+        <VBox styleClass="well" spacing="12">
+            <Label text="📊 各院系平均折扣率" style="-fx-font-size: 16px; -fx-font-weight: bold;"/>
+            <TableView fx:id="discountTable" prefHeight="160">
+                <columns>
+                    <TableColumn fx:id="discountDeptCol" text="院系" prefWidth="200"/>
+                    <TableColumn fx:id="discountRateCol" text="平均折扣率" prefWidth="150"/>
+                    <TableColumn fx:id="discountBarCol" text="趋势" prefWidth="200"/>
+                </columns>
+            </TableView>
+        </VBox>
+    </VBox>
+</ScrollPane>

+ 28 - 0
client/src/main/resources/fxml/trans.fxml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.geometry.*?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<VBox fx:id="transPane" xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1" visible="false" managed="false">
+    <padding><Insets top="16" right="16" bottom="16" left="16"/></padding>
+    <HBox spacing="10" alignment="CENTER_LEFT">
+        <Label text="交易记录" style="-fx-font-size: 18px; -fx-font-weight: bold;" HBox.hgrow="ALWAYS"/>
+        <Button text="刷新" onAction="#handleLoadTransactions" styleClass="btn,btn-secondary"/>
+    </HBox>
+    <StackPane VBox.vgrow="ALWAYS">
+        <TableView fx:id="transTable" VBox.vgrow="ALWAYS">
+            <columns>
+                <TableColumn fx:id="transIdCol" text="交易编号" prefWidth="80"/>
+                <TableColumn fx:id="transBookTitleCol" text="书名" prefWidth="200"/>
+                <TableColumn fx:id="transNumCol" text="数量" prefWidth="80"/>
+                <TableColumn fx:id="transPriceCol" text="总价" prefWidth="120"/>
+                <TableColumn fx:id="transTimeCol" text="交易时间" prefWidth="220"/>
+            </columns>
+        </TableView>
+        <VBox fx:id="transEmptyState" visible="false" managed="false" styleClass="empty-state">
+            <Label text="📄" styleClass="empty-icon"/>
+            <Label text="还没有交易记录,去逛逛吧~" styleClass="empty-text"/>
+        </VBox>
+    </StackPane>
+</VBox>

+ 4 - 0
common/pom.xml

@@ -20,5 +20,9 @@
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
         </dependency>
+        <dependency>
+            <groupId>jakarta.validation</groupId>
+            <artifactId>jakarta.validation-api</artifactId>
+        </dependency>
     </dependencies>
 </project>

+ 3 - 0
common/src/main/java/space/anyi/common/dto/LoginRequest.java

@@ -1,9 +1,12 @@
 package space.anyi.common.dto;
 
+import jakarta.validation.constraints.NotBlank;
 import lombok.Data;
 
 @Data
 public class LoginRequest {
+    @NotBlank(message = "用户名不能为空")
     private String username;
+    @NotBlank(message = "密码不能为空")
     private String password;
 }

+ 3 - 0
common/src/main/java/space/anyi/common/dto/RegisterRequest.java

@@ -1,5 +1,6 @@
 package space.anyi.common.dto;
 
+import jakarta.validation.constraints.NotBlank;
 import lombok.Data;
 
 @Data
@@ -10,4 +11,6 @@ public class RegisterRequest {
     private String email;
     private String phone;
     private String dept;
+    private Integer role;
+    private Integer isSeller;
 }

+ 14 - 0
common/src/main/java/space/anyi/common/dto/UserTransactionDTO.java

@@ -0,0 +1,14 @@
+package space.anyi.common.dto;
+
+import lombok.Data;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+public class UserTransactionDTO {
+    private Integer transId;
+    private String bookTitle;
+    private Integer transactionNum;
+    private BigDecimal transactionPrice;
+    private LocalDateTime transactionTime;
+}

+ 1 - 0
pom.xml

@@ -104,6 +104,7 @@
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-compiler-plugin</artifactId>
                     <configuration>
+                        <parameters>true</parameters>
                         <annotationProcessorPaths>
                             <path>
                                 <groupId>org.projectlombok</groupId>

+ 4 - 0
server/pom.xml

@@ -36,6 +36,10 @@
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-jsqlparser</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 5 - 0
server/src/main/java/space/anyi/server/controller/BookController.java

@@ -58,6 +58,11 @@ public class BookController {
         return R.ok(bookService.getSalesByBookId(id));
     }
 
+    @GetMapping("/sales-summary")
+    public R<Integer> getSalesSummary(@RequestParam Integer sellerId) {
+        return R.ok(bookService.getTotalSoldBySellerId(sellerId));
+    }
+
     @GetMapping("/pending")
     public R<List<Book>> getPendingBooks() {
         return R.ok(bookService.getPendingBooks());

+ 6 - 0
server/src/main/java/space/anyi/server/controller/TransactionController.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.springframework.web.bind.annotation.*;
 import space.anyi.common.dto.R;
+import space.anyi.common.dto.UserTransactionDTO;
 import space.anyi.common.entity.TransactionRecord;
 import space.anyi.server.service.TransactionService;
 import java.util.List;
@@ -52,6 +53,11 @@ public class TransactionController {
         return R.ok(transactionService.getByUserId(userId));
     }
 
+    @GetMapping("/user/{userId}/detail")
+    public R<List<UserTransactionDTO>> getByUserDetail(@PathVariable Integer userId) {
+        return R.ok(transactionService.getByUserIdWithTitle(userId));
+    }
+
     @PutMapping("/{id}")
     public R<TransactionRecord> update(@PathVariable Integer id, @RequestBody TransactionRecord record) {
         record.setTransId(id);

+ 2 - 0
server/src/main/java/space/anyi/server/controller/UserController.java

@@ -37,6 +37,8 @@ public class UserController {
         user.setEmail(req.getEmail());
         user.setPhone(req.getPhone());
         user.setDept(req.getDept());
+        user.setRole(req.getRole());
+        user.setIsSeller(req.getIsSeller());
         User created = userService.register(user);
         return R.ok(created);
     }

+ 3 - 0
server/src/main/java/space/anyi/server/mapper/BookMapper.java

@@ -15,4 +15,7 @@ public interface BookMapper extends BaseMapper<Book> {
             "WHERE b.book_id = #{bookId} " +
             "ORDER BY t.transaction_time DESC")
     List<SalesRecordDTO> selectSalesByBookId(@Param("bookId") Integer bookId);
+
+    @Select("SELECT COALESCE(SUM(t.transaction_num), 0) FROM transaction_record t JOIN book b ON t.book_id = b.book_id WHERE b.seller_id = #{sellerId}")
+    Integer selectTotalSoldBySellerId(@Param("sellerId") Integer sellerId);
 }

+ 7 - 0
server/src/main/java/space/anyi/server/mapper/TransactionRecordMapper.java

@@ -1,7 +1,14 @@
 package space.anyi.server.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Select;
+import space.anyi.common.dto.UserTransactionDTO;
 import space.anyi.common.entity.TransactionRecord;
+import java.util.List;
 
 public interface TransactionRecordMapper extends BaseMapper<TransactionRecord> {
+    @Select("SELECT t.trans_id, b.title AS book_title, t.transaction_num, t.transaction_price, t.transaction_time " +
+            "FROM transaction_record t LEFT JOIN book b ON t.book_id = b.book_id " +
+            "WHERE t.buyer_id = #{userId} ORDER BY t.transaction_time DESC")
+    List<UserTransactionDTO> selectByUserIdWithTitle(Integer userId);
 }

+ 1 - 0
server/src/main/java/space/anyi/server/service/BookService.java

@@ -12,4 +12,5 @@ public interface BookService extends IService<Book> {
     List<Book> listAll();
     List<SalesRecordDTO> getSalesByBookId(Integer bookId);
     List<Book> getPendingBooks();
+    Integer getTotalSoldBySellerId(Integer sellerId);
 }

+ 2 - 0
server/src/main/java/space/anyi/server/service/TransactionService.java

@@ -1,10 +1,12 @@
 package space.anyi.server.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import space.anyi.common.dto.UserTransactionDTO;
 import space.anyi.common.entity.TransactionRecord;
 import java.util.List;
 
 public interface TransactionService extends IService<TransactionRecord> {
     TransactionRecord purchase(Integer bookId, Integer buyerId, Integer quantity);
     List<TransactionRecord> getByUserId(Integer userId);
+    List<UserTransactionDTO> getByUserIdWithTitle(Integer userId);
 }

+ 5 - 0
server/src/main/java/space/anyi/server/service/impl/BookServiceImpl.java

@@ -60,4 +60,9 @@ public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements Bo
                .orderByDesc(Book::getCreatedAt);
         return list(wrapper);
     }
+
+    @Override
+    public Integer getTotalSoldBySellerId(Integer sellerId) {
+        return bookMapper.selectTotalSoldBySellerId(sellerId);
+    }
 }

+ 9 - 1
server/src/main/java/space/anyi/server/service/impl/TransactionServiceImpl.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import space.anyi.common.dto.UserTransactionDTO;
 import space.anyi.common.entity.Book;
 import space.anyi.common.entity.TransactionRecord;
 import space.anyi.server.mapper.TransactionRecordMapper;
@@ -17,9 +18,11 @@ import java.util.List;
 public class TransactionServiceImpl extends ServiceImpl<TransactionRecordMapper, TransactionRecord> implements TransactionService {
 
     private final BookService bookService;
+    private final TransactionRecordMapper transactionRecordMapper;
 
-    public TransactionServiceImpl(BookService bookService) {
+    public TransactionServiceImpl(BookService bookService, TransactionRecordMapper transactionRecordMapper) {
         this.bookService = bookService;
+        this.transactionRecordMapper = transactionRecordMapper;
     }
 
     @Override
@@ -53,4 +56,9 @@ public class TransactionServiceImpl extends ServiceImpl<TransactionRecordMapper,
                .orderByDesc(TransactionRecord::getTransactionTime);
         return list(wrapper);
     }
+
+    @Override
+    public List<UserTransactionDTO> getByUserIdWithTitle(Integer userId) {
+        return transactionRecordMapper.selectByUserIdWithTitle(userId);
+    }
 }

+ 2 - 2
server/src/main/java/space/anyi/server/service/impl/UserServiceImpl.java

@@ -22,8 +22,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
     @Override
     public User register(User user) {
         user.setCredit(100);
-        user.setRole(0);
-        user.setIsSeller(0);
+        if (user.getRole() == null) user.setRole(0);
+        if (user.getIsSeller() == null) user.setIsSeller(0);
         user.setCreatedAt(LocalDateTime.now());
         save(user);
         return user;

+ 3 - 1
sql/campus_book_trade.sql

@@ -83,7 +83,8 @@ CREATE TABLE `user`  (
   `role` int(0) NULL DEFAULT NULL,
   `is_seller` int(0) NULL DEFAULT NULL,
   `created_at` datetime(0) NULL DEFAULT NULL,
-  PRIMARY KEY (`userid`) USING BTREE
+  PRIMARY KEY (`userid`) USING BTREE,
+  UNIQUE INDEX `uk_username`(`username`) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
 
 -- ----------------------------
@@ -101,3 +102,4 @@ SET FOREIGN_KEY_CHECKS = 1;
 -- Migration for existing databases:
 -- ALTER TABLE `book` ADD COLUMN `status` int DEFAULT 0 COMMENT '0=待审核, 1=已上架, 2=已下架';
 -- UPDATE `book` SET `status` = 1;
+-- ALTER TABLE `user` ADD UNIQUE INDEX `uk_username`(`username`) COMMENT '用户名唯一索引';

Неке датотеке нису приказане због велике количине промена