From 915d80766dd8e0157e9b9510b3634ed758eb5c5a Mon Sep 17 00:00:00 2001
From: Codex Assistant <codex@example.com>
Date: 星期日, 05 十月 2025 14:45:58 +0800
Subject: [PATCH] feat: 新增员工审核入口与审核页面

---
 backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeReviewStatsResponse.java       |   43 +
 backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeReviewPageResponse.java        |   55 ++
 wx/pages/profile/employee-review.json                                                               |    5 
 wx/pages/profile/employee-review.js                                                                 |  217 ++++++++
 wx/pages/profile/employee-review-detail.wxss                                                        |  124 ++++
 wx/pages/profile/employee-review-detail.wxml                                                        |   71 ++
 backend/src/main/java/com/rongyichuang/employee/resolver/EmployeeReviewResolver.java                |   34 +
 wx/pages/profile/profile.wxml                                                                       |   29 +
 wx/pages/profile/employee-review-detail.json                                                        |    4 
 backend/src/main/java/com/rongyichuang/employee/service/EmployeeReviewService.java                  |  248 +++++++++
 wx/pages/profile/profile.js                                                                         |   74 ++
 wx/app.json                                                                                         |   50 +
 backend/src/main/resources/graphql/employee.graphqls                                                |   32 +
 wx/pages/profile/employee-review.wxml                                                               |   72 ++
 wx/pages/profile/employee-review.wxss                                                               |  195 +++++++
 backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeReviewApplicationResponse.java |   82 +++
 wx/pages/profile/employee-review-detail.js                                                          |  165 ++++++
 17 files changed, 1,474 insertions(+), 26 deletions(-)

diff --git a/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeReviewApplicationResponse.java b/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeReviewApplicationResponse.java
new file mode 100644
index 0000000..01391fa
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeReviewApplicationResponse.java
@@ -0,0 +1,82 @@
+package com.rongyichuang.employee.dto.response;
+
+/**
+ * 鍛樺伐瀹℃牳鍒楄〃鏉$洰
+ */
+public class EmployeeReviewApplicationResponse {
+    private Long id;
+    private String playerName;
+    private String projectName;
+    private String activityName;
+    private Integer state;
+    private String stateText;
+    private String stateType;
+    private String applyTime;
+
+    public EmployeeReviewApplicationResponse() {
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getPlayerName() {
+        return playerName;
+    }
+
+    public void setPlayerName(String playerName) {
+        this.playerName = playerName;
+    }
+
+    public String getProjectName() {
+        return projectName;
+    }
+
+    public void setProjectName(String projectName) {
+        this.projectName = projectName;
+    }
+
+    public String getActivityName() {
+        return activityName;
+    }
+
+    public void setActivityName(String activityName) {
+        this.activityName = activityName;
+    }
+
+    public Integer getState() {
+        return state;
+    }
+
+    public void setState(Integer state) {
+        this.state = state;
+    }
+
+    public String getStateText() {
+        return stateText;
+    }
+
+    public void setStateText(String stateText) {
+        this.stateText = stateText;
+    }
+
+    public String getStateType() {
+        return stateType;
+    }
+
+    public void setStateType(String stateType) {
+        this.stateType = stateType;
+    }
+
+    public String getApplyTime() {
+        return applyTime;
+    }
+
+    public void setApplyTime(String applyTime) {
+        this.applyTime = applyTime;
+    }
+}
diff --git a/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeReviewPageResponse.java b/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeReviewPageResponse.java
new file mode 100644
index 0000000..a8d770e
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeReviewPageResponse.java
@@ -0,0 +1,55 @@
+package com.rongyichuang.employee.dto.response;
+
+import java.util.List;
+
+/**
+ * 鍛樺伐瀹℃牳鍒嗛〉缁撴灉
+ */
+public class EmployeeReviewPageResponse {
+    private List<EmployeeReviewApplicationResponse> content;
+    private int totalElements;
+    private int page;
+    private int size;
+
+    public EmployeeReviewPageResponse() {
+    }
+
+    public EmployeeReviewPageResponse(List<EmployeeReviewApplicationResponse> content, int totalElements, int page, int size) {
+        this.content = content;
+        this.totalElements = totalElements;
+        this.page = page;
+        this.size = size;
+    }
+
+    public List<EmployeeReviewApplicationResponse> getContent() {
+        return content;
+    }
+
+    public void setContent(List<EmployeeReviewApplicationResponse> content) {
+        this.content = content;
+    }
+
+    public int getTotalElements() {
+        return totalElements;
+    }
+
+    public void setTotalElements(int totalElements) {
+        this.totalElements = totalElements;
+    }
+
+    public int getPage() {
+        return page;
+    }
+
+    public void setPage(int page) {
+        this.page = page;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public void setSize(int size) {
+        this.size = size;
+    }
+}
diff --git a/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeReviewStatsResponse.java b/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeReviewStatsResponse.java
new file mode 100644
index 0000000..117f8e9
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeReviewStatsResponse.java
@@ -0,0 +1,43 @@
+package com.rongyichuang.employee.dto.response;
+
+/**
+ * 鍛樺伐瀹℃牳缁熻鏁版嵁
+ */
+public class EmployeeReviewStatsResponse {
+    private int pendingCount;
+    private int approvedCount;
+    private int rejectedCount;
+
+    public EmployeeReviewStatsResponse() {
+    }
+
+    public EmployeeReviewStatsResponse(int pendingCount, int approvedCount, int rejectedCount) {
+        this.pendingCount = pendingCount;
+        this.approvedCount = approvedCount;
+        this.rejectedCount = rejectedCount;
+    }
+
+    public int getPendingCount() {
+        return pendingCount;
+    }
+
+    public void setPendingCount(int pendingCount) {
+        this.pendingCount = pendingCount;
+    }
+
+    public int getApprovedCount() {
+        return approvedCount;
+    }
+
+    public void setApprovedCount(int approvedCount) {
+        this.approvedCount = approvedCount;
+    }
+
+    public int getRejectedCount() {
+        return rejectedCount;
+    }
+
+    public void setRejectedCount(int rejectedCount) {
+        this.rejectedCount = rejectedCount;
+    }
+}
diff --git a/backend/src/main/java/com/rongyichuang/employee/resolver/EmployeeReviewResolver.java b/backend/src/main/java/com/rongyichuang/employee/resolver/EmployeeReviewResolver.java
new file mode 100644
index 0000000..d0c1e4a
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/employee/resolver/EmployeeReviewResolver.java
@@ -0,0 +1,34 @@
+package com.rongyichuang.employee.resolver;
+
+import com.rongyichuang.employee.dto.response.EmployeeReviewPageResponse;
+import com.rongyichuang.employee.dto.response.EmployeeReviewStatsResponse;
+import com.rongyichuang.employee.service.EmployeeReviewService;
+import org.springframework.graphql.data.method.annotation.Argument;
+import org.springframework.graphql.data.method.annotation.QueryMapping;
+import org.springframework.stereotype.Controller;
+
+/**
+ * 鍛樺伐瀹℃牳GraphQL鎺ュ彛
+ */
+@Controller
+public class EmployeeReviewResolver {
+
+    private final EmployeeReviewService employeeReviewService;
+
+    public EmployeeReviewResolver(EmployeeReviewService employeeReviewService) {
+        this.employeeReviewService = employeeReviewService;
+    }
+
+    @QueryMapping
+    public EmployeeReviewStatsResponse employeeReviewStats(@Argument String keyword) {
+        return employeeReviewService.getReviewStats(keyword);
+    }
+
+    @QueryMapping
+    public EmployeeReviewPageResponse employeeReviewApplications(@Argument String keyword,
+                                                                 @Argument Integer state,
+                                                                 @Argument Integer page,
+                                                                 @Argument Integer size) {
+        return employeeReviewService.listReviewApplications(keyword, state, page, size);
+    }
+}
diff --git a/backend/src/main/java/com/rongyichuang/employee/service/EmployeeReviewService.java b/backend/src/main/java/com/rongyichuang/employee/service/EmployeeReviewService.java
new file mode 100644
index 0000000..e0a32e8
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/employee/service/EmployeeReviewService.java
@@ -0,0 +1,248 @@
+package com.rongyichuang.employee.service;
+
+import com.rongyichuang.common.exception.BusinessException;
+import com.rongyichuang.common.util.UserContextUtil;
+import com.rongyichuang.employee.dto.response.EmployeeReviewApplicationResponse;
+import com.rongyichuang.employee.dto.response.EmployeeReviewPageResponse;
+import com.rongyichuang.employee.dto.response.EmployeeReviewStatsResponse;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鍛樺伐瀹℃牳鏁版嵁鏈嶅姟
+ */
+@Service
+public class EmployeeReviewService {
+
+    private static final Logger log = LoggerFactory.getLogger(EmployeeReviewService.class);
+
+    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    @PersistenceContext
+    private EntityManager entityManager;
+
+    private final UserContextUtil userContextUtil;
+    private final EmployeeService employeeService;
+
+    public EmployeeReviewService(UserContextUtil userContextUtil, EmployeeService employeeService) {
+        this.userContextUtil = userContextUtil;
+        this.employeeService = employeeService;
+    }
+
+    /**
+     * 鑾峰彇鍛樺伐瀹℃牳缁熻
+     */
+    public EmployeeReviewStatsResponse getReviewStats(String keyword) {
+        ensureEmployeeIdentity();
+
+        StringBuilder sql = new StringBuilder();
+        sql.append("SELECT COALESCE(ap.state, 0) AS state, COUNT(*) AS total ");
+        sql.append("FROM t_activity_player ap ");
+        sql.append("JOIN t_player p ON p.id = ap.player_id ");
+        sql.append("JOIN t_activity stage ON stage.id = ap.stage_id ");
+        sql.append("JOIN t_activity parent ON parent.id = stage.pid ");
+        sql.append("WHERE stage.sort_order = 1 ");
+
+        if (StringUtils.hasText(keyword)) {
+            sql.append("AND (p.name LIKE :keyword OR parent.name LIKE :keyword OR ap.project_name LIKE :keyword) ");
+        }
+
+        sql.append("GROUP BY ap.state");
+
+        var query = entityManager.createNativeQuery(sql.toString());
+        if (StringUtils.hasText(keyword)) {
+            query.setParameter("keyword", wrapKeyword(keyword));
+        }
+
+        @SuppressWarnings("unchecked")
+        List<Object[]> rows = query.getResultList();
+
+        int pending = 0;
+        int approved = 0;
+        int rejected = 0;
+
+        for (Object[] row : rows) {
+            int state = 0;
+            int total = 0;
+            if (row[0] instanceof Number) {
+                state = ((Number) row[0]).intValue();
+            } else if (row[0] != null) {
+                state = Integer.parseInt(row[0].toString());
+            }
+            if (row[1] instanceof Number) {
+                total = ((Number) row[1]).intValue();
+            } else if (row[1] != null) {
+                total = Integer.parseInt(row[1].toString());
+            }
+
+            switch (state) {
+                case 0 -> pending = total;
+                case 1 -> approved = total;
+                case 2 -> rejected = total;
+                default -> log.debug("蹇界暐鐘舵�亄}鐨勭粺璁�", state);
+            }
+        }
+
+        return new EmployeeReviewStatsResponse(pending, approved, rejected);
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ鍛樺伐瀹℃牳鍒楄〃
+     */
+    public EmployeeReviewPageResponse listReviewApplications(String keyword, Integer state, Integer page, Integer size) {
+        ensureEmployeeIdentity();
+
+        int pageNumber = (page != null && page > 0) ? page : 1;
+        int pageSize = (size != null && size > 0) ? size : 10;
+        int offset = (pageNumber - 1) * pageSize;
+
+        String baseSql = "SELECT ap.id, p.name AS player_name, ap.project_name, parent.name AS activity_name, ap.state, ap.create_time " +
+                "FROM t_activity_player ap " +
+                "JOIN t_player p ON p.id = ap.player_id " +
+                "JOIN t_activity stage ON stage.id = ap.stage_id " +
+                "JOIN t_activity parent ON parent.id = stage.pid ";
+
+        StringBuilder whereClause = new StringBuilder();
+        whereClause.append("stage.sort_order = 1");
+
+        if (StringUtils.hasText(keyword)) {
+            whereClause.append(" AND (p.name LIKE :keyword OR parent.name LIKE :keyword OR ap.project_name LIKE :keyword)");
+        }
+
+        if (state != null) {
+            whereClause.append(" AND ap.state = :state");
+        }
+
+        String where = whereClause.length() > 0 ? " WHERE " + whereClause + " " : "";
+        String order = "ORDER BY ap.create_time DESC ";
+        String limit = "LIMIT " + pageSize + " OFFSET " + offset + " ";
+
+        var query = entityManager.createNativeQuery(baseSql + where + order + limit);
+        if (StringUtils.hasText(keyword)) {
+            query.setParameter("keyword", wrapKeyword(keyword));
+        }
+        if (state != null) {
+            query.setParameter("state", state);
+        }
+
+        @SuppressWarnings("unchecked")
+        List<Object[]> rows = query.getResultList();
+        List<EmployeeReviewApplicationResponse> content = new ArrayList<>();
+
+        for (Object[] row : rows) {
+            EmployeeReviewApplicationResponse dto = new EmployeeReviewApplicationResponse();
+            dto.setId(row[0] instanceof Number ? ((Number) row[0]).longValue() : parseLong(row[0]));
+            dto.setPlayerName(row[1] != null ? row[1].toString() : "");
+            dto.setProjectName(row[2] != null ? row[2].toString() : "");
+            dto.setActivityName(row[3] != null ? row[3].toString() : "");
+            Integer reviewState = row[4] instanceof Number ? ((Number) row[4]).intValue() : parseInt(row[4]);
+            dto.setState(reviewState);
+            dto.setStateText(resolveStateText(reviewState));
+            dto.setStateType(resolveStateType(reviewState));
+            dto.setApplyTime(formatDateTime(row[5]));
+            content.add(dto);
+        }
+
+        String countSql = "SELECT COUNT(*) FROM t_activity_player ap " +
+                "JOIN t_player p ON p.id = ap.player_id " +
+                "JOIN t_activity stage ON stage.id = ap.stage_id " +
+                "JOIN t_activity parent ON parent.id = stage.pid " +
+                where;
+
+        var countQuery = entityManager.createNativeQuery(countSql);
+        if (StringUtils.hasText(keyword)) {
+            countQuery.setParameter("keyword", wrapKeyword(keyword));
+        }
+        if (state != null) {
+            countQuery.setParameter("state", state);
+        }
+
+        Number totalNumber = (Number) countQuery.getSingleResult();
+        int total = totalNumber != null ? totalNumber.intValue() : 0;
+
+        return new EmployeeReviewPageResponse(content, total, pageNumber, pageSize);
+    }
+
+    private void ensureEmployeeIdentity() {
+        Long userId = userContextUtil.getCurrentUserId();
+        if (userId == null) {
+            throw new BusinessException("UNAUTHORIZED", "璇峰厛鐧诲綍");
+        }
+        if (employeeService.findByUserId(userId) == null) {
+            throw new BusinessException("EMPLOYEE_REQUIRED", "褰撳墠鐢ㄦ埛娌℃湁瀹℃牳鏉冮檺");
+        }
+    }
+
+    private String wrapKeyword(String keyword) {
+        return "%" + keyword.trim() + "%";
+    }
+
+    private Integer parseInt(Object value) {
+        if (value == null) {
+            return null;
+        }
+        try {
+            return Integer.parseInt(value.toString());
+        } catch (NumberFormatException ex) {
+            log.warn("鏃犳硶瑙f瀽鏁村瀷鍊�: {}", value, ex);
+            return null;
+        }
+    }
+
+    private Long parseLong(Object value) {
+        if (value == null) {
+            return null;
+        }
+        try {
+            return Long.parseLong(value.toString());
+        } catch (NumberFormatException ex) {
+            log.warn("鏃犳硶瑙f瀽闀挎暣鍨嬪��: {}", value, ex);
+            return null;
+        }
+    }
+
+    private String formatDateTime(Object value) {
+        if (value instanceof Timestamp timestamp) {
+            LocalDateTime dateTime = timestamp.toLocalDateTime();
+            return DATE_TIME_FORMATTER.format(dateTime);
+        }
+        if (value instanceof LocalDateTime localDateTime) {
+            return DATE_TIME_FORMATTER.format(localDateTime);
+        }
+        return value != null ? value.toString() : "";
+    }
+
+    private String resolveStateText(Integer state) {
+        if (state == null) {
+            return "鏈煡";
+        }
+        return switch (state) {
+            case 0 -> "鏈鏍�";
+            case 1 -> "瀹℃牳閫氳繃";
+            case 2 -> "瀹℃牳椹冲洖";
+            default -> "鏈煡";
+        };
+    }
+
+    private String resolveStateType(Integer state) {
+        if (state == null) {
+            return "info";
+        }
+        return switch (state) {
+            case 0 -> "warning";
+            case 1 -> "success";
+            case 2 -> "danger";
+            default -> "info";
+        };
+    }
+}
diff --git a/backend/src/main/resources/graphql/employee.graphqls b/backend/src/main/resources/graphql/employee.graphqls
index 78727ab..621d614 100644
--- a/backend/src/main/resources/graphql/employee.graphqls
+++ b/backend/src/main/resources/graphql/employee.graphqls
@@ -26,6 +26,32 @@
 # Spring Data鐨凱age瀵硅薄浼氳嚜鍔ㄦ槧灏勫埌GraphQL
 
 # 鎵╁睍鏌ヨ绫诲瀷
+
+
+type EmployeeReviewApplication {
+    id: Long!
+    playerName: String
+    projectName: String
+    activityName: String
+    state: Int
+    stateText: String
+    stateType: String
+    applyTime: String
+}
+
+type EmployeeReviewPage {
+    content: [EmployeeReviewApplication!]!
+    totalElements: Int!
+    page: Int!
+    size: Int!
+}
+
+type EmployeeReviewStats {
+    pendingCount: Int!
+    approvedCount: Int!
+    rejectedCount: Int!
+}
+
 extend type Query {
     # 鑾峰彇鎵�鏈夊憳宸ュ垪琛�
     employees: [EmployeeResponse!]!
@@ -35,6 +61,12 @@
     
     # 鏍规嵁ID鑾峰彇鍛樺伐璇︽儏
     employee(id: Long!): EmployeeResponse
+
+    # 鍛樺伐瀹℃牳缁熻
+    employeeReviewStats(keyword: String): EmployeeReviewStats!
+
+    # 鍛樺伐瀹℃牳鍒楄〃
+    employeeReviewApplications(keyword: String, state: Int, page: Int, size: Int): EmployeeReviewPage!
 }
 
 # 鎵╁睍鍙樻洿绫诲瀷
diff --git a/wx/app.json b/wx/app.json
index e5d2a32..8bea719 100644
--- a/wx/app.json
+++ b/wx/app.json
@@ -5,6 +5,8 @@
     "pages/registration/registration",
     "pages/profile/profile",
     "pages/profile/personal-info",
+    "pages/profile/employee-review",
+    "pages/profile/employee-review-detail",
     "pages/project/detail",
     "pages/message/message",
     "pages/review/index",
@@ -19,28 +21,26 @@
     "navigationBarTextStyle": "black",
     "backgroundColor": "#f5f5f5"
   },
-
-    "tabBar": {
-      "custom": true,
-      "color": "#999999",
-      "selectedColor": "#007aff",
-      "backgroundColor": "#ffffff",
-      "list": [
-        {
-          "pagePath": "pages/index/index",
-          "text": "棣栭〉"
-        },
-        {
-          "pagePath": "pages/message/message",
-          "text": "娑堟伅"
-        },
-        {
-          "pagePath": "pages/profile/profile",
-          "text": "鎴戠殑"
-        }
-      ]
-    },
-
+  "tabBar": {
+    "custom": true,
+    "color": "#999999",
+    "selectedColor": "#007aff",
+    "backgroundColor": "#ffffff",
+    "list": [
+      {
+        "pagePath": "pages/index/index",
+        "text": "棣栭〉"
+      },
+      {
+        "pagePath": "pages/message/message",
+        "text": "娑堟伅"
+      },
+      {
+        "pagePath": "pages/profile/profile",
+        "text": "鎴戠殑"
+      }
+    ]
+  },
   "networkTimeout": {
     "request": 10000,
     "downloadFile": 10000
@@ -51,6 +51,8 @@
       "desc": "浣犵殑浣嶇疆淇℃伅灏嗙敤浜庡皬绋嬪簭浣嶇疆鎺ュ彛鐨勬晥鏋滃睍绀�"
     }
   },
-  "requiredBackgroundModes": ["audio"],
+  "requiredBackgroundModes": [
+    "audio"
+  ],
   "sitemapLocation": "sitemap.json"
-}
\ No newline at end of file
+}
diff --git a/wx/pages/profile/employee-review-detail.js b/wx/pages/profile/employee-review-detail.js
new file mode 100644
index 0000000..0b6dadc
--- /dev/null
+++ b/wx/pages/profile/employee-review-detail.js
@@ -0,0 +1,165 @@
+const { graphqlRequest } = require('../../lib/utils')
+
+const DETAIL_QUERY = `
+  query ActivityPlayerDetail($id: ID!) {
+    activityPlayerDetail(id: $id) {
+      id
+      activityName
+      projectName
+      description
+      feedback
+      state
+      playerInfo {
+        id
+        name
+        phone
+      }
+      submissionFiles {
+        id
+        name
+        fullUrl
+        fullThumbUrl
+        fileExt
+        mediaType
+      }
+    }
+  }
+`
+
+const APPROVE_MUTATION = `
+  mutation ApproveActivityPlayer($id: ID!, $feedback: String) {
+    approveActivityPlayer(activityPlayerId: $id, feedback: $feedback)
+  }
+`
+
+const REJECT_MUTATION = `
+  mutation RejectActivityPlayer($id: ID!, $feedback: String) {
+    rejectActivityPlayer(activityPlayerId: $id, feedback: $feedback)
+  }
+`
+
+Page({
+  data: {
+    loading: false,
+    submitting: false,
+    activityPlayerId: null,
+    detail: null,
+    feedback: ''
+  },
+
+  onLoad(options) {
+    if (options && options.id) {
+      if (typeof this.getOpenerEventChannel === 'function') {
+        this.eventChannel = this.getOpenerEventChannel()
+      }
+      this.setData({ activityPlayerId: options.id })
+      this.loadDetail()
+    } else {
+      wx.showToast({ title: '缂哄皯鎶ュ悕ID', icon: 'none' })
+      setTimeout(() => wx.navigateBack(), 800)
+    }
+  },
+
+  async loadDetail() {
+    if (!this.data.activityPlayerId) {
+      return
+    }
+
+    this.setData({ loading: true })
+
+    try {
+      const result = await graphqlRequest(DETAIL_QUERY, { id: this.data.activityPlayerId })
+      const detail = result && result.activityPlayerDetail
+
+      if (!detail) {
+        wx.showToast({ title: '鏈壘鍒版姤鍚嶄俊鎭�', icon: 'none' })
+        return
+      }
+
+      this.setData({
+        detail: {
+          ...detail,
+          stateText: this.getStateText(detail.state),
+          submissionFiles: (detail.submissionFiles || []).map(file => ({
+            id: file.id,
+            name: file.name,
+            url: file.fullUrl || file.fullThumbUrl || ''
+          }))
+        },
+        feedback: detail.feedback || ''
+      })
+    } catch (error) {
+      console.error('鍔犺浇瀹℃牳璇︽儏澶辫触:', error)
+      wx.showToast({ title: '鍔犺浇澶辫触', icon: 'none' })
+    } finally {
+      this.setData({ loading: false })
+    }
+  },
+
+  onFeedbackInput(e) {
+    this.setData({ feedback: e.detail.value })
+  },
+
+  onApprove() {
+    this.handleAudit('APPROVE')
+  },
+
+  onReject() {
+    this.handleAudit('REJECT')
+  },
+
+  async handleAudit(action) {
+    if (!this.data.activityPlayerId) {
+      return
+    }
+
+    const mutation = action === 'APPROVE' ? APPROVE_MUTATION : REJECT_MUTATION
+    const successText = action === 'APPROVE' ? '瀹℃牳閫氳繃' : '宸查┏鍥�'
+
+    this.setData({ submitting: true })
+
+    try {
+      const variables = {
+        id: Number(this.data.activityPlayerId),
+        feedback: this.data.feedback ? this.data.feedback.trim() : null
+      }
+      const result = await graphqlRequest(mutation, variables)
+      const success = result && (action === 'APPROVE' ? result.approveActivityPlayer : result.rejectActivityPlayer)
+
+      if (success) {
+        wx.showToast({ title: successText, icon: 'success' })
+        if (this.eventChannel) {
+          this.eventChannel.emit('auditUpdated', { id: this.data.activityPlayerId })
+        }
+        setTimeout(() => wx.navigateBack(), 600)
+      } else {
+        wx.showToast({ title: '鎿嶄綔澶辫触', icon: 'none' })
+      }
+    } catch (error) {
+      console.error('鎻愪氦瀹℃牳鍐崇瓥澶辫触:', error)
+      wx.showToast({ title: '鎿嶄綔澶辫触', icon: 'none' })
+    } finally {
+      this.setData({ submitting: false })
+    }
+  },
+
+  previewFile(e) {
+    const url = e.currentTarget.dataset.url
+    if (url) {
+      wx.navigateTo({ url: `/pages/webview/webview?url=${encodeURIComponent(url)}` })
+    }
+  },
+
+  getStateText(state) {
+    switch (state) {
+      case 0:
+        return '鏈鏍�'
+      case 1:
+        return '瀹℃牳閫氳繃'
+      case 2:
+        return '瀹℃牳椹冲洖'
+      default:
+        return '鏈煡鐘舵��'
+    }
+  }
+})
diff --git a/wx/pages/profile/employee-review-detail.json b/wx/pages/profile/employee-review-detail.json
new file mode 100644
index 0000000..08545b4
--- /dev/null
+++ b/wx/pages/profile/employee-review-detail.json
@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "瀹℃牳璇︽儏",
+  "enablePullDownRefresh": false
+}
diff --git a/wx/pages/profile/employee-review-detail.wxml b/wx/pages/profile/employee-review-detail.wxml
new file mode 100644
index 0000000..b66c8a2
--- /dev/null
+++ b/wx/pages/profile/employee-review-detail.wxml
@@ -0,0 +1,71 @@
+<view class="container">
+  <view wx:if="{{loading}}" class="loading">鍔犺浇涓�...</view>
+  <view wx:else>
+    <view class="info-card">
+      <text class="section-title">椤圭洰淇℃伅</text>
+      <view class="info-row">
+        <text class="label">姣旇禌鍚嶇О</text>
+        <text class="value">{{detail.activityName || '-'}} </text>
+      </view>
+      <view class="info-row">
+        <text class="label">椤圭洰鍚嶇О</text>
+        <text class="value">{{detail.projectName || '-'}} </text>
+      </view>
+      <view class="info-row">
+        <text class="label">褰撳墠鐘舵��</text>
+        <text class="status">{{detail.stateText || '鏈煡'}}</text>
+      </view>
+      <view class="info-block">
+        <text class="label">椤圭洰绠�浠�</text>
+        <text class="description">{{detail.description || '鏆傛棤绠�浠�'}}</text>
+      </view>
+    </view>
+
+    <view class="info-card">
+      <text class="section-title">瀛﹀憳淇℃伅</text>
+      <view class="info-row">
+        <text class="label">濮撳悕</text>
+        <text class="value">{{detail.playerInfo.name || '-'}}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">鑱旂郴鏂瑰紡</text>
+        <text class="value">{{detail.playerInfo.phone || '-'}}</text>
+      </view>
+    </view>
+
+    <view class="info-card" wx:if="{{detail.submissionFiles && detail.submissionFiles.length}}">
+      <text class="section-title">鎻愪氦璧勬枡</text>
+      <view class="file-item" wx:for="{{detail.submissionFiles}}" wx:key="id" data-url="{{item.url}}" bindtap="previewFile">
+        <text class="file-name">{{item.name || '璧勬枡鏂囦欢'}}</text>
+        <text class="file-action">棰勮</text>
+      </view>
+    </view>
+
+    <view class="info-card">
+      <text class="section-title">瀹℃牳鎰忚</text>
+      <textarea
+        class="feedback-input"
+        placeholder="璇疯緭鍏ュ鏍告剰瑙侊紙鍙�夛級"
+        value="{{feedback}}"
+        bindinput="onFeedbackInput"
+        maxlength="500"
+        auto-height
+      />
+    </view>
+
+    <view class="action-bar">
+      <button
+        class="reject-btn"
+        bindtap="onReject"
+        loading="{{submitting}}"
+        disabled="{{submitting}}"
+      >椹冲洖</button>
+      <button
+        class="approve-btn"
+        bindtap="onApprove"
+        loading="{{submitting}}"
+        disabled="{{submitting}}"
+      >瀹℃牳閫氳繃</button>
+    </view>
+  </view>
+</view>
diff --git a/wx/pages/profile/employee-review-detail.wxss b/wx/pages/profile/employee-review-detail.wxss
new file mode 100644
index 0000000..dc136a0
--- /dev/null
+++ b/wx/pages/profile/employee-review-detail.wxss
@@ -0,0 +1,124 @@
+.container {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding: 24rpx;
+}
+
+.loading {
+  text-align: center;
+  color: #666666;
+  margin-top: 200rpx;
+  font-size: 28rpx;
+}
+
+.info-card {
+  background: #ffffff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #1a1a1a;
+  margin-bottom: 16rpx;
+  display: block;
+}
+
+.info-row {
+  display: flex;
+  justify-content: space-between;
+  font-size: 28rpx;
+  color: #444444;
+  margin-bottom: 12rpx;
+}
+
+.label {
+  color: #888888;
+}
+
+.value {
+  color: #333333;
+}
+
+.status {
+  color: #1677ff;
+}
+
+.info-block {
+  margin-top: 12rpx;
+}
+
+.description {
+  color: #555555;
+  font-size: 26rpx;
+  line-height: 1.6;
+  margin-top: 8rpx;
+}
+
+.file-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+  font-size: 26rpx;
+}
+
+.file-item:last-child {
+  border-bottom: none;
+}
+
+.file-name {
+  color: #333333;
+  flex: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  padding-right: 20rpx;
+}
+
+.file-action {
+  color: #1677ff;
+}
+
+.feedback-input {
+  background: #f8f8f8;
+  border-radius: 12rpx;
+  padding: 16rpx;
+  min-height: 120rpx;
+  font-size: 26rpx;
+  color: #333333;
+}
+
+.action-bar {
+  position: sticky;
+  bottom: 0;
+  display: flex;
+  gap: 16rpx;
+  background: #f5f5f5;
+  padding-bottom: 24rpx;
+}
+
+.reject-btn {
+  flex: 1;
+  background: #ffffff;
+  color: #ff5454;
+  border: 2rpx solid #ff5454;
+  border-radius: 36rpx;
+  height: 80rpx;
+  line-height: 80rpx;
+  font-size: 28rpx;
+}
+
+.approve-btn {
+  flex: 1;
+  background: linear-gradient(135deg, #16a75c 0%, #4bc077 100%);
+  color: #ffffff;
+  border-radius: 36rpx;
+  height: 80rpx;
+  line-height: 80rpx;
+  font-size: 28rpx;
+}
diff --git a/wx/pages/profile/employee-review.js b/wx/pages/profile/employee-review.js
new file mode 100644
index 0000000..5c3ad4a
--- /dev/null
+++ b/wx/pages/profile/employee-review.js
@@ -0,0 +1,217 @@
+const { graphqlRequest } = require('../../lib/utils')
+
+const LIST_QUERY = `
+  query EmployeeReviewApplications($keyword: String, $state: Int, $page: Int, $size: Int) {
+    employeeReviewApplications(keyword: $keyword, state: $state, page: $page, size: $size) {
+      content {
+        id
+        playerName
+        projectName
+        activityName
+        state
+        stateText
+        stateType
+        applyTime
+      }
+      totalElements
+      page
+      size
+    }
+  }
+`
+
+const STATS_QUERY = `
+  query EmployeeReviewStats($keyword: String) {
+    employeeReviewStats(keyword: $keyword) {
+      pendingCount
+      approvedCount
+      rejectedCount
+    }
+  }
+`
+
+Page({
+  data: {
+    loading: false,
+    loadingMore: false,
+    hasMore: true,
+    tabs: [
+      { label: '寰呭鏍�', value: 0 },
+      { label: '宸插鏍�', value: 1 },
+      { label: '椹冲洖', value: 2 }
+    ],
+    currentTab: 0,
+    searchKeyword: '',
+    list: [],
+    page: 1,
+    pageSize: 10,
+    stats: {
+      pendingCount: 0,
+      approvedCount: 0,
+      rejectedCount: 0
+    },
+    employeeReviewStats: {
+      pendingCount: 0,
+      approvedCount: 0,
+      rejectedCount: 0
+    },
+    needRefresh: false
+  },
+
+  onLoad() {
+    this.initData()
+  },
+
+  onShow() {
+    if (this.data.needRefresh) {
+      this.initData()
+      this.setData({ needRefresh: false })
+    }
+  },
+
+  onPullDownRefresh() {
+    this.initData().finally(() => {
+      wx.stopPullDownRefresh()
+    })
+  },
+
+  onReachBottom() {
+    if (this.data.hasMore && !this.data.loadingMore) {
+      this.loadList(false)
+    }
+  },
+
+  async initData() {
+    this.setData({ loading: true, page: 1, list: [], hasMore: true })
+
+    try {
+      await Promise.all([
+        this.loadList(true),
+        this.loadStats(this.data.searchKeyword)
+      ])
+    } catch (error) {
+      console.error('鍒濆鍖栧鏍告暟鎹け璐�:', error)
+    } finally {
+      this.setData({ loading: false })
+    }
+  },
+
+  async loadStats(keyword) {
+    try {
+      const variables = {}
+      const trimmed = keyword && typeof keyword === 'string' ? keyword.trim() : ''
+      if (trimmed) {
+        variables.keyword = trimmed
+      }
+
+      const result = await graphqlRequest(STATS_QUERY, variables)
+      if (result && result.employeeReviewStats) {
+        this.setData({ stats: result.employeeReviewStats, employeeReviewStats: result.employeeReviewStats })
+      }
+    } catch (error) {
+      console.error('鍔犺浇瀹℃牳缁熻澶辫触:', error)
+    }
+  },
+
+  async loadList(reset = false) {
+    if (reset) {
+      this.setData({ loading: true })
+    } else {
+      this.setData({ loadingMore: true })
+    }
+
+    const nextPage = reset ? 1 : this.data.page + 1
+    const keywordInput = this.data.searchKeyword || ''
+    const trimmedKeyword = keywordInput.trim()
+    const variables = {
+      keyword: trimmedKeyword ? trimmedKeyword : null,
+      state: this.getStateByTab(this.data.currentTab),
+      page: nextPage,
+      size: this.data.pageSize
+    }
+
+    try {
+      const result = await graphqlRequest(LIST_QUERY, variables)
+      const pageData = result && result.employeeReviewApplications
+      const items = pageData && Array.isArray(pageData.content) ? pageData.content : []
+      const list = reset ? items : this.data.list.concat(items)
+      const total = pageData && typeof pageData.totalElements === 'number' ? pageData.totalElements : 0
+      const hasMore = nextPage * this.data.pageSize < total
+
+      this.setData({
+        list,
+        page: nextPage,
+        hasMore
+      })
+    } catch (error) {
+      console.error('鍔犺浇瀹℃牳鍒楄〃澶辫触:', error)
+      wx.showToast({ title: '鍔犺浇澶辫触', icon: 'none' })
+    } finally {
+      if (reset) {
+        this.setData({ loading: false })
+      } else {
+        this.setData({ loadingMore: false })
+      }
+    }
+  },
+
+  onSearchInput(e) {
+    this.setData({ searchKeyword: e.detail.value || '' })
+  },
+
+  onSearch() {
+    this.initData()
+  },
+
+  clearSearch() {
+    if (!this.data.searchKeyword) return
+    this.setData({ searchKeyword: '' })
+    this.initData()
+  },
+
+  switchTab(e) {
+    const index = Number(e.currentTarget.dataset.index || 0)
+    if (index === this.data.currentTab) {
+      return
+    }
+
+    this.setData({
+      currentTab: index,
+      page: 1,
+      list: [],
+      hasMore: true
+    })
+
+    this.initData()
+  },
+
+  getStateByTab(index) {
+    switch (index) {
+      case 0:
+        return 0
+      case 1:
+        return 1
+      case 2:
+        return 2
+      default:
+        return null
+    }
+  },
+
+  goToDetail(e) {
+    const id = e.currentTarget.dataset.id
+    if (!id) {
+      wx.showToast({ title: '鎶ュ悕ID鏃犳晥', icon: 'none' })
+      return
+    }
+
+    wx.navigateTo({
+      url: `/pages/profile/employee-review-detail?id=${id}`,
+      events: {
+        auditUpdated: () => {
+          this.setData({ needRefresh: true })
+        }
+      }
+    })
+  }
+})
diff --git a/wx/pages/profile/employee-review.json b/wx/pages/profile/employee-review.json
new file mode 100644
index 0000000..271e618
--- /dev/null
+++ b/wx/pages/profile/employee-review.json
@@ -0,0 +1,5 @@
+{
+  "navigationBarTitleText": "鎴戠殑瀹℃牳",
+  "enablePullDownRefresh": true,
+  "backgroundTextStyle": "dark"
+}
diff --git a/wx/pages/profile/employee-review.wxml b/wx/pages/profile/employee-review.wxml
new file mode 100644
index 0000000..a1c4e8c
--- /dev/null
+++ b/wx/pages/profile/employee-review.wxml
@@ -0,0 +1,72 @@
+<view class="container">
+  <view class="search-bar">
+    <input
+      class="search-input"
+      value="{{searchKeyword}}"
+      placeholder="鎼滅储姣旇禌 / 椤圭洰 / 瀛﹀憳"
+      bindinput="onSearchInput"
+      confirm-type="search"
+      bindconfirm="onSearch"
+    />
+    <view class="search-actions">
+      <button class="primary-btn" bindtap="onSearch">鎼滅储</button>
+      <button class="plain-btn" bindtap="clearSearch" wx:if="{{searchKeyword}}">娓呯┖</button>
+    </view>
+  </view>
+
+  <view class="tab-bar">
+    <view
+      class="tab-item {{currentTab === index ? 'active' : ''}}"
+      wx:for="{{tabs}}"
+      wx:key="value"
+      data-index="{{index}}"
+      bindtap="switchTab"
+    >
+      <text class="tab-label">{{item.label}}</text>
+      <text class="tab-count">
+        {{item.value === 0 ? employeeReviewStats.pendingCount || 0 :
+          item.value === 1 ? employeeReviewStats.approvedCount || 0 :
+          employeeReviewStats.rejectedCount || 0}}
+      </text>
+    </view>
+  </view>
+
+  <view class="list-container">
+    <view wx:if="{{list.length > 0}}">
+      <view class="audit-card" wx:for="{{list}}" wx:key="id">
+        <view class="card-header">
+          <text class="card-title">{{item.projectName || '鏈懡鍚嶉」鐩�'}}</text>
+          <text class="card-status status-{{item.stateType || 'info'}}">{{item.stateText || ''}}</text>
+        </view>
+        <view class="card-row">
+          <text class="card-label">姣旇禌</text>
+          <text class="card-value">{{item.activityName || '-'}}</text>
+        </view>
+        <view class="card-row">
+          <text class="card-label">瀛﹀憳</text>
+          <text class="card-value">{{item.playerName || '-'}}</text>
+        </view>
+        <view class="card-row">
+          <text class="card-label">鎶ュ悕鏃堕棿</text>
+          <text class="card-value">{{item.applyTime || '-'}}</text>
+        </view>
+        <view class="card-footer">
+          <text class="card-tip">鏈�鏂扮姸鎬侊細{{item.stateText || '鏈煡'}}</text>
+          <button class="action-btn" data-id="{{item.id}}" bindtap="goToDetail">瀹℃牳</button>
+        </view>
+      </view>
+      <view class="load-more" wx:if="{{loadingMore}}">
+        <text>鍔犺浇涓�...</text>
+      </view>
+      <view class="load-more" wx:elif="{{!hasMore}}">
+        <text>娌℃湁鏇村鏁版嵁</text>
+      </view>
+    </view>
+
+    <view wx:else class="empty-state">
+      <text class="empty-icon">馃搨</text>
+      <text class="empty-text">鏆傛棤绗﹀悎鏉′欢鐨勯」鐩�</text>
+      <text class="empty-desc">灏濊瘯璋冩暣绛涢�夋潯浠舵垨鎼滅储鍏朵粬鍏抽敭璇�</text>
+    </view>
+  </view>
+</view>
diff --git a/wx/pages/profile/employee-review.wxss b/wx/pages/profile/employee-review.wxss
new file mode 100644
index 0000000..9979dbd
--- /dev/null
+++ b/wx/pages/profile/employee-review.wxss
@@ -0,0 +1,195 @@
+.container {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding: 24rpx;
+}
+
+.search-bar {
+  display: flex;
+  align-items: center;
+  gap: 16rpx;
+  margin-bottom: 24rpx;
+}
+
+.search-input {
+  flex: 1;
+  height: 72rpx;
+  background: #ffffff;
+  border-radius: 36rpx;
+  padding: 0 24rpx;
+  font-size: 28rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+}
+
+.search-actions {
+  display: flex;
+  gap: 12rpx;
+}
+
+.primary-btn {
+  background: #1677ff;
+  color: #ffffff;
+  border-radius: 36rpx;
+  padding: 0 32rpx;
+  height: 72rpx;
+  line-height: 72rpx;
+  font-size: 28rpx;
+}
+
+.plain-btn {
+  background: #ffffff;
+  color: #1677ff;
+  border-radius: 36rpx;
+  padding: 0 32rpx;
+  height: 72rpx;
+  line-height: 72rpx;
+  font-size: 28rpx;
+  border: 2rpx solid #1677ff;
+}
+
+.tab-bar {
+  display: flex;
+  background: #ffffff;
+  border-radius: 16rpx;
+  padding: 12rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+.tab-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 20rpx 0;
+  border-radius: 12rpx;
+  font-size: 26rpx;
+  color: #666666;
+  transition: all 0.2s ease;
+}
+
+.tab-item.active {
+  background: linear-gradient(135deg, #1677ff 0%, #4091ff 100%);
+  color: #ffffff;
+}
+
+.tab-count {
+  margin-top: 8rpx;
+  font-size: 32rpx;
+  font-weight: 600;
+}
+
+.list-container {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+
+.audit-card {
+  background: #ffffff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
+  display: flex;
+  flex-direction: column;
+  gap: 16rpx;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.card-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.card-status {
+  font-size: 26rpx;
+  padding: 4rpx 16rpx;
+  border-radius: 24rpx;
+}
+
+.status-warning {
+  background: rgba(255, 180, 0, 0.15);
+  color: #ffa114;
+}
+
+.status-success {
+  background: rgba(17, 201, 128, 0.15);
+  color: #16a75c;
+}
+
+.status-danger {
+  background: rgba(255, 84, 84, 0.15);
+  color: #ff5454;
+}
+
+.status-info {
+  background: rgba(64, 145, 255, 0.15);
+  color: #1677ff;
+}
+
+.card-row {
+  display: flex;
+  justify-content: space-between;
+  font-size: 26rpx;
+  color: #444444;
+}
+
+.card-label {
+  color: #888888;
+}
+
+.card-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: 8rpx;
+}
+
+.card-tip {
+  font-size: 24rpx;
+  color: #999999;
+}
+
+.action-btn {
+  background: linear-gradient(135deg, #1677ff 0%, #4091ff 100%);
+  color: #ffffff;
+  padding: 0 32rpx;
+  height: 64rpx;
+  line-height: 64rpx;
+  border-radius: 32rpx;
+  font-size: 26rpx;
+}
+
+.load-more {
+  text-align: center;
+  color: #999999;
+  font-size: 26rpx;
+  padding: 20rpx 0;
+}
+
+.empty-state {
+  margin-top: 120rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  color: #999999;
+  gap: 12rpx;
+}
+
+.empty-icon {
+  font-size: 72rpx;
+}
+
+.empty-text {
+  font-size: 28rpx;
+}
+
+.empty-desc {
+  font-size: 24rpx;
+}
diff --git a/wx/pages/profile/profile.js b/wx/pages/profile/profile.js
index 15cedc9..99de718 100644
--- a/wx/pages/profile/profile.js
+++ b/wx/pages/profile/profile.js
@@ -27,6 +27,14 @@
     isJudge: false,
     isOrganizer: false,
     hasPlayer: false,
+    isEmployee: false,
+    
+    // 鍛樺伐瀹℃牳缁熻
+    employeeReviewStats: {
+      pendingCount: 0,
+      approvedCount: 0,
+      rejectedCount: 0
+    },
     
     // 璇勫鐩稿叧鏁版嵁
     judgeStats: {
@@ -198,6 +206,12 @@
             grade
             roles
             createdAt
+            employee {
+              id
+              name
+              roleId
+              description
+            }
             player {
               id
               name
@@ -218,6 +232,7 @@
         const isJudge = userRoles.includes('JUDGE')
         const isOrganizer = userRoles.includes('ORGANIZER')
         const hasPlayer = userInfo.player && userInfo.player.id
+        const isEmployee = !!(userInfo.employee && userInfo.employee.id)
         
         // 澶勭悊澶村儚鏂囧瓧
         const avatarText = (userInfo.name || '鐢ㄦ埛').substring(0, 1)
@@ -228,7 +243,8 @@
           userRoles,
           isJudge,
           isOrganizer,
-          hasPlayer
+          hasPlayer,
+          isEmployee
         })
         
         // 鏇存柊鍏ㄥ眬鐢ㄦ埛淇℃伅
@@ -241,6 +257,18 @@
         
         if (isOrganizer) {
           this.loadOrganizerStats()
+        }
+
+        if (isEmployee) {
+          this.loadEmployeeReviewStats()
+        } else {
+          this.setData({
+            employeeReviewStats: {
+              pendingCount: 0,
+              approvedCount: 0,
+              rejectedCount: 0
+            }
+          })
         }
       }
     } catch (error) {
@@ -358,6 +386,39 @@
     }
   },
 
+  // 鍔犺浇鍛樺伐瀹℃牳缁熻
+  async loadEmployeeReviewStats(keyword = null) {
+    if (!this.data.isEmployee) {
+      return
+    }
+
+    try {
+      const query = `
+        query EmployeeReviewStats($keyword: String) {
+          employeeReviewStats(keyword: $keyword) {
+            pendingCount
+            approvedCount
+            rejectedCount
+          }
+        }
+      `
+
+      const variables = {}
+      if (keyword && typeof keyword === 'string' && keyword.trim()) {
+        variables.keyword = keyword.trim()
+      }
+
+      const result = await graphqlRequest(query, variables)
+
+      if (result && result.employeeReviewStats) {
+        this.setData({
+          employeeReviewStats: result.employeeReviewStats
+        })
+      }
+    } catch (error) {
+      console.error('鍔犺浇鍛樺伐瀹℃牳缁熻澶辫触:', error)
+    }
+  },
   // 鍔犺浇璇勫缁熻鏁版嵁
   async loadJudgeStats() {
     try {
@@ -465,6 +526,17 @@
     })
   },
 
+  // 璺宠浆鍒板憳宸ュ鏍搁〉闈�
+  goToEmployeeReviewPage() {
+    if (!this.data.isEmployee) {
+      return
+    }
+
+    wx.navigateTo({
+      url: '/pages/profile/employee-review'
+    })
+  },
+
 
 
   // 鏌ョ湅鎶ュ悕璇︽儏
diff --git a/wx/pages/profile/profile.wxml b/wx/pages/profile/profile.wxml
index fd4c5d4..6b3b802 100644
--- a/wx/pages/profile/profile.wxml
+++ b/wx/pages/profile/profile.wxml
@@ -97,6 +97,33 @@
       </view>
     </view>
 
+    <!-- 鎴戠殑瀹℃牳鍖哄煙 - 浠呭憳宸ュ彲瑙� -->
+    <view class="review-section" wx:if="{{isEmployee}}">
+      <view class="section-header">
+        <text class="section-title">鎴戠殑瀹℃牳</text>
+        <view class="review-action-btn" bindtap="goToEmployeeReviewPage">
+          <text class="action-text">瀹℃牳</text>
+          <text class="action-arrow">></text>
+        </view>
+      </view>
+
+      <view class="review-stats">
+        <view class="stat-item">
+          <text class="stat-number">{{employeeReviewStats.pendingCount || 0}}</text>
+          <text class="stat-label">寰呭鏍�</text>
+        </view>
+        <view class="stat-divider"></view>
+        <view class="stat-item">
+          <text class="stat-number">{{employeeReviewStats.approvedCount || 0}}</text>
+          <text class="stat-label">宸插鏍�</text>
+        </view>
+        <view class="stat-divider"></view>
+        <view class="stat-item">
+          <text class="stat-number">{{employeeReviewStats.rejectedCount || 0}}</text>
+          <text class="stat-label">椹冲洖</text>
+        </view>
+      </view>
+    </view>
 
   </view>
-</view>
\ No newline at end of file
+</view>

--
Gitblit v1.8.0