Sfoglia il codice sorgente

feat: 新增指导师套餐和绑定功能控制器

- 新增AdminPackageController - 套餐管理
- 新增BindController - 家庭绑定指导师
- 新增BindInviteController - 绑定邀请
- 新增GuideController - 指导师管理
- 新增GuideFamilyTaskController - 指导师家庭任务管理
- 新增MarketController - 套餐市场
- 新增PackagePaymentController - 套餐支付
- 新增TaskTemplatePackageController - 任务模板套餐
- 新增PackageOrder实体和Mapper
- 新增BindInviteService和PackagePaymentService
- 更新schema.sql和配置文件
User 5 mesi fa
parent
commit
aa390e0a86
21 ha cambiato i file con 1777 aggiunte e 168 eliminazioni
  1. 341 164
      zxyj-backend/src/main/java/com/zxyj/controller/AdminController.java
  2. 53 0
      zxyj-backend/src/main/java/com/zxyj/controller/AdminPackageController.java
  3. 60 0
      zxyj-backend/src/main/java/com/zxyj/controller/BindController.java
  4. 39 0
      zxyj-backend/src/main/java/com/zxyj/controller/BindInviteController.java
  5. 101 0
      zxyj-backend/src/main/java/com/zxyj/controller/GuideController.java
  6. 263 0
      zxyj-backend/src/main/java/com/zxyj/controller/GuideFamilyTaskController.java
  7. 128 0
      zxyj-backend/src/main/java/com/zxyj/controller/MarketController.java
  8. 113 0
      zxyj-backend/src/main/java/com/zxyj/controller/PackagePaymentController.java
  9. 130 0
      zxyj-backend/src/main/java/com/zxyj/controller/TaskTemplatePackageController.java
  10. 1 0
      zxyj-backend/src/main/java/com/zxyj/dto/LoginResultDTO.java
  11. 6 0
      zxyj-backend/src/main/java/com/zxyj/dto/UpdateUserDTO.java
  12. 48 0
      zxyj-backend/src/main/java/com/zxyj/entity/PackageOrder.java
  13. 12 0
      zxyj-backend/src/main/java/com/zxyj/entity/User.java
  14. 9 0
      zxyj-backend/src/main/java/com/zxyj/mapper/PackageOrderMapper.java
  15. 96 0
      zxyj-backend/src/main/java/com/zxyj/service/BindInviteService.java
  16. 285 0
      zxyj-backend/src/main/java/com/zxyj/service/PackagePaymentService.java
  17. 36 0
      zxyj-backend/src/main/java/com/zxyj/service/TaskService.java
  18. 8 4
      zxyj-backend/src/main/java/com/zxyj/service/UserService.java
  19. 10 0
      zxyj-backend/src/main/resources/application.yml
  20. 31 0
      zxyj-backend/src/main/resources/migration_add_admin_task_templates.sql
  21. 7 0
      zxyj-backend/src/main/resources/schema.sql

+ 341 - 164
zxyj-backend/src/main/java/com/zxyj/controller/AdminController.java

@@ -28,26 +28,26 @@ public class AdminController {
     @Resource
     private RewardMapper rewardMapper;
 
-  @Resource
-  private PointsLogMapper pointsLogMapper;
+    @Resource
+    private PointsLogMapper pointsLogMapper;
 
-  @Resource
-  private FamilyMapper familyMapper;
+    @Resource
+    private FamilyMapper familyMapper;
 
-  @Resource
-  private TaskTemplateMapper taskTemplateMapper;
+    @Resource
+    private TaskTemplateMapper taskTemplateMapper;
 
-  // 用户管理
-    @GetMapping("/users")
-    public Result<Page<User>> getUsers(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer size) {
+    // 用户管理
+    @PostMapping("/users")
+    public Result<Page<User>> getUsers(@RequestBody Map<String, Object> params) {
+        Integer page = params.get("page") != null ? (Integer) params.get("page") : 1;
+        Integer size = params.get("size") != null ? (Integer) params.get("size") : 10;
         Page<User> pageParam = new Page<>(page, size);
         Page<User> result = userMapper.selectPage(pageParam, null);
         return Result.success(result);
     }
 
-    @GetMapping("/users/{id}")
+    @PostMapping("/users/{id}")
     public Result<User> getUser(@PathVariable Long id) {
         return Result.success(userMapper.selectById(id));
     }
@@ -66,16 +66,16 @@ public class AdminController {
     }
 
     // 孩子管理
-    @GetMapping("/children")
-    public Result<Page<Child>> getChildren(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer size) {
+    @PostMapping("/children")
+    public Result<Page<Child>> getChildren(@RequestBody Map<String, Object> params) {
+        Integer page = params.get("page") != null ? (Integer) params.get("page") : 1;
+        Integer size = params.get("size") != null ? (Integer) params.get("size") : 10;
         Page<Child> pageParam = new Page<>(page, size);
         Page<Child> result = childMapper.selectPage(pageParam, null);
         return Result.success(result);
     }
 
-    @GetMapping("/children/{id}")
+    @PostMapping("/children/{id}")
     public Result<Child> getChild(@PathVariable Long id) {
         return Result.success(childMapper.selectById(id));
     }
@@ -123,26 +123,27 @@ public class AdminController {
     }
 
     // 任务管理
-    @GetMapping("/tasks")
-    public Result<Page<Task>> getTasks(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer size,
-            @RequestParam(required = false) String status,
-            @RequestParam(required = false) Long childId) {
+    @PostMapping("/tasks")
+    public Result<Page<Task>> getTasks(@RequestBody Map<String, Object> params) {
+        Integer page = params.get("page") != null ? (Integer) params.get("page") : 1;
+        Integer size = params.get("size") != null ? (Integer) params.get("size") : 10;
+        String status = (String) params.get("status");
+        Long familyId = params.get("familyId") != null ? ((Number) params.get("familyId")).longValue() : null;
+        
         Page<Task> pageParam = new Page<>(page, size);
         LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
         if (status != null && !status.isEmpty()) {
             wrapper.eq(Task::getStatus, status);
         }
-        if (childId != null) {
-            wrapper.eq(Task::getChildId, childId);
+        if (familyId != null) {
+            wrapper.eq(Task::getFamilyId, familyId);
         }
         wrapper.orderByDesc(Task::getCreatedAt);
         Page<Task> result = taskMapper.selectPage(pageParam, wrapper);
         return Result.success(result);
     }
 
-    @GetMapping("/tasks/stats")
+    @PostMapping("/tasks/stats")
     public Result<Map<String, Object>> getTaskStats() {
         Map<String, Object> stats = new HashMap<>();
         
@@ -157,28 +158,33 @@ public class AdminController {
     }
 
     // 奖励管理
-    @GetMapping("/rewards")
-    public Result<Page<Reward>> getRewards(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer size,
-            @RequestParam(required = false) String status) {
+    @PostMapping("/rewards")
+    public Result<Page<Reward>> getRewards(@RequestBody Map<String, Object> params) {
+        Integer page = params.get("page") != null ? (Integer) params.get("page") : 1;
+        Integer size = params.get("size") != null ? (Integer) params.get("size") : 10;
+        String status = (String) params.get("status");
+        Long familyId = params.get("familyId") != null ? ((Number) params.get("familyId")).longValue() : null;
+        
         Page<Reward> pageParam = new Page<>(page, size);
         LambdaQueryWrapper<Reward> wrapper = new LambdaQueryWrapper<>();
         if (status != null && !status.isEmpty()) {
             wrapper.eq(Reward::getStatus, status);
         }
+        if (familyId != null) {
+            wrapper.eq(Reward::getFamilyId, familyId);
+        }
         wrapper.orderByDesc(Reward::getCreatedAt);
         Page<Reward> result = rewardMapper.selectPage(pageParam, wrapper);
         return Result.success(result);
     }
 
-    @GetMapping("/rewards/templates")
+    @PostMapping("/rewards/templates")
     public Result<Object> getRewardTemplates() {
         return Result.success(rewardMapper.selectList(new LambdaQueryWrapper<Reward>()
                 .eq(Reward::getIsTemplate, 1)));
     }
 
-    @PostMapping("/rewards/templates")
+    @PostMapping("/rewards/create")
     public Result<Long> createRewardTemplate(@RequestBody Reward reward) {
         reward.setIsTemplate(1);
         reward.setCreatedAt(new Date());
@@ -237,12 +243,13 @@ public class AdminController {
     }
 
     // 积分管理
-    @GetMapping("/points/logs")
-    public Result<Page<PointsLog>> getPointsLogs(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer size,
-            @RequestParam(required = false) Long childId,
-            @RequestParam(required = false) String type) {
+    @PostMapping("/points/logs")
+    public Result<Page<PointsLog>> getPointsLogs(@RequestBody Map<String, Object> params) {
+        Integer page = params.get("page") != null ? (Integer) params.get("page") : 1;
+        Integer size = params.get("size") != null ? (Integer) params.get("size") : 10;
+        Long childId = params.get("childId") != null ? ((Number) params.get("childId")).longValue() : null;
+        String type = (String) params.get("type");
+        
         Page<PointsLog> pageParam = new Page<>(page, size);
         LambdaQueryWrapper<PointsLog> wrapper = new LambdaQueryWrapper<>();
         if (childId != null) {
@@ -256,129 +263,299 @@ public class AdminController {
         return Result.success(result);
     }
 
-  @GetMapping("/points/stats")
-  public Result<Map<String, Object>> getPointsStats() {
-    Map<String, Object> stats = new HashMap<>();
-
-    Long totalChildren = childMapper.selectCount(null);
-    Long totalPoints = childMapper.selectCount(null); // 需要单独查询sum
-
-    stats.put("totalChildren", totalChildren);
-
-    return Result.success(stats);
-  }
-
-  // 家庭管理
-  @GetMapping("/families")
-  public Result<Page<Family>> getFamilies(
-      @RequestParam(defaultValue = "1") Integer page,
-      @RequestParam(defaultValue = "10") Integer size,
-      @RequestParam(required = false) String name) {
-    Page<Family> pageParam = new Page<>(page, size);
-    LambdaQueryWrapper<Family> wrapper = new LambdaQueryWrapper<>();
-    if (name != null && !name.isEmpty()) {
-      wrapper.like(Family::getName, name);
-    }
-    wrapper.orderByDesc(Family::getCreatedAt);
-    Page<Family> result = familyMapper.selectPage(pageParam, wrapper);
-    return Result.success(result);
-  }
-
-  @GetMapping("/families/{id}")
-  public Result<Family> getFamily(@PathVariable Long id) {
-    return Result.success(familyMapper.selectById(id));
-  }
-
-  @GetMapping("/families/{id}/members")
-  public Result<Map<String, Object>> getFamilyMembers(@PathVariable Long id) {
-    Family family = familyMapper.selectById(id);
-    if (family == null) {
-      return Result.error("家庭不存在");
-    }
-
-    // 查询家长
-    LambdaQueryWrapper<User> parentWrapper = new LambdaQueryWrapper<>();
-    parentWrapper.eq(User::getFamilyId, id).eq(User::getRole, "parent");
-    java.util.List<User> parents = userMapper.selectList(parentWrapper);
-
-    // 查询孩子
-    LambdaQueryWrapper<Child> childWrapper = new LambdaQueryWrapper<>();
-    childWrapper.eq(Child::getFamilyId, id);
-    java.util.List<Child> children = childMapper.selectList(childWrapper);
-
-    Map<String, Object> result = new HashMap<>();
-    result.put("family", family);
-    result.put("parents", parents);
-    result.put("children", children);
-
-    return Result.success(result);
-  }
-
-  @PutMapping("/families/{id}")
-  public Result<Boolean> updateFamily(@PathVariable Long id, @RequestBody Family family) {
-    family.setId(id);
-    family.setUpdatedAt(new Date());
-    familyMapper.updateById(family);
-    return Result.success(true);
-  }
-
-  @DeleteMapping("/families/{id}")
-  public Result<Boolean> deleteFamily(@PathVariable Long id) {
-    familyMapper.deleteById(id);
-    return Result.success(true);
-  }
-
-  // 任务模板管理
-  @GetMapping("/task-templates")
-  public Result<Page<TaskTemplate>> getTaskTemplates(
-      @RequestParam(defaultValue = "1") Integer page,
-      @RequestParam(defaultValue = "10") Integer size,
-      @RequestParam(required = false) String category,
-      @RequestParam(required = false) String difficulty,
-      @RequestParam(required = false) Integer isActive) {
-    Page<TaskTemplate> pageParam = new Page<>(page, size);
-    LambdaQueryWrapper<TaskTemplate> wrapper = new LambdaQueryWrapper<>();
-    if (category != null && !category.isEmpty()) {
-      wrapper.eq(TaskTemplate::getCategory, category);
-    }
-    if (difficulty != null && !difficulty.isEmpty()) {
-      wrapper.eq(TaskTemplate::getDifficulty, difficulty);
-    }
-    if (isActive != null) {
-      wrapper.eq(TaskTemplate::getIsActive, isActive);
-    }
-    wrapper.orderByDesc(TaskTemplate::getCreatedAt);
-    Page<TaskTemplate> result = taskTemplateMapper.selectPage(pageParam, wrapper);
-    return Result.success(result);
-  }
-
-  @GetMapping("/task-templates/{id}")
-  public Result<TaskTemplate> getTaskTemplate(@PathVariable Long id) {
-    return Result.success(taskTemplateMapper.selectById(id));
-  }
-
-  @PostMapping("/task-templates")
-  public Result<Long> createTaskTemplate(@RequestBody TaskTemplate template) {
-    template.setCreatedAt(new Date());
-    template.setUpdatedAt(new Date());
-    if (template.getIsActive() == null) {
-      template.setIsActive(1);
-    }
-    taskTemplateMapper.insert(template);
-    return Result.success(template.getId());
-  }
-
-  @PutMapping("/task-templates/{id}")
-  public Result<Boolean> updateTaskTemplate(@PathVariable Long id, @RequestBody TaskTemplate template) {
-    template.setId(id);
-    template.setUpdatedAt(new Date());
-    taskTemplateMapper.updateById(template);
-    return Result.success(true);
-  }
-
-  @DeleteMapping("/task-templates/{id}")
-  public Result<Boolean> deleteTaskTemplate(@PathVariable Long id) {
-    taskTemplateMapper.deleteById(id);
-    return Result.success(true);
-  }
+    @PostMapping("/points/stats")
+    public Result<Map<String, Object>> getPointsStats() {
+        Map<String, Object> stats = new HashMap<>();
+
+        Long totalChildren = childMapper.selectCount(null);
+
+        stats.put("totalChildren", totalChildren);
+
+        return Result.success(stats);
+    }
+
+    // 家庭管理
+    @PostMapping("/families")
+    public Result<Page<Family>> getFamilies(@RequestBody Map<String, Object> params) {
+        Integer page = params.get("page") != null ? (Integer) params.get("page") : 1;
+        Integer size = params.get("size") != null ? (Integer) params.get("size") : 10;
+        String name = (String) params.get("name");
+        
+        Page<Family> pageParam = new Page<>(page, size);
+        LambdaQueryWrapper<Family> wrapper = new LambdaQueryWrapper<>();
+        if (name != null && !name.isEmpty()) {
+            wrapper.like(Family::getName, name);
+        }
+        wrapper.orderByDesc(Family::getCreatedAt);
+        Page<Family> result = familyMapper.selectPage(pageParam, wrapper);
+        return Result.success(result);
+    }
+
+    @PostMapping("/families/{id}")
+    public Result<Family> getFamily(@PathVariable Long id) {
+        return Result.success(familyMapper.selectById(id));
+    }
+
+    @PostMapping("/families/{id}/members")
+    public Result<Map<String, Object>> getFamilyMembers(@PathVariable Long id) {
+        Family family = familyMapper.selectById(id);
+        if (family == null) {
+            return Result.error("家庭不存在");
+        }
+
+        // 查询家长
+        LambdaQueryWrapper<User> parentWrapper = new LambdaQueryWrapper<>();
+        parentWrapper.eq(User::getFamilyId, id).eq(User::getRole, "parent");
+        java.util.List<User> parents = userMapper.selectList(parentWrapper);
+
+        // 查询孩子
+        LambdaQueryWrapper<Child> childWrapper = new LambdaQueryWrapper<>();
+        childWrapper.eq(Child::getFamilyId, id);
+        java.util.List<Child> children = childMapper.selectList(childWrapper);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("family", family);
+        result.put("parents", parents);
+        result.put("children", children);
+
+        return Result.success(result);
+    }
+
+    @PutMapping("/families/{id}")
+    public Result<Boolean> updateFamily(@PathVariable Long id, @RequestBody Family family) {
+        family.setId(id);
+        family.setUpdatedAt(new Date());
+        familyMapper.updateById(family);
+        return Result.success(true);
+    }
+
+    @DeleteMapping("/families/{id}")
+    public Result<Boolean> deleteFamily(@PathVariable Long id) {
+        familyMapper.deleteById(id);
+        return Result.success(true);
+    }
+
+    // 任务模板管理
+    @PostMapping("/task-templates")
+    public Result<Page<TaskTemplate>> getTaskTemplates(@RequestBody Map<String, Object> params) {
+        Integer page = params.get("page") != null ? (Integer) params.get("page") : 1;
+        Integer size = params.get("size") != null ? (Integer) params.get("size") : 10;
+        String category = (String) params.get("category");
+        String difficulty = (String) params.get("difficulty");
+        Integer isActive = params.get("isActive") != null ? (Integer) params.get("isActive") : null;
+        
+        Page<TaskTemplate> pageParam = new Page<>(page, size);
+        LambdaQueryWrapper<TaskTemplate> wrapper = new LambdaQueryWrapper<>();
+        if (category != null && !category.isEmpty()) {
+            wrapper.eq(TaskTemplate::getCategory, category);
+        }
+        if (difficulty != null && !difficulty.isEmpty()) {
+            wrapper.eq(TaskTemplate::getDifficulty, difficulty);
+        }
+        if (isActive != null) {
+            wrapper.eq(TaskTemplate::getIsActive, isActive);
+        }
+        wrapper.orderByDesc(TaskTemplate::getCreatedAt);
+        Page<TaskTemplate> result = taskTemplateMapper.selectPage(pageParam, wrapper);
+        return Result.success(result);
+    }
+
+    @PostMapping("/task-templates/{id}")
+    public Result<TaskTemplate> getTaskTemplate(@PathVariable Long id) {
+        return Result.success(taskTemplateMapper.selectById(id));
+    }
+
+    @PostMapping("/task-templates/create")
+    public Result<Long> createTaskTemplate(@RequestBody TaskTemplate template) {
+        template.setCreatedAt(new Date());
+        template.setUpdatedAt(new Date());
+        if (template.getIsActive() == null) {
+            template.setIsActive(1);
+        }
+        taskTemplateMapper.insert(template);
+        return Result.success(template.getId());
+    }
+
+    @PutMapping("/task-templates/{id}")
+    public Result<Boolean> updateTaskTemplate(@PathVariable Long id, @RequestBody TaskTemplate template) {
+        template.setId(id);
+        template.setUpdatedAt(new Date());
+        taskTemplateMapper.updateById(template);
+        return Result.success(true);
+    }
+
+    @DeleteMapping("/task-templates/{id}")
+    public Result<Boolean> deleteTaskTemplate(@PathVariable Long id) {
+        taskTemplateMapper.deleteById(id);
+        return Result.success(true);
+    }
+
+    // ========== 指导师相关接口 ==========
+
+    /**
+     * 获取指导师关联的家庭列表
+     */
+    @PostMapping("/teacher/families")
+    public Result<Object> getTeacherFamilies(@RequestHeader("X-User-Id") Long userId) {
+        User teacher = userMapper.selectById(userId);
+        if (teacher == null || !"teacher".equals(teacher.getRole())) {
+            return Result.error("非指导师账号");
+        }
+
+        String familyIds = teacher.getTeacherFamilyIds();
+        if (familyIds == null || familyIds.isEmpty()) {
+            return Result.success(new java.util.ArrayList<>());
+        }
+
+        // 解析家庭ID列表
+        java.util.List<Long> ids = new java.util.ArrayList<>();
+        for (String id : familyIds.split(",")) {
+            try {
+                ids.add(Long.parseLong(id.trim()));
+            } catch (Exception e) {
+                // 忽略
+            }
+        }
+
+        if (ids.isEmpty()) {
+            return Result.success(new java.util.ArrayList<>());
+        }
+
+        // 查询家庭信息
+        java.util.List<Family> families = familyMapper.selectBatchIds(ids);
+        return Result.success(families);
+    }
+
+    /**
+     * 获取指导师发布的任务模板
+     */
+    @PostMapping("/teacher/task-templates")
+    public Result<Object> getTeacherTaskTemplates(@RequestHeader("X-User-Id") Long userId) {
+        User teacher = userMapper.selectById(userId);
+        if (teacher == null || !"teacher".equals(teacher.getRole())) {
+            return Result.error("非指导师账号");
+        }
+
+        java.util.List<TaskTemplate> templates = taskTemplateMapper.selectList(
+            new LambdaQueryWrapper<TaskTemplate>()
+                .eq(TaskTemplate::getTeacherId, userId)
+                .orderByDesc(TaskTemplate::getCreatedAt)
+        );
+        return Result.success(templates);
+    }
+
+    /**
+     * 指导师创建任务模板
+     */
+    @PostMapping("/teacher/task-templates/create")
+    public Result<Long> createTeacherTaskTemplate(@RequestHeader("X-User-Id") Long userId,
+                                                   @RequestBody TaskTemplate template) {
+        User teacher = userMapper.selectById(userId);
+        if (teacher == null || !"teacher".equals(teacher.getRole())) {
+            return Result.error("非指导师账号");
+        }
+
+        template.setTeacherId(userId);
+        template.setCreatedAt(new Date());
+        template.setUpdatedAt(new Date());
+        if (template.getIsActive() == null) {
+            template.setIsActive(1);
+        }
+        taskTemplateMapper.insert(template);
+        return Result.success(template.getId());
+    }
+
+    /**
+     * 指导师删除自己的任务模板
+     */
+    @DeleteMapping("/teacher/task-templates/{id}")
+    public Result<Boolean> deleteTeacherTaskTemplate(@RequestHeader("X-User-Id") Long userId,
+                                                     @PathVariable Long id) {
+        User teacher = userMapper.selectById(userId);
+        if (teacher == null || !"teacher".equals(teacher.getRole())) {
+            return Result.error("非指导师账号");
+        }
+
+        TaskTemplate template = taskTemplateMapper.selectById(id);
+        if (template == null || !template.getTeacherId().equals(userId)) {
+            return Result.error("模板不存在或无权限删除");
+        }
+
+        taskTemplateMapper.deleteById(id);
+        return Result.success(true);
+    }
+
+    /**
+     * 获取指定家庭的任务列表
+     */
+    @PostMapping("/teacher/families/{familyId}/tasks")
+    public Result<Object> getTeacherFamilyTasks(@RequestHeader("X-User-Id") Long userId,
+                                                @PathVariable Long familyId,
+                                                @RequestBody Map<String, Object> params) {
+        User teacher = userMapper.selectById(userId);
+        if (teacher == null || !"teacher".equals(teacher.getRole())) {
+            return Result.error("非指导师账号");
+        }
+
+        // 检查家庭是否在指导师关联列表中
+        if (!isTeacherFamily(teacher, familyId)) {
+            return Result.error("无权限查看该家庭的任务");
+        }
+
+        Integer page = params.get("page") != null ? (Integer) params.get("page") : 1;
+        Integer size = params.get("size") != null ? (Integer) params.get("size") : 10;
+        
+        Page<Task> pageParam = new Page<>(page, size);
+        Page<Task> result = taskMapper.selectPage(pageParam, new LambdaQueryWrapper<Task>()
+            .eq(Task::getFamilyId, familyId)
+            .orderByDesc(Task::getCreatedAt));
+        return Result.success(result);
+    }
+
+    /**
+     * 获取指定家庭的奖励列表
+     */
+    @PostMapping("/teacher/families/{familyId}/rewards")
+    public Result<Object> getTeacherFamilyRewards(@RequestHeader("X-User-Id") Long userId,
+                                                  @PathVariable Long familyId,
+                                                  @RequestBody Map<String, Object> params) {
+        User teacher = userMapper.selectById(userId);
+        if (teacher == null || !"teacher".equals(teacher.getRole())) {
+            return Result.error("非指导师账号");
+        }
+
+        // 检查家庭是否在指导师关联列表中
+        if (!isTeacherFamily(teacher, familyId)) {
+            return Result.error("无权限查看该家庭的奖励");
+        }
+
+        Integer page = params.get("page") != null ? (Integer) params.get("page") : 1;
+        Integer size = params.get("size") != null ? (Integer) params.get("size") : 10;
+        
+        Page<Reward> pageParam = new Page<>(page, size);
+        Page<Reward> result = rewardMapper.selectPage(pageParam, new LambdaQueryWrapper<Reward>()
+            .eq(Reward::getFamilyId, familyId)
+            .orderByDesc(Reward::getCreatedAt));
+        return Result.success(result);
+    }
+
+    /**
+     * 检查家庭是否在指导师关联列表中
+     */
+    private boolean isTeacherFamily(User teacher, Long familyId) {
+        String familyIds = teacher.getTeacherFamilyIds();
+        if (familyIds == null || familyIds.isEmpty()) {
+            return false;
+        }
+        for (String id : familyIds.split(",")) {
+            try {
+                if (Long.parseLong(id.trim()) == familyId) {
+                    return true;
+                }
+            } catch (Exception e) {
+                // 忽略
+            }
+        }
+        return false;
+    }
 }

+ 53 - 0
zxyj-backend/src/main/java/com/zxyj/controller/AdminPackageController.java

@@ -0,0 +1,53 @@
+package com.zxyj.controller;
+
+import com.zxyj.common.Result;
+import com.zxyj.entity.TaskTemplatePackage;
+import com.zxyj.service.TaskTemplatePackageService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Tag(name = "管理端-套餐审核", description = "套餐审核管理接口")
+@RestController
+@RequestMapping("/api/admin/packages")
+public class AdminPackageController {
+
+    @Autowired
+    private TaskTemplatePackageService packageService;
+
+    /**
+     * 获取待审核的套餐列表
+     */
+    @Operation(summary = "获取待审核套餐列表")
+    @GetMapping("/pending")
+    public Result<List<TaskTemplatePackage>> getPendingPackages() {
+        List<TaskTemplatePackage> packages = packageService.getPendingPackages();
+        return Result.success(packages);
+    }
+
+    /**
+     * 审核通过
+     */
+    @Operation(summary = "审核通过")
+    @PostMapping("/{id}/approve")
+    public Result<Boolean> approvePackage(@PathVariable Long id,
+                                           @RequestAttribute("userId") Long reviewerId) {
+        boolean success = packageService.approvePackage(id, reviewerId);
+        return Result.success(success);
+    }
+
+    /**
+     * 审核拒绝
+     */
+    @Operation(summary = "审核拒绝")
+    @PostMapping("/{id}/reject")
+    public Result<Boolean> rejectPackage(@PathVariable Long id,
+                                          @RequestParam String reason,
+                                          @RequestAttribute("userId") Long reviewerId) {
+        boolean success = packageService.rejectPackage(id, reviewerId, reason);
+        return Result.success(success);
+    }
+}

+ 60 - 0
zxyj-backend/src/main/java/com/zxyj/controller/BindController.java

@@ -0,0 +1,60 @@
+package com.zxyj.controller;
+
+import com.zxyj.common.Result;
+import com.zxyj.dto.BindGuideDTO;
+import com.zxyj.entity.GuideFamily;
+import com.zxyj.service.GuideFamilyService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Tag(name = "家长绑定", description = "家长绑定指导师接口")
+@RestController
+@RequestMapping("/api/bind")
+public class BindController {
+
+    @Autowired
+    private GuideFamilyService guideFamilyService;
+
+    /**
+     * 家长确认绑定指导师
+     */
+    @Operation(summary = "确认绑定指导师")
+    @PostMapping("/accept")
+    public Result<Boolean> acceptBind(@RequestAttribute("userId") Long userId,
+                                        @RequestAttribute("familyId") Long familyId,
+                                        @RequestBody BindGuideDTO dto) {
+        // 这里应该通过 bindToken 解析出指导师ID
+        // 简化处理: 假设 dto 中包含 guideId
+        boolean success = guideFamilyService.confirmBind(dto.getGuideId(), familyId,
+            dto.getServiceType(), dto.getServicePrice());
+        return Result.success(success);
+    }
+
+    /**
+     * 获取绑定的指导师列表
+     */
+    @Operation(summary = "获取绑定的指导师列表")
+    @GetMapping("/guides")
+    public Result<List<Map<String, Object>>> getBoundGuides(@RequestAttribute("familyId") Long familyId) {
+        List<GuideFamily> bindings = guideFamilyService.getGuidesByFamily(familyId);
+        
+        List<Map<String, Object>> result = bindings.stream().map(binding -> {
+            Map<String, Object> item = new java.util.HashMap<>();
+            item.put("id", binding.getId());
+            item.put("guideId", binding.getGuideId());
+            item.put("serviceType", binding.getServiceType());
+            item.put("servicePrice", binding.getServicePrice());
+            item.put("status", binding.getStatus());
+            item.put("boundAt", binding.getBoundAt());
+            return item;
+        }).collect(Collectors.toList());
+        
+        return Result.success(result);
+    }
+}

+ 39 - 0
zxyj-backend/src/main/java/com/zxyj/controller/BindInviteController.java

@@ -0,0 +1,39 @@
+package com.zxyj.controller;
+
+import com.zxyj.common.Result;
+import com.zxyj.service.BindInviteService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+@Tag(name = "指导师绑定邀请", description = "指导师生成邀请、家长确认绑定等接口")
+@RestController
+@RequestMapping("/api/guide/bind")
+public class BindInviteController {
+
+    @Autowired
+    private BindInviteService bindInviteService;
+
+    /**
+     * 生成绑定邀请
+     */
+    @Operation(summary = "生成绑定邀请")
+    @PostMapping("/invite")
+    public Result<Map<String, Object>> generateInvite(@RequestAttribute("userId") Long userId) {
+        Map<String, Object> result = bindInviteService.generateInvite(userId);
+        return Result.success(result);
+    }
+
+    /**
+     * 校验邀请令牌
+     */
+    @Operation(summary = "校验邀请令牌")
+    @GetMapping("/validate")
+    public Result<Map<String, Object>> validateToken(@RequestParam String token) {
+        Map<String, Object> result = bindInviteService.validateToken(token);
+        return Result.success(result);
+    }
+}

+ 101 - 0
zxyj-backend/src/main/java/com/zxyj/controller/GuideController.java

@@ -0,0 +1,101 @@
+package com.zxyj.controller;
+
+import com.zxyj.common.Result;
+import com.zxyj.dto.BindGuideDTO;
+import com.zxyj.entity.Family;
+import com.zxyj.entity.GuideFamily;
+import com.zxyj.entity.User;
+import com.zxyj.mapper.FamilyMapper;
+import com.zxyj.service.GuideFamilyService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Tag(name = "指导师管理", description = "指导师绑定家庭、管理套餐等接口")
+@RestController
+@RequestMapping("/api/guide")
+public class GuideController {
+
+    @Autowired
+    private GuideFamilyService guideFamilyService;
+
+    @Autowired
+    private FamilyMapper familyMapper;
+
+    /**
+     * 生成绑定邀请
+     */
+    @Operation(summary = "生成绑定邀请")
+    @PostMapping("/bind/invite")
+    public Result<Map<String, String>> generateBindInvite(@RequestAttribute("userId") Long userId) {
+        // 获取当前用户家庭
+        User user = new User();
+        user.setId(userId);
+        
+        // 生成邀请信息
+        Map<String, String> result = new HashMap<>();
+        result.put("bindToken", userId + "_" + System.currentTimeMillis());
+        result.put("inviteUrl", "/pages/bind/accept?token=" + result.get("bindToken"));
+        
+        return Result.success(result);
+    }
+
+    /**
+     * 获取绑定的家庭列表
+     */
+    @Operation(summary = "获取绑定的家庭列表")
+    @GetMapping("/families")
+    public Result<List<Map<String, Object>>> getBoundFamilies(@RequestAttribute("userId") Long userId) {
+        List<GuideFamily> bindings = guideFamilyService.getFamiliesByGuide(userId);
+        
+        List<Map<String, Object>> result = bindings.stream().map(binding -> {
+            Map<String, Object> item = new HashMap<>();
+            item.put("id", binding.getId());
+            item.put("familyId", binding.getFamilyId());
+            item.put("serviceType", binding.getServiceType());
+            item.put("servicePrice", binding.getServicePrice());
+            item.put("status", binding.getStatus());
+            item.put("boundAt", binding.getBoundAt());
+            
+            // 获取家庭信息
+            Family family = familyMapper.selectById(binding.getFamilyId());
+            if (family != null) {
+                item.put("familyName", family.getName());
+            }
+            
+            return item;
+        }).collect(Collectors.toList());
+        
+        return Result.success(result);
+    }
+
+    /**
+     * 确认绑定家庭
+     */
+    @Operation(summary = "确认绑定家庭")
+    @PostMapping("/bind/confirm")
+    public Result<Boolean> confirmBind(@RequestAttribute("userId") Long userId,
+                                        @RequestBody BindGuideDTO dto) {
+        // 这里简化处理,实际应该通过 bindToken 解析
+        // 暂时使用 familyId
+        boolean success = guideFamilyService.confirmBind(userId, dto.getFamilyId(), 
+            dto.getServiceType(), dto.getServicePrice());
+        return Result.success(success);
+    }
+
+    /**
+     * 解除绑定
+     */
+    @Operation(summary = "解除绑定")
+    @DeleteMapping("/bind/{id}")
+    public Result<Boolean> unbind(@PathVariable Long id) {
+        boolean success = guideFamilyService.unbind(id);
+        return Result.success(success);
+    }
+}

+ 263 - 0
zxyj-backend/src/main/java/com/zxyj/controller/GuideFamilyTaskController.java

@@ -0,0 +1,263 @@
+package com.zxyj.controller;
+
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zxyj.common.Result;
+import com.zxyj.dto.TaskReviewDTO;
+import com.zxyj.entity.Child;
+import com.zxyj.entity.Task;
+import com.zxyj.entity.User;
+import com.zxyj.mapper.ChildMapper;
+import com.zxyj.mapper.UserMapper;
+import com.zxyj.service.TaskService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Tag(name = "指导师-家庭任务管理", description = "指导师查看家庭任务、审核任务等接口")
+@RestController
+@RequestMapping("/api/guide/families")
+public class GuideFamilyTaskController {
+
+    @Autowired
+    private TaskService taskService;
+
+    @Autowired
+    private ChildMapper childMapper;
+
+    @Autowired
+    private UserMapper userMapper;
+
+    /**
+     * 获取指导师关联的家庭列表(带简要任务统计)
+     */
+    @Operation(summary = "获取关联家庭列表")
+    @ApiResponses({
+        @ApiResponse(responseCode = "200", description = "成功返回家庭列表"),
+        @ApiResponse(responseCode = "403", description = "Access denied - 非教师"),
+        @ApiResponse(responseCode = "401", description = "未登录")
+    })
+    @GetMapping
+    public Result<List<Map<String, Object>>> getBoundFamilies(@RequestAttribute("userId") Long guideId, @RequestAttribute("role") String role) {
+        if (!"teacher".equals(role)) { return Result.error("Access denied"); }
+// 获取指导师关联的家庭
+        List<User> users = userMapper.selectList(
+            new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<User>()
+                .like("teacher_family_ids", String.valueOf(guideId))
+        );
+
+        List<Map<String, Object>> result = new ArrayList<>();
+        Set<Long> processedFamilyIds = new HashSet<>();
+
+        for (User user : users) {
+            if (user.getFamilyId() == null || processedFamilyIds.contains(user.getFamilyId())) {
+                continue;
+            }
+            processedFamilyIds.add(user.getFamilyId());
+
+            Map<String, Object> item = new HashMap<>();
+            item.put("familyId", user.getFamilyId());
+            item.put("familyName", "家庭" + user.getFamilyId());
+            item.put("parentName", user.getNickname());
+
+            // 获取家庭孩子列表
+            List<Child> children = childMapper.selectList(
+                new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<Child>()
+                    .eq("family_id", user.getFamilyId())
+            );
+            item.put("childrenCount", children.size());
+
+            // 获取待审核任务数量
+            long pendingCount = 0;
+            for (Child child : children) {
+                List<Task> pendingTasks = taskService.getPendingReviewTasksForChild(child.getId());
+                pendingCount += pendingTasks.size();
+            }
+            item.put("pendingReviewCount", pendingCount);
+
+            result.add(item);
+        }
+
+        return Result.success(result);
+    }
+
+    /**
+     * 获取指定家庭的所有孩子任务概览
+     */
+    @Operation(summary = "获取家庭任务概览")
+    @ApiResponses({
+        @ApiResponse(responseCode = "200", description = "成功返回概览"),
+        @ApiResponse(responseCode = "403", description = "Access denied"),
+        @ApiResponse(responseCode = "401", description = "未登录")
+    })
+    @GetMapping("/{familyId}/overview")
+    public Result<Map<String, Object>> getFamilyTaskOverview(
+            @RequestAttribute("userId") Long guideId,
+            @RequestAttribute("role") String role,
+            @PathVariable Long familyId) {
+        if (!"teacher".equals(role)) { return Result.error("Access denied"); }
+
+        List<Child> children = childMapper.selectList(
+            new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<Child>()
+                .eq("family_id", familyId)
+        );
+
+        List<Map<String, Object>> childTaskList = new ArrayList<>();
+        int totalPending = 0;
+        int totalCompleted = 0;
+        int totalOverdue = 0;
+
+        for (Child child : children) {
+            Map<String, Object> childInfo = new HashMap<>();
+            childInfo.put("childId", child.getId());
+            childInfo.put("childName", child.getNickname());
+            childInfo.put("totalPoints", child.getTotalPoints());
+            childInfo.put("streakDays", child.getStreakDays());
+
+            // 获取今日任务
+            List<Task> todayTasks = taskService.getTodayTasks(child.getId());
+            childInfo.put("todayTaskCount", todayTasks.size());
+            childInfo.put("todayCompletedCount", todayTasks.stream()
+                .filter(t -> "completed".equals(t.getStatus()))
+                .count());
+
+            // 获取待审核任务
+            List<Task> pendingTasks = taskService.getPendingReviewTasksForChild(child.getId());
+            childInfo.put("pendingReviewCount", pendingTasks.size());
+            totalPending += pendingTasks.size();
+
+            // 统计已完成和超时
+            List<Task> allTasks = taskService.getAllTasksByChild(child.getId());
+            totalCompleted += allTasks.stream()
+                .filter(t -> "completed".equals(t.getStatus()))
+                .count();
+            totalOverdue += allTasks.stream()
+                .filter(t -> "overdue".equals(t.getStatus()))
+                .count();
+
+            childTaskList.add(childInfo);
+        }
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("familyId", familyId);
+        result.put("children", childTaskList);
+        result.put("totalChildren", children.size());
+        result.put("totalPendingReview", totalPending);
+        result.put("totalCompleted", totalCompleted);
+        result.put("totalOverdue", totalOverdue);
+
+        return Result.success(result);
+    }
+
+    /**
+     * 获取家庭孩子的任务列表
+     */
+    @Operation(summary = "获取孩子任务列表")
+    @ApiResponses({
+        @ApiResponse(responseCode = "200", description = "成功返回分页任务"),
+        @ApiResponse(responseCode = "403", description = "Access denied"),
+        @ApiResponse(responseCode = "401", description = "未登录")
+    })
+    @GetMapping("/{familyId}/children/{childId}/tasks")
+    public Result<Page<Task>> getChildTasks(
+            @PathVariable Long familyId,
+            @PathVariable Long childId,
+            @RequestAttribute("role") String role,
+            @RequestParam(defaultValue = "1") Integer page,
+            @RequestParam(defaultValue = "10") Integer size,
+            @RequestParam(required = false) String status) {
+        if (!"teacher".equals(role)) { return Result.error("Access denied"); }
+
+        Page<Task> tasks = taskService.getTaskHistoryByChild(childId, page, size, status);
+        return Result.success(tasks);
+    }
+
+    /**
+     * 获取家庭孩子的待审核任务
+     */
+    @Operation(summary = "获取待审核任务列表")
+    @ApiResponses({
+        @ApiResponse(responseCode = "200", description = "成功返回待审核任务"),
+        @ApiResponse(responseCode = "403", description = "Access denied"),
+        @ApiResponse(responseCode = "401", description = "未登录")
+    })
+    @GetMapping("/{familyId}/children/{childId}/pending-review")
+    public Result<List<Task>> getPendingReviewTasks(
+            @PathVariable Long familyId,
+            @PathVariable Long childId,
+            @RequestAttribute("role") String role) {
+
+        if (!"teacher".equals(role)) { return Result.error("Access denied"); }
+        List<Task> tasks = taskService.getPendingReviewTasksForChild(childId);
+        return Result.success(tasks);
+    }
+
+    /**
+     * 审核任务(指导师)
+     */
+    @Operation(summary = "审核任务")
+    @ApiResponses({
+        @ApiResponse(responseCode = "200", description = "审核成功返回布尔值"),
+        @ApiResponse(responseCode = "403", description = "Access denied"),
+        @ApiResponse(responseCode = "401", description = "未登录")
+    })
+    @PostMapping("/{familyId}/tasks/{taskId}/review")
+    public Result<Boolean> reviewTask(
+            @PathVariable Long familyId,
+            @PathVariable Long taskId,
+            @RequestAttribute("role") String role,
+            @RequestBody TaskReviewDTO dto) {
+
+        if (!"teacher".equals(role)) { return Result.error("Access denied"); }
+        boolean success = taskService.reviewTask(taskId, dto);
+        return Result.success(success);
+    }
+
+    /**
+     * 批量审核任务
+     */
+    @Operation(summary = "批量审核任务")
+    @ApiResponses({
+        @ApiResponse(responseCode = "200", description = "批量审核成功返回统计"),
+        @ApiResponse(responseCode = "403", description = "Access denied"),
+        @ApiResponse(responseCode = "401", description = "未登录")
+    })
+    @PostMapping("/{familyId}/tasks/batch-review")
+    public Result<Map<String, Object>> batchReviewTasks(
+            @PathVariable Long familyId,
+            @RequestAttribute("role") String role,
+            @RequestBody List<Long> taskIds,
+            @RequestParam Boolean approved) {
+
+
+        if (!"teacher".equals(role)) { return Result.error("Access denied"); }
+        int successCount = 0;
+        int failCount = 0;
+
+        for (Long taskId : taskIds) {
+            try {
+                TaskReviewDTO dto = new TaskReviewDTO();
+                dto.setApproved(approved);
+                dto.setComment(approved ? "指导师审核通过" : "指导师审核拒绝");
+                if (taskService.reviewTask(taskId, dto)) {
+                    successCount++;
+                } else {
+                    failCount++;
+                }
+            } catch (Exception e) {
+                failCount++;
+            }
+        }
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("successCount", successCount);
+        result.put("failCount", failCount);
+        return Result.success(result);
+    }
+}

+ 128 - 0
zxyj-backend/src/main/java/com/zxyj/controller/MarketController.java

@@ -0,0 +1,128 @@
+package com.zxyj.controller;
+
+import com.zxyj.common.Result;
+import com.zxyj.dto.ApplyPackageDTO;
+import com.zxyj.entity.TaskPlanInstance;
+import com.zxyj.entity.TaskPlanItem;
+import com.zxyj.entity.TaskTemplatePackage;
+import com.zxyj.entity.TaskTemplateItem;
+import com.zxyj.service.TaskPlanService;
+import com.zxyj.service.TaskTemplatePackageService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Tag(name = "套餐市场", description = "家长浏览、购买、应用套餐等接口")
+@RestController
+@RequestMapping("/api/market")
+public class MarketController {
+
+    @Autowired
+    private TaskTemplatePackageService packageService;
+
+    @Autowired
+    private TaskPlanService planService;
+
+    /**
+     * 获取公开套餐列表 (市场)
+     */
+    @Operation(summary = "获取公开套餐列表")
+    @GetMapping("/packages")
+    public Result<List<TaskTemplatePackage>> getPublicPackages(
+            @RequestParam(required = false) String category) {
+        List<TaskTemplatePackage> packages = packageService.getPublicPackages(category);
+        return Result.success(packages);
+    }
+
+    /**
+     * 获取套餐详情
+     */
+    @Operation(summary = "获取套餐详情")
+    @GetMapping("/packages/{id}")
+    public Result<TaskTemplatePackage> getPackageDetail(@PathVariable Long id) {
+        TaskTemplatePackage pkg = packageService.getPackageById(id);
+        return Result.success(pkg);
+    }
+
+    /**
+     * 获取套餐的任务项列表
+     */
+    @Operation(summary = "获取套餐任务项列表")
+    @GetMapping("/packages/{id}/items")
+    public Result<List<TaskTemplateItem>> getPackageItems(@PathVariable Long id) {
+        List<TaskTemplateItem> items = packageService.getPackageItems(id);
+        return Result.success(items);
+    }
+
+    /**
+     * 通过分享码获取套餐
+     */
+    @Operation(summary = "通过分享码获取套餐")
+    @GetMapping("/share/{code}")
+    public Result<TaskTemplatePackage> getPackageByShareCode(@PathVariable String code) {
+        TaskTemplatePackage pkg = packageService.getPackageByShareCode(code);
+        if (pkg == null) {
+            return Result.error("分享码无效");
+        }
+        return Result.success(pkg);
+    }
+
+    /**
+     * 购买并应用套餐
+     */
+    @Operation(summary = "购买并应用套餐")
+    @PostMapping("/packages/{id}/purchase")
+    public Result<TaskPlanInstance> purchasePackage(
+            @PathVariable Long id,
+            @RequestBody ApplyPackageDTO dto,
+            @RequestAttribute("userId") Long userId,
+            @RequestAttribute("familyId") Long familyId) {
+        
+        dto.setPackageId(id);
+        TaskPlanInstance instance = planService.applyPackage(dto, familyId);
+        return Result.success(instance);
+    }
+
+    /**
+     * 获取我的任务计划列表
+     */
+    @Operation(summary = "获取我的任务计划")
+    @GetMapping("/plans")
+    public Result<List<TaskPlanInstance>> getMyPlans(@RequestAttribute("familyId") Long familyId) {
+        List<TaskPlanInstance> plans = planService.getFamilyPlans(familyId);
+        return Result.success(plans);
+    }
+
+    /**
+     * 获取计划详情
+     */
+    @Operation(summary = "获取计划详情")
+    @GetMapping("/plans/{id}")
+    public Result<TaskPlanInstance> getPlanDetail(@PathVariable Long id) {
+        TaskPlanInstance plan = planService.getPlanById(id);
+        return Result.success(plan);
+    }
+
+    /**
+     * 获取计划的任务项
+     */
+    @Operation(summary = "获取计划任务项")
+    @GetMapping("/plans/{id}/items")
+    public Result<List<TaskPlanItem>> getPlanItems(@PathVariable Long id) {
+        List<TaskPlanItem> items = planService.getPlanItems(id);
+        return Result.success(items);
+    }
+
+    /**
+     * 取消计划
+     */
+    @Operation(summary = "取消计划")
+    @PostMapping("/plans/{id}/cancel")
+    public Result<Boolean> cancelPlan(@PathVariable Long id) {
+        boolean success = planService.cancelPlan(id);
+        return Result.success(success);
+    }
+}

+ 113 - 0
zxyj-backend/src/main/java/com/zxyj/controller/PackagePaymentController.java

@@ -0,0 +1,113 @@
+package com.zxyj.controller;
+
+import com.zxyj.common.Result;
+import com.zxyj.entity.PackageOrder;
+import com.zxyj.entity.TaskPlanInstance;
+import com.zxyj.service.PackagePaymentService;
+import com.zxyj.service.TaskPlanService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+@Tag(name = "套餐支付", description = "套餐购买支付接口")
+@RestController
+@RequestMapping("/api/payment")
+public class PackagePaymentController {
+
+    @Autowired
+    private PackagePaymentService paymentService;
+
+    @Autowired
+    private TaskPlanService planService;
+
+    /**
+     * 创建支付订单
+     */
+    @Operation(summary = "创建支付订单")
+    @PostMapping("/create")
+    public Result<PackageOrder> createOrder(
+            @RequestAttribute("userId") Long userId,
+            @RequestAttribute("familyId") Long familyId,
+            @RequestParam Long packageId,
+            @RequestParam(defaultValue = "wechat") String payMethod) {
+        
+        PackageOrder order = paymentService.createOrder(userId, familyId, packageId, payMethod);
+        return Result.success(order);
+    }
+
+    /**
+     * 获取支付参数 (微信JSAPI)
+     */
+    @Operation(summary = "获取微信支付参数")
+    @PostMapping("/wechat params")
+    public Result<Map<String, Object>> getWechatPayParams(
+            @RequestParam Long orderId,
+            @RequestParam String openid) {
+        
+        Map<String, Object> params = paymentService.createWechatPayOrder(orderId, openid);
+        return Result.success(params);
+    }
+
+    /**
+     * 微信支付回调
+     */
+    @Operation(summary = "微信支付回调")
+    @PostMapping("/notify")
+    public String handlePayNotify(@RequestBody String xmlData) {
+        boolean success = paymentService.handlePayNotify(xmlData);
+        
+        if (success) {
+            return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
+        } else {
+            return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[ERROR]]></return_msg></xml>";
+        }
+    }
+
+    /**
+     * 查询订单状态
+     */
+    @Operation(summary = "查询订单状态")
+    @GetMapping("/orders/{orderId}")
+    public Result<PackageOrder> getOrderStatus(@PathVariable Long orderId) {
+        PackageOrder order = paymentService.getOrderById(orderId);
+        return Result.success(order);
+    }
+
+    /**
+     * 支付成功后应用套餐
+     */
+    @Operation(summary = "支付成功后应用套餐")
+    @PostMapping("/apply")
+    public Result<TaskPlanInstance> applyAfterPayment(
+            @RequestParam Long orderId,
+            @RequestParam Long childId,
+            @RequestParam(required = false) java.util.List<Long> selectedItemIds) {
+        
+        PackageOrder order = paymentService.getOrderById(orderId);
+        if (order == null || !"paid".equals(order.getStatus())) {
+            return Result.error("订单未支付");
+        }
+
+        // 应用套餐
+        com.zxyj.dto.ApplyPackageDTO dto = new com.zxyj.dto.ApplyPackageDTO();
+        dto.setPackageId(order.getPackageId());
+        dto.setChildId(childId);
+        dto.setSelectedItemIds(selectedItemIds);
+        
+        TaskPlanInstance instance = planService.applyPackage(dto, order.getFamilyId());
+        return Result.success(instance);
+    }
+
+    /**
+     * 取消订单
+     */
+    @Operation(summary = "取消订单")
+    @PostMapping("/orders/{orderId}/cancel")
+    public Result<Boolean> cancelOrder(@PathVariable Long orderId) {
+        boolean success = paymentService.cancelOrder(orderId);
+        return Result.success(success);
+    }
+}

+ 130 - 0
zxyj-backend/src/main/java/com/zxyj/controller/TaskTemplatePackageController.java

@@ -0,0 +1,130 @@
+package com.zxyj.controller;
+
+import com.zxyj.common.Result;
+import com.zxyj.dto.CreatePackageDTO;
+import com.zxyj.dto.CreatePackageItemDTO;
+import com.zxyj.entity.TaskTemplatePackage;
+import com.zxyj.entity.TaskTemplateItem;
+import com.zxyj.service.TaskTemplatePackageService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+@Tag(name = "任务模板套餐", description = "套餐创建、审核、发布等接口")
+@RestController
+@RequestMapping("/api/packages")
+public class TaskTemplatePackageController {
+
+    @Autowired
+    private TaskTemplatePackageService packageService;
+
+    /**
+     * 创建套餐
+     */
+    @Operation(summary = "创建套餐")
+    @PostMapping
+    public Result<Long> createPackage(@RequestAttribute("userId") Long userId,
+                                       @RequestBody CreatePackageDTO dto) {
+        TaskTemplatePackage pkg = packageService.createPackage(dto, userId);
+        return Result.success(pkg.getId());
+    }
+
+    /**
+     * 更新套餐
+     */
+    @Operation(summary = "更新套餐")
+    @PutMapping("/{id}")
+    public Result<Boolean> updatePackage(@PathVariable Long id,
+                                          @RequestBody CreatePackageDTO dto) {
+        boolean success = packageService.updatePackage(id, dto);
+        return Result.success(success);
+    }
+
+    /**
+     * 获取套餐详情
+     */
+    @Operation(summary = "获取套餐详情")
+    @GetMapping("/{id}")
+    public Result<TaskTemplatePackage> getPackage(@PathVariable Long id) {
+        TaskTemplatePackage pkg = packageService.getPackageById(id);
+        return Result.success(pkg);
+    }
+
+    /**
+     * 获取我的套餐列表
+     */
+    @Operation(summary = "获取我的套餐列表")
+    @GetMapping
+    public Result<List<TaskTemplatePackage>> getMyPackages(@RequestAttribute("userId") Long userId) {
+        List<TaskTemplatePackage> packages = packageService.getMyPackages(userId);
+        return Result.success(packages);
+    }
+
+    /**
+     * 提交审核
+     */
+    @Operation(summary = "提交审核")
+    @PostMapping("/{id}/submit")
+    public Result<Boolean> submitForReview(@PathVariable Long id) {
+        boolean success = packageService.submitForReview(id);
+        return Result.success(success);
+    }
+
+    /**
+     * 发布/下架
+     */
+    @Operation(summary = "发布/下架")
+    @PostMapping("/{id}/publish")
+    public Result<Boolean> publishPackage(@PathVariable Long id,
+                                           @RequestParam boolean publish) {
+        boolean success = packageService.publishPackage(id, publish);
+        return Result.success(success);
+    }
+
+    /**
+     * 添加任务项
+     */
+    @Operation(summary = "添加任务项")
+    @PostMapping("/{id}/items")
+    public Result<Long> addItem(@PathVariable Long id,
+                                  @RequestBody CreatePackageItemDTO dto) {
+        dto.setPackageId(id);
+        TaskTemplateItem item = packageService.addItem(dto);
+        return Result.success(item.getId());
+    }
+
+    /**
+     * 更新任务项
+     */
+    @Operation(summary = "更新任务项")
+    @PutMapping("/items/{itemId}")
+    public Result<Boolean> updateItem(@PathVariable Long itemId,
+                                        @RequestBody CreatePackageItemDTO dto) {
+        boolean success = packageService.updateItem(itemId, dto);
+        return Result.success(success);
+    }
+
+    /**
+     * 删除任务项
+     */
+    @Operation(summary = "删除任务项")
+    @DeleteMapping("/items/{itemId}")
+    public Result<Boolean> deleteItem(@PathVariable Long itemId) {
+        boolean success = packageService.deleteItem(itemId);
+        return Result.success(success);
+    }
+
+    /**
+     * 获取套餐的任务项列表
+     */
+    @Operation(summary = "获取套餐的任务项列表")
+    @GetMapping("/{id}/items")
+    public Result<List<TaskTemplateItem>> getPackageItems(@PathVariable Long id) {
+        List<TaskTemplateItem> items = packageService.getPackageItems(id);
+        return Result.success(items);
+    }
+}

+ 1 - 0
zxyj-backend/src/main/java/com/zxyj/dto/LoginResultDTO.java

@@ -7,6 +7,7 @@ public class LoginResultDTO {
     private String token;
     private Long userId;
     private String role;
+    private String roles; // 用户拥有的所有角色列表
     private Long familyId;
     private String nickname;
     private Long currentChildId; // 当前切换到的孩子ID(如果切换了)

+ 6 - 0
zxyj-backend/src/main/java/com/zxyj/dto/UpdateUserDTO.java

@@ -8,7 +8,13 @@ public class UpdateUserDTO {
     private String avatar;
     private String realName;
     private String role;
+    private String roles;
     private String gender;
     private String birthday;
     private String idCard;
+    
+    // 指导师相关字段
+    private String teacherNo;
+    private String teacherPhoto;
+    private String certificateImage;
 }

+ 48 - 0
zxyj-backend/src/main/java/com/zxyj/entity/PackageOrder.java

@@ -0,0 +1,48 @@
+package com.zxyj.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@TableName("package_orders")
+public class PackageOrder implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String orderNo;
+
+    private Long userId;          // 购买者用户ID
+
+    private Long familyId;        // 家庭ID
+
+    private Long packageId;       // 套餐ID
+
+    private String packageName;   // 套餐名称
+
+    private BigDecimal price;      // 支付金额
+
+    private BigDecimal platformFee; // 平台服务费
+
+    private Long guideId;         // 指导师ID
+
+    private String status;         // pending/paid/cancelled/refunded
+
+    private String payMethod;      // wechat/alipay
+
+    private String transactionId;  // 微信支付订单号
+
+    private Date paidAt;           // 支付时间
+
+    private String notifyData;     // 支付回调数据
+
+    private Date createdAt;
+
+    private Date updatedAt;
+}

+ 12 - 0
zxyj-backend/src/main/java/com/zxyj/entity/User.java

@@ -49,6 +49,18 @@ public class User implements Serializable {
     // 指导师关联的家庭ID列表 (逗号分隔)
     private String teacherFamilyIds;
 
+    // 指导师证书号
+    private String teacherNo;
+
+    // 指导师照片
+    private String teacherPhoto;
+
+    // 指导师证书照片
+    private String certificateImage;
+
+    // 指导师审核状态: pending/approved/rejected
+    private String teacherStatus;
+
     private Date createdAt;
 
     private Date updatedAt;

+ 9 - 0
zxyj-backend/src/main/java/com/zxyj/mapper/PackageOrderMapper.java

@@ -0,0 +1,9 @@
+package com.zxyj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zxyj.entity.PackageOrder;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PackageOrderMapper extends BaseMapper<PackageOrder> {
+}

+ 96 - 0
zxyj-backend/src/main/java/com/zxyj/service/BindInviteService.java

@@ -0,0 +1,96 @@
+package com.zxyj.service;
+
+import com.zxyj.entity.GuideFamily;
+import com.zxyj.entity.User;
+import com.zxyj.mapper.UserMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+@Service
+public class BindInviteService {
+
+    @Autowired
+    private GuideFamilyService guideFamilyService;
+
+    @Autowired
+    private UserMapper userMapper;
+
+    /**
+     * 生成绑定邀请
+     * @param guideId 指导师用户ID
+     * @return 邀请信息
+     */
+    public Map<String, Object> generateInvite(Long guideId) {
+        // 生成唯一邀请令牌
+        String inviteToken = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
+        
+        // 获取指导师信息
+        User guide = userMapper.selectById(guideId);
+        
+        // 创建待绑定关系
+        GuideFamily gf = new GuideFamily();
+        gf.setGuideId(guideId);
+        gf.setFamilyId(0L); // 暂时为0,家长确认时再设置
+        gf.setServiceType("consulting");
+        gf.setServicePrice(BigDecimal.ZERO);
+        gf.setStatus("pending"); // 待确认状态
+        gf.setBoundAt(new Date());
+        gf.setCreatedAt(new Date());
+        gf.setUpdatedAt(new Date());
+        
+        // 保存到数据库
+        guideFamilyService.getClass(); // 简单触发
+        // 这里简化处理,实际应该用 Mapper 保存
+        
+        // 返回邀请信息
+        Map<String, Object> result = new HashMap<>();
+        result.put("inviteToken", inviteToken);
+        result.put("guideId", guideId);
+        result.put("guideName", guide != null ? guide.getNickname() : "指导师");
+        result.put("expireTime", new Date(System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000)); // 7天有效
+        
+        // 生成邀请链接和小程序码
+        result.put("inviteUrl", "pages/bind/accept?token=" + inviteToken + "&guideId=" + guideId);
+        
+        return result;
+    }
+
+    /**
+     * 通过邀请码确认绑定
+     * @param inviteToken 邀请令牌
+     * @param guideId 指导师ID
+     * @param familyId 家庭ID
+     * @param serviceType 服务类型
+     * @param servicePrice 服务价格
+     * @return 绑定结果
+     */
+    public boolean confirmBind(String inviteToken, Long guideId, Long familyId, String serviceType, BigDecimal servicePrice) {
+        // 确认绑定
+        return guideFamilyService.confirmBind(guideId, familyId, serviceType, servicePrice);
+    }
+
+    /**
+     * 校验邀请令牌
+     * @param inviteToken 邀请令牌
+     * @return 令牌信息
+     */
+    public Map<String, Object> validateToken(String inviteToken) {
+        // 简化处理:检查令牌格式
+        Map<String, Object> result = new HashMap<>();
+        if (inviteToken == null || inviteToken.length() < 16) {
+            result.put("valid", false);
+            result.put("message", "邀请令牌无效");
+            return result;
+        }
+        
+        result.put("valid", true);
+        result.put("message", "令牌有效");
+        return result;
+    }
+}

+ 285 - 0
zxyj-backend/src/main/java/com/zxyj/service/PackagePaymentService.java

@@ -0,0 +1,285 @@
+package com.zxyj.service;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.zxyj.entity.PackageOrder;
+import com.zxyj.entity.TaskTemplatePackage;
+import com.zxyj.mapper.PackageOrderMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.Map;
+import java.util.UUID;
+
+@Service
+public class PackagePaymentService {
+
+    private static final Logger log = LoggerFactory.getLogger(PackagePaymentService.class);
+
+    @Autowired
+    private PackageOrderMapper orderMapper;
+
+    @Autowired
+    private TaskTemplatePackageService packageService;
+
+    @Value("${wechat.appid}")
+    private String appid;
+
+    @Value("${wechat.mch-id}")
+    private String mchId;
+
+    @Value("${wechat.mch-key}")
+    private String mchKey;
+
+    @Value("${wechat.notify-url}")
+    private String notifyUrl;
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    /**
+     * 创建支付订单
+     */
+    public PackageOrder createOrder(Long userId, Long familyId, Long packageId, String payMethod) {
+        // 获取套餐信息
+        TaskTemplatePackage pkg = packageService.getPackageById(packageId);
+        if (pkg == null) {
+            throw new RuntimeException("套餐不存在");
+        }
+
+        // 如果套餐免费,直接创建并返回
+        if (pkg.getPrice() == null || pkg.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
+            PackageOrder order = new PackageOrder();
+            order.setOrderNo(generateOrderNo());
+            order.setUserId(userId);
+            order.setFamilyId(familyId);
+            order.setPackageId(packageId);
+            order.setPackageName(pkg.getName());
+            order.setPrice(BigDecimal.ZERO);
+            order.setPlatformFee(BigDecimal.ZERO);
+            order.setGuideId(pkg.getCreatorId());
+            order.setStatus("paid");
+            order.setPayMethod("free");
+            order.setPaidAt(new Date());
+            order.setCreatedAt(new Date());
+            order.setUpdatedAt(new Date());
+            orderMapper.insert(order);
+            return order;
+        }
+
+        // 创建付费订单
+        PackageOrder order = new PackageOrder();
+        order.setOrderNo(generateOrderNo());
+        order.setUserId(userId);
+        order.setFamilyId(familyId);
+        order.setPackageId(packageId);
+        order.setPackageName(pkg.getName());
+        order.setPrice(pkg.getPrice());
+        order.setPlatformFee(pkg.getPlatformFee());
+        order.setGuideId(pkg.getCreatorId());
+        order.setStatus("pending");
+        order.setPayMethod(payMethod);
+        order.setCreatedAt(new Date());
+        order.setUpdatedAt(new Date());
+        
+        orderMapper.insert(order);
+        
+        return order;
+    }
+
+    /**
+     * 发起微信支付 (JSAPI)
+     */
+    public Map<String, Object> createWechatPayOrder(Long orderId, String openid) {
+        PackageOrder order = orderMapper.selectById(orderId);
+        if (order == null) {
+            throw new RuntimeException("订单不存在");
+        }
+
+        // 调用微信统一下单接口
+        String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
+        
+        JSONObject params = new JSONObject();
+        params.put("appid", appid);
+        params.put("mch_id", mchId);
+        params.put("nonce_str", UUID.randomUUID().toString().replace("-", "").substring(0, 16));
+        params.put("body", "知行益家-" + order.getPackageName());
+        params.put("out_trade_no", order.getOrderNo());
+        params.put("total_fee", order.getPrice().multiply(new BigDecimal("100")).intValue());
+        params.put("spbill_create_ip", "127.0.0.1");
+        params.put("notify_url", notifyUrl);
+        params.put("trade_type", "JSAPI");
+        params.put("openid", openid);
+
+        // 生成签名
+        String sign = generateSign(params, mchKey);
+        params.put("sign", sign);
+
+        try {
+            ResponseEntity<String> response = restTemplate.postForEntity(url, params.toJSONString(), String.class);
+            JSONObject result = JSON.parseObject(response.getBody());
+
+            if ("SUCCESS".equals(result.getString("return_code"))) {
+                // 返回预支付订单信息给前端
+                JSONObject payParams = new JSONObject();
+                payParams.put("orderId", orderId);
+                payParams.put("prepay_id", result.getString("prepay_id"));
+                payParams.put("nonce_str", params.getString("nonce_str"));
+                
+                // 生成前端签名
+                String paySign = generatePaySign(payParams, mchKey);
+                payParams.put("sign", paySign);
+
+                return payParams;
+            } else {
+                log.error("微信下单失败: {}", result.toJSONString());
+                throw new RuntimeException("微信下单失败");
+            }
+        } catch (Exception e) {
+            log.error("微信支付异常", e);
+            throw new RuntimeException("支付异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 处理微信支付回调
+     */
+    public boolean handlePayNotify(String xmlData) {
+        try {
+            // 解析XML
+            JSONObject params = parseXmlToJson(xmlData);
+            
+            if (!"SUCCESS".equals(params.getString("return_code"))) {
+                log.error("支付回调失败: {}", params.getString("return_msg"));
+                return false;
+            }
+
+            // 验签
+            String sign = params.getString("sign");
+            params.remove("sign");
+            String calculatedSign = generateSign(params, mchKey);
+            
+            if (!sign.equals(calculatedSign)) {
+                log.error("签名验证失败");
+                return false;
+            }
+
+            // 更新订单状态
+            String orderNo = params.getString("out_trade_no");
+            PackageOrder order = orderMapper.selectOne(
+                new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<PackageOrder>()
+                    .eq("order_no", orderNo)
+            );
+            
+            if (order != null) {
+                order.setStatus("paid");
+                order.setTransactionId(params.getString("transaction_id"));
+                order.setPaidAt(new Date());
+                order.setNotifyData(xmlData);
+                order.setUpdatedAt(new Date());
+                orderMapper.updateById(order);
+                
+                log.info("订单支付成功: {}", orderNo);
+                return true;
+            }
+            
+            return false;
+        } catch (Exception e) {
+            log.error("处理支付回调异常", e);
+            return false;
+        }
+    }
+
+    /**
+     * 查询订单状态
+     */
+    public PackageOrder getOrderById(Long orderId) {
+        return orderMapper.selectById(orderId);
+    }
+
+    /**
+     * 取消订单
+     */
+    public boolean cancelOrder(Long orderId) {
+        PackageOrder order = new PackageOrder();
+        order.setId(orderId);
+        order.setStatus("cancelled");
+        order.setUpdatedAt(new Date());
+        return orderMapper.updateById(order) > 0;
+    }
+
+    /**
+     * 生成订单号
+     */
+    private String generateOrderNo() {
+        return "PKG" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 4).toUpperCase();
+    }
+
+    /**
+     * 生成签名
+     */
+    private String generateSign(JSONObject params, String mchKey) {
+        StringBuilder sb = new StringBuilder();
+        params.keySet().stream().sorted().forEach(key -> {
+            if (params.getString(key) != null && !params.getString(key).isEmpty()) {
+                sb.append(key).append("=").append(params.getString(key)).append("&");
+            }
+        });
+        sb.append("key=").append(mchKey);
+        
+        // MD5加密并转大写
+        try {
+            java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
+            byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
+            StringBuilder hex = new StringBuilder();
+            for (byte b : digest) {
+                hex.append(String.format("%02x", b));
+            }
+            return hex.toString().toUpperCase();
+        } catch (Exception e) {
+            throw new RuntimeException("签名失败", e);
+        }
+    }
+
+    /**
+     * 生成前端支付签名
+     */
+    private String generatePaySign(JSONObject params, String mchKey) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("appId=").append(appid).append("&");
+        sb.append("nonceStr=").append(params.getString("nonce_str")).append("&");
+        sb.append("package=prepay_id=").append(params.getString("prepay_id")).append("&");
+        sb.append("signType=MD5&");
+        sb.append("timeStamp=").append(System.currentTimeMillis() / 1000).append("&");
+        sb.append("key=").append(mchKey);
+        
+        try {
+            java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
+            byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
+            StringBuilder hex = new StringBuilder();
+            for (byte b : digest) {
+                hex.append(String.format("%02x", b));
+            }
+            return hex.toString().toUpperCase();
+        } catch (Exception e) {
+            throw new RuntimeException("签名失败", e);
+        }
+    }
+
+    /**
+     * 解析XML为JSON
+     */
+    private JSONObject parseXmlToJson(String xml) {
+        JSONObject json = new JSONObject();
+        // 简单XML解析
+        xml = xml.replace("<", "{").replace(">", ":").replace("</", "}").replace("><", ",");
+        // 这里简化处理,实际应该使用XML解析器
+        return json;
+    }
+}

+ 36 - 0
zxyj-backend/src/main/java/com/zxyj/service/TaskService.java

@@ -360,4 +360,40 @@ public class TaskService {
     return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
         && cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
   }
+
+  /**
+   * 获取孩子的待审核任务
+   */
+  public List<Task> getPendingReviewTasksForChild(Long childId) {
+    return taskMapper.selectList(new LambdaQueryWrapper<Task>()
+        .eq(Task::getChildId, childId)
+        .eq(Task::getNeedReview, 1)
+        .eq(Task::getStatus, "completed")
+        .orderByDesc(Task::getCompletedAt));
+  }
+
+  /**
+   * 获取孩子的所有任务
+   */
+  public List<Task> getAllTasksByChild(Long childId) {
+    return taskMapper.selectList(new LambdaQueryWrapper<Task>()
+        .eq(Task::getChildId, childId)
+        .orderByDesc(Task::getCreatedAt));
+  }
+
+  /**
+   * 获取孩子的任务历史(分页)
+   */
+  public Page<Task> getTaskHistoryByChild(Long childId, Integer page, Integer size, String status) {
+    Page<Task> pageParam = new Page<>(page, size);
+    LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<Task>()
+        .eq(Task::getChildId, childId)
+        .orderByDesc(Task::getCreatedAt);
+    
+    if (status != null && !status.isEmpty()) {
+      wrapper.eq(Task::getStatus, status);
+    }
+    
+    return taskMapper.selectPage(pageParam, wrapper);
+  }
 }

+ 8 - 4
zxyj-backend/src/main/java/com/zxyj/service/UserService.java

@@ -78,16 +78,17 @@ private WechatService wechatService;
         result.setToken(token);
         result.setUserId(user.getId());
         result.setRole(user.getRole());
+        result.setRoles(user.getRoles());
         result.setFamilyId(user.getFamilyId());
         result.setNickname(user.getNickname());
 
         return result;
     }
 
-/**
- * 手机号验证码登录
- */
-public LoginResultDTO phoneLogin(PhoneLoginDTO dto) {
+    /**
+     * 手机号验证码登录
+     */
+    public LoginResultDTO phoneLogin(PhoneLoginDTO dto) {
   // 使用手机号作为唯一标识
   String phone = dto.getPhone();
 
@@ -142,6 +143,7 @@ public LoginResultDTO phoneLogin(PhoneLoginDTO dto) {
   result.setToken(token);
   result.setUserId(user.getId());
   result.setRole(user.getRole());
+  result.setRoles(user.getRoles());
   result.setFamilyId(user.getFamilyId());
   result.setNickname(user.getNickname());
   result.setIsNewUser(isNewUser);
@@ -218,6 +220,7 @@ public LoginResultDTO phoneLogin(PhoneLoginDTO dto) {
         result.setToken(token);
         result.setUserId(user.getId());
         result.setRole(user.getRole());
+        result.setRoles(user.getRoles());
         result.setFamilyId(user.getFamilyId());
         result.setNickname(user.getNickname());
         result.setIsNewUser(isNewUser);
@@ -250,6 +253,7 @@ public LoginResultDTO phoneLogin(PhoneLoginDTO dto) {
         result.setToken(token);
         result.setUserId(user.getId());
         result.setRole(user.getRole());
+        result.setRoles(user.getRoles());
         result.setFamilyId(user.getFamilyId());
         result.setNickname(user.getNickname());
         result.setIsNewUser(false);

+ 10 - 0
zxyj-backend/src/main/resources/application.yml

@@ -31,6 +31,16 @@ jwt:
   secret: zxyj-secret-key-2026-spring-boot-jwt-token
   expiration: 86400000
 
+wechat:
+  appid: wx5ba8038ef16fb245
+  secret: YOUR_APP_SECRET_HERE
+  mch-id: YOUR_MCH_ID
+  mch-key: YOUR_MCH_KEY
+  notify-url: http://your-domain.com/api/payment/notify
+  login-url: https://api.weixin.qq.com/sns/jscode2session
+  phone-url: https://api.weixin.qq.com/wxa/business/getuserphonenumber
+  test-mode: true  # 测试模式:跳过微信API调用,使用模拟数据
+
 logging:
   level:
     com.zxyj: debug

+ 31 - 0
zxyj-backend/src/main/resources/migration_add_admin_task_templates.sql

@@ -0,0 +1,31 @@
+-- 管理后台任务模板表(管理员创建的通用任务模板)
+-- 注意:这与管理员功能相关,与DAN测评任务模板(task_templates)不同
+
+CREATE TABLE IF NOT EXISTS admin_task_templates (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  title VARCHAR(200) NOT NULL COMMENT '模板名称',
+  description TEXT COMMENT '任务描述',
+  points INT DEFAULT 10 COMMENT '完成奖励积分',
+  category VARCHAR(32) COMMENT '任务分类:学习、家务、运动、阅读、生活习惯、其他',
+  need_review TINYINT DEFAULT 0 COMMENT '是否需要家长审核',
+  review_by_category TINYINT DEFAULT 0 COMMENT '是否按分类审核',
+  recommended_age INT COMMENT '推荐年龄',
+  difficulty ENUM('easy', 'medium', 'hard') DEFAULT 'medium' COMMENT '难度等级',
+  is_active TINYINT DEFAULT 1 COMMENT '是否启用:0禁用,1启用',
+  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  INDEX idx_category (category),
+  INDEX idx_difficulty (difficulty),
+  INDEX idx_is_active (is_active)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理后台任务模板表';
+
+-- 插入一些示例模板数据
+INSERT INTO admin_task_templates (title, description, points, category, need_review, recommended_age, difficulty, is_active) VALUES
+('完成作业', '按时完成学校布置的家庭作业', 10, '学习', 1, 6, 'medium', 1),
+('阅读30分钟', '自主阅读课外书籍30分钟', 8, '阅读', 0, 5, 'easy', 1),
+('整理房间', '收拾整理自己的房间和书桌', 6, '家务', 0, 6, 'easy', 1),
+('洗碗', '饭后帮忙清洗餐具', 5, '家务', 1, 8, 'medium', 1),
+('户外运动1小时', '进行跑步、球类等户外运动', 12, '运动', 0, 7, 'hard', 1),
+('练习书法', '练习毛笔或硬笔书法20分钟', 7, '学习', 0, 7, 'medium', 1),
+('早睡早起', '晚上9点前睡觉,早上7点前起床', 5, '生活习惯', 0, 5, 'easy', 1),
+('背诵古诗', '背诵一首古诗词', 8, '学习', 1, 6, 'medium', 1);

+ 7 - 0
zxyj-backend/src/main/resources/schema.sql

@@ -639,3 +639,10 @@ CREATE TABLE IF NOT EXISTS package_orders (
     INDEX idx_package_id (package_id),
     INDEX idx_status (status)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 用户表增加指导师相关字段
+ALTER TABLE users ADD COLUMN teacher_no VARCHAR(50) COMMENT '指导师证书号';
+ALTER TABLE users ADD COLUMN teacher_photo VARCHAR(500) COMMENT '指导师照片';
+ALTER TABLE users ADD COLUMN certificate_image VARCHAR(500) COMMENT '指导师证书照片';
+ALTER TABLE users ADD COLUMN teacher_status VARCHAR(20) DEFAULT 'pending' COMMENT '指导师审核状态: pending/approved/rejected';
+ALTER TABLE users ADD INDEX idx_teacher_status (teacher_status);