Explorar el Código

feat: 实现指导师功能完整迁移

- 添加指导师层级关系管理(GuideHierarchy)
- 实现指导师邀请码系统(GuideInviteCode)
- 支持指导师套餐定价(GuidePackage)
- 实现测评报告权限控制(上级可查看下级报告)
- 添加套餐购买和支付功能
- 创建指导师注册、邀请码分享、测评申请等前端页面
- 添加数据库表结构(schema_guide.sql)
- 修复 Java 8 兼容性问题(toList、List.of)
- 编译验证通过
User hace 6 meses
padre
commit
6eab717a1b
Se han modificado 27 ficheros con 4598 adiciones y 7 borrados
  1. 77 0
      zxyj-backend/src/main/java/com/zxyj/controller/guide/AssessmentController.java
  2. 226 0
      zxyj-backend/src/main/java/com/zxyj/controller/guide/GuideManagementController.java
  3. 82 0
      zxyj-backend/src/main/java/com/zxyj/controller/guide/PackagePurchaseController.java
  4. 92 0
      zxyj-backend/src/main/java/com/zxyj/controller/guide/PaymentController.java
  5. 28 0
      zxyj-backend/src/main/java/com/zxyj/dto/GuideRegisterDTO.java
  6. 62 0
      zxyj-backend/src/main/java/com/zxyj/dto/PackagePurchaseDTO.java
  7. 32 0
      zxyj-backend/src/main/java/com/zxyj/entity/GuideHierarchy.java
  8. 40 0
      zxyj-backend/src/main/java/com/zxyj/entity/GuideInviteCode.java
  9. 37 0
      zxyj-backend/src/main/java/com/zxyj/entity/GuidePackage.java
  10. 12 0
      zxyj-backend/src/main/java/com/zxyj/mapper/GuideHierarchyMapper.java
  11. 12 0
      zxyj-backend/src/main/java/com/zxyj/mapper/GuideInviteCodeMapper.java
  12. 12 0
      zxyj-backend/src/main/java/com/zxyj/mapper/GuidePackageMapper.java
  13. 90 0
      zxyj-backend/src/main/java/com/zxyj/service/AssessmentPermissionService.java
  14. 151 0
      zxyj-backend/src/main/java/com/zxyj/service/GuideHierarchyService.java
  15. 109 0
      zxyj-backend/src/main/java/com/zxyj/service/GuideInviteCodeService.java
  16. 111 0
      zxyj-backend/src/main/java/com/zxyj/service/GuidePackageService.java
  17. 205 0
      zxyj-backend/src/main/java/com/zxyj/service/PackagePurchaseService.java
  18. 108 0
      zxyj-backend/src/main/java/com/zxyj/service/PaymentService.java
  19. 132 0
      zxyj-backend/src/main/resources/schema_guide.sql
  20. 43 7
      zxyj-frontend/pages.json
  21. 525 0
      zxyj-frontend/pages/assessment/apply.vue
  22. 537 0
      zxyj-frontend/pages/assessment/purchase.vue
  23. 489 0
      zxyj-frontend/pages/assessment/results.vue
  24. 295 0
      zxyj-frontend/pages/guide/hierarchy/index.vue
  25. 548 0
      zxyj-frontend/pages/guide/invite/share.vue
  26. 360 0
      zxyj-frontend/pages/guide/register/index.vue
  27. 183 0
      zxyj-frontend/utils/api.js

+ 77 - 0
zxyj-backend/src/main/java/com/zxyj/controller/guide/AssessmentController.java

@@ -0,0 +1,77 @@
+package com.zxyj.controller.guide;
+
+import com.zxyj.common.Result;
+import com.zxyj.entity.DanAssessmentResult;
+import com.zxyj.service.AssessmentPermissionService;
+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/assessment")
+public class AssessmentController {
+
+    @Autowired
+    private AssessmentPermissionService assessmentPermissionService;
+
+    /**
+     * 获取测评报告详情
+     */
+    @Operation(summary = "获取测评报告详情")
+    @GetMapping("/result/{id}")
+    public Result<DanAssessmentResult> getAssessmentResult(@RequestAttribute("userId") Long userId,
+                                                          @RequestAttribute("role") String role,
+                                                          @PathVariable Long id) {
+        // 只有指导师可以查看测评报告
+        if (!"teacher".equals(role)) {
+            return Result.error("只有指导师可以查看测评报告");
+        }
+
+        // 检查权限
+        if (!assessmentPermissionService.canViewAssessment(userId, id)) {
+            return Result.error("无权查看该测评报告");
+        }
+
+        DanAssessmentResult result = assessmentPermissionService.getAssessmentById(id);
+        return Result.success(result);
+    }
+
+    /**
+     * 获取指导师自己的测评报告列表
+     */
+    @Operation(summary = "获取指导师自己的测评报告列表")
+    @GetMapping("/my-results")
+    public Result<List<DanAssessmentResult>> getMyAssessmentResults(@RequestAttribute("userId") Long userId,
+                                                                     @RequestAttribute("role") String role) {
+        // 只有指导师可以查看测评报告
+        if (!"teacher".equals(role)) {
+            return Result.error("只有指导师可以查看测评报告");
+        }
+
+        List<DanAssessmentResult> results = assessmentPermissionService.getOwnAssessments(userId);
+        return Result.success(results);
+    }
+
+    /**
+     * 获取下级指导师的测评报告列表
+     */
+    @Operation(summary = "获取下级指导师的测评报告列表")
+    @GetMapping("/child-results")
+    public Result<List<DanAssessmentResult>> getChildAssessmentResults(@RequestAttribute("userId") Long userId,
+                                                                       @RequestAttribute("role") String role) {
+        // 只有指导师可以查看测评报告
+        if (!"teacher".equals(role)) {
+            return Result.error("只有指导师可以查看测评报告");
+        }
+
+        List<DanAssessmentResult> results = assessmentPermissionService.getAccessibleAssessments(userId);
+        return Result.success(results);
+    }
+}

+ 226 - 0
zxyj-backend/src/main/java/com/zxyj/controller/guide/GuideManagementController.java

@@ -0,0 +1,226 @@
+package com.zxyj.controller.guide;
+
+import com.zxyj.common.Result;
+import com.zxyj.dto.GuideRegisterDTO;
+import com.zxyj.dto.PackagePurchaseDTO;
+import com.zxyj.entity.GuideInviteCode;
+import com.zxyj.entity.GuidePackage;
+import com.zxyj.entity.GuideHierarchy;
+import com.zxyj.entity.PackageOrder;
+import com.zxyj.entity.User;
+import com.zxyj.mapper.UserMapper;
+import com.zxyj.service.GuideHierarchyService;
+import com.zxyj.service.GuideInviteCodeService;
+import com.zxyj.service.GuidePackageService;
+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;
+
+/**
+ * 指导师管理控制器
+ */
+@Tag(name = "指导师管理", description = "指导师注册、邀请码、层级关系等接口")
+@RestController
+@RequestMapping("/api/guide")
+public class GuideManagementController {
+
+    @Autowired
+    private GuideInviteCodeService guideInviteCodeService;
+
+    @Autowired
+    private GuideHierarchyService guideHierarchyService;
+
+    @Autowired
+    private GuidePackageService guidePackageService;
+
+    @Autowired
+    private UserMapper userMapper;
+
+    /**
+     * 指导师注册
+     */
+    @Operation(summary = "指导师注册")
+    @PostMapping("/register")
+    public Result<Boolean> registerGuide(@RequestBody GuideRegisterDTO dto) {
+        // 检查用户是否存在
+        User user = userMapper.selectById(dto.getUserId());
+        if (user == null) {
+            return Result.error("用户不存在");
+        }
+
+        // 如果有邀请码,验证并建立层级关系
+        if (dto.getInviteCode() != null && !dto.getInviteCode().isEmpty()) {
+            GuideInviteCode inviteCode = guideInviteCodeService.validateInviteCode(dto.getInviteCode());
+            if (inviteCode == null) {
+                return Result.error("邀请码无效或已过期");
+            }
+
+            // 建立层级关系
+            guideHierarchyService.addChildGuide(inviteCode.getGuideId(), dto.getUserId());
+
+            // 使用邀请码
+            guideInviteCodeService.useInviteCode(dto.getInviteCode(), dto.getUserId());
+        }
+
+        // 更新用户信息
+        user.setTeacherNo(dto.getTeacherNo());
+        user.setTeacherPhoto(dto.getTeacherPhoto());
+        user.setCertificateImage(dto.getCertificateImage());
+        user.setRealName(dto.getRealName());
+        user.setPhone(dto.getPhone());
+        user.setIdCard(dto.getIdCard());
+        user.setTeacherStatus("pending"); // 待审核
+        user.setUpdatedAt(new java.util.Date());
+        userMapper.updateById(user);
+
+        return Result.success(true);
+    }
+
+    /**
+     * 生成邀请码
+     */
+    @Operation(summary = "生成邀请码")
+    @PostMapping("/invite-code/generate")
+    public Result<GuideInviteCode> generateInviteCode(@RequestAttribute("userId") Long userId,
+                                                      @RequestParam(defaultValue = "1") Integer maxUseCount,
+                                                      @RequestParam(defaultValue = "30") Integer expireDays) {
+        GuideInviteCode inviteCode = guideInviteCodeService.generateInviteCode(userId, maxUseCount, expireDays);
+        return Result.success(inviteCode);
+    }
+
+    /**
+     * 获取邀请码列表
+     */
+    @Operation(summary = "获取邀请码列表")
+    @GetMapping("/invite-codes")
+    public Result<List<GuideInviteCode>> getInviteCodes(@RequestAttribute("userId") Long userId) {
+        List<GuideInviteCode> inviteCodes = guideInviteCodeService.getGuideInviteCodes(userId);
+        return Result.success(inviteCodes);
+    }
+
+    /**
+     * 验证邀请码
+     */
+    @Operation(summary = "验证邀请码")
+    @GetMapping("/invite-code/validate")
+    public Result<Map<String, Object>> validateInviteCode(@RequestParam String code) {
+        GuideInviteCode inviteCode = guideInviteCodeService.validateInviteCode(code);
+        
+        if (inviteCode == null) {
+            return Result.error("邀请码无效或已过期");
+        }
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("guideId", inviteCode.getGuideId());
+        result.put("code", inviteCode.getCode());
+        result.put("maxUseCount", inviteCode.getMaxUseCount());
+        result.put("useCount", inviteCode.getUseCount());
+        result.put("expireAt", inviteCode.getExpireAt());
+
+        // 获取指导师信息
+        User guide = userMapper.selectById(inviteCode.getGuideId());
+        if (guide != null) {
+            result.put("guideName", guide.getNickname());
+            result.put("guideAvatar", guide.getAvatar());
+        }
+
+        return Result.success(result);
+    }
+
+    /**
+     * 获取下级指导师列表
+     */
+    @Operation(summary = "获取下级指导师列表")
+    @GetMapping("/child-guides")
+    public Result<List<User>> getChildGuides(@RequestAttribute("userId") Long userId) {
+        List<User> childGuides = guideHierarchyService.getChildGuides(userId);
+        return Result.success(childGuides);
+    }
+
+    /**
+     * 获取所有下级指导师(包括间接下级)
+     */
+    @Operation(summary = "获取所有下级指导师")
+    @GetMapping("/all-child-guides")
+    public Result<List<User>> getAllChildGuides(@RequestAttribute("userId") Long userId) {
+        List<User> childGuides = guideHierarchyService.getAllChildGuides(userId);
+        return Result.success(childGuides);
+    }
+
+    /**
+     * 获取上级指导师
+     */
+    @Operation(summary = "获取上级指导师")
+    @GetMapping("/parent-guide")
+    public Result<User> getParentGuide(@RequestAttribute("userId") Long userId) {
+        User parentGuide = guideHierarchyService.getParentGuide(userId);
+        return Result.success(parentGuide);
+    }
+
+    /**
+     * 添加指导师套餐
+     */
+    @Operation(summary = "添加指导师套餐")
+    @PostMapping("/package/add")
+    public Result<Boolean> addGuidePackage(@RequestAttribute("userId") Long userId,
+                                          @RequestBody Map<String, Object> params) {
+        Long packageId = Long.valueOf(params.get("packageId").toString());
+        java.math.BigDecimal price = new java.math.BigDecimal(params.get("price").toString());
+        java.math.BigDecimal commissionRate = params.containsKey("commissionRate") 
+            ? new java.math.BigDecimal(params.get("commissionRate").toString())
+            : new java.math.BigDecimal("0.1");
+
+        boolean success = guidePackageService.addGuidePackage(userId, packageId, price, commissionRate);
+        return Result.success(success);
+    }
+
+    /**
+     * 获取指导师套餐列表
+     */
+    @Operation(summary = "获取指导师套餐列表")
+    @GetMapping("/packages")
+    public Result<List<GuidePackage>> getGuidePackages(@RequestAttribute("userId") Long userId) {
+        List<GuidePackage> packages = guidePackageService.getGuidePackages(userId);
+        return Result.success(packages);
+    }
+
+    /**
+     * 获取指导师对特定套餐的定价
+     */
+    @Operation(summary = "获取指导师套餐定价")
+    @GetMapping("/package/{packageId}")
+    public Result<GuidePackage> getGuidePackage(@RequestAttribute("userId") Long userId,
+                                               @PathVariable Long packageId) {
+        GuidePackage guidePackage = guidePackageService.getGuidePackage(userId, packageId);
+        return Result.success(guidePackage);
+    }
+
+    /**
+     * 更新指导师套餐价格
+     */
+    @Operation(summary = "更新指导师套餐价格")
+    @PutMapping("/package/{packageId}")
+    public Result<Boolean> updateGuidePackage(@RequestAttribute("userId") Long userId,
+                                               @PathVariable Long packageId,
+                                               @RequestBody Map<String, Object> params) {
+        java.math.BigDecimal price = new java.math.BigDecimal(params.get("price").toString());
+        boolean success = guidePackageService.updateGuidePackagePrice(userId, packageId, price);
+        return Result.success(success);
+    }
+
+    /**
+     * 删除指导师套餐
+     */
+    @Operation(summary = "删除指导师套餐")
+    @DeleteMapping("/package/{packageId}")
+    public Result<Boolean> deleteGuidePackage(@RequestAttribute("userId") Long userId,
+                                               @PathVariable Long packageId) {
+        boolean success = guidePackageService.deleteGuidePackage(userId, packageId);
+        return Result.success(success);
+    }
+}

+ 82 - 0
zxyj-backend/src/main/java/com/zxyj/controller/guide/PackagePurchaseController.java

@@ -0,0 +1,82 @@
+package com.zxyj.controller.guide;
+
+import com.zxyj.common.Result;
+import com.zxyj.dto.PackagePurchaseDTO;
+import com.zxyj.entity.PackageOrder;
+import com.zxyj.service.PackagePurchaseService;
+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/package")
+public class PackagePurchaseController {
+
+    @Autowired
+    private PackagePurchaseService packagePurchaseService;
+
+    /**
+     * 获取套餐价格
+     */
+    @Operation(summary = "获取套餐价格")
+    @GetMapping("/price")
+    public Result<Map<String, Object>> getPackagePrice(@RequestParam Long packageId,
+                                                        @RequestParam(required = false) Long guideId,
+                                                        @RequestParam(required = false) String inviteCode) {
+        Map<String, Object> priceInfo = packagePurchaseService.getPackagePrice(packageId, guideId, inviteCode);
+        
+        if (priceInfo == null) {
+            return Result.error("套餐不存在");
+        }
+        
+        return Result.success(priceInfo);
+    }
+
+    /**
+     * 获取可用的指导师列表
+     */
+    @Operation(summary = "获取可用的指导师列表")
+    @GetMapping("/available-guides")
+    public Result<List<Map<String, Object>>> getAvailableGuides(@RequestParam Long packageId) {
+        List<Map<String, Object>> guides = packagePurchaseService.getAvailableGuides(packageId);
+        return Result.success(guides);
+    }
+
+    /**
+     * 创建套餐订单
+     */
+    @Operation(summary = "创建套餐订单")
+    @PostMapping("/order/create")
+    public Result<PackageOrder> createOrder(@RequestBody PackagePurchaseDTO dto) {
+        PackageOrder order = packagePurchaseService.createOrder(dto);
+        
+        if (order == null) {
+            return Result.error("创建订单失败");
+        }
+        
+        return Result.success(order);
+    }
+
+    /**
+     * 获取订单详情
+     */
+    @Operation(summary = "获取订单详情")
+    @GetMapping("/order/{orderNo}")
+    public Result<PackageOrder> getOrder(@PathVariable String orderNo) {
+        PackageOrder order = packagePurchaseService.getOrder(orderNo);
+        
+        if (order == null) {
+            return Result.error("订单不存在");
+        }
+        
+        return Result.success(order);
+    }
+}

+ 92 - 0
zxyj-backend/src/main/java/com/zxyj/controller/guide/PaymentController.java

@@ -0,0 +1,92 @@
+package com.zxyj.controller.guide;
+
+import com.zxyj.common.Result;
+import com.zxyj.entity.PackageOrder;
+import com.zxyj.service.PaymentService;
+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 PaymentController {
+
+    @Autowired
+    private PaymentService paymentService;
+
+    /**
+     * 创建微信支付订单
+     */
+    @Operation(summary = "创建微信支付订单")
+    @PostMapping("/wechat/create")
+    public Result<Map<String, Object>> createWechatPayOrder(@RequestBody Map<String, Object> params) {
+        String orderNo = (String) params.get("orderNo");
+        String description = (String) params.get("description");
+        Integer amount = (Integer) params.get("amount");
+        
+        Map<String, Object> result = paymentService.createWechatPayOrder(orderNo, description, amount);
+        return Result.success(result);
+    }
+
+    /**
+     * 创建支付宝订单
+     */
+    @Operation(summary = "创建支付宝订单")
+    @PostMapping("/alipay/create")
+    public Result<Map<String, Object>> createAlipayOrder(@RequestBody Map<String, Object> params) {
+        String orderNo = (String) params.get("orderNo");
+        String description = (String) params.get("description");
+        Integer amount = (Integer) params.get("amount");
+        
+        Map<String, Object> result = paymentService.createAlipayOrder(orderNo, description, amount);
+        return Result.success(result);
+    }
+
+    /**
+     * 处理微信支付回调
+     */
+    @Operation(summary = "处理微信支付回调")
+    @PostMapping("/wechat/callback")
+    public Result<Boolean> handleWechatCallback(@RequestBody Map<String, Object> params) {
+        String orderNo = (String) params.get("orderNo");
+        String transactionId = (String) params.get("transactionId");
+        
+        boolean success = paymentService.handlePaymentCallback(orderNo, transactionId, "wechat");
+        return Result.success(success);
+    }
+
+    /**
+     * 处理支付宝回调
+     */
+    @Operation(summary = "处理支付宝回调")
+    @PostMapping("/alipay/callback")
+    public Result<Boolean> handleAlipayCallback(@RequestBody Map<String, Object> params) {
+        String orderNo = (String) params.get("orderNo");
+        String transactionId = (String) params.get("transactionId");
+        
+        boolean success = paymentService.handlePaymentCallback(orderNo, transactionId, "alipay");
+        return Result.success(success);
+    }
+
+    /**
+     * 查询订单状态
+     */
+    @Operation(summary = "查询订单状态")
+    @GetMapping("/order/status/{orderNo}")
+    public Result<PackageOrder> getOrderStatus(@PathVariable String orderNo) {
+        PackageOrder order = paymentService.getOrderStatus(orderNo);
+        
+        if (order == null) {
+            return Result.error("订单不存在");
+        }
+        
+        return Result.success(order);
+    }
+}

+ 28 - 0
zxyj-backend/src/main/java/com/zxyj/dto/GuideRegisterDTO.java

@@ -0,0 +1,28 @@
+package com.zxyj.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 指导师注册 DTO
+ */
+@Data
+public class GuideRegisterDTO implements Serializable {
+
+    private Long userId;         // 用户ID
+
+    private String inviteCode;    // 邀请码(可选)
+
+    private String teacherNo;     // 指导师证书号
+
+    private String teacherPhoto;  // 指导师照片
+
+    private String certificateImage; // 指导师证书照片
+
+    private String realName;      // 真实姓名
+
+    private String phone;         // 手机号
+
+    private String idCard;        // 身份证号
+}

+ 62 - 0
zxyj-backend/src/main/java/com/zxyj/dto/PackagePurchaseDTO.java

@@ -0,0 +1,62 @@
+package com.zxyj.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 套餐购买 DTO
+ */
+@Data
+public class PackagePurchaseDTO implements Serializable {
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 家庭ID
+     */
+    private Long familyId;
+
+    /**
+     * 套餐ID
+     */
+    private Long packageId;
+
+    /**
+     * 指导师ID(可选)
+     */
+    private Long guideId;
+
+    /**
+     * 邀请码(可选)
+     */
+    private String inviteCode;
+
+    /**
+     * 孩子ID(可选)
+     */
+    private Long childId;
+
+    /**
+     * 预约日期
+     */
+    private String appointmentDate;
+
+    /**
+     * 预约时间段
+     */
+    private String appointmentTime;
+
+    /**
+     * 支付方式
+     */
+    private String payMethod;
+
+    /**
+     * 备注
+     */
+    private String remark;
+}

+ 32 - 0
zxyj-backend/src/main/java/com/zxyj/entity/GuideHierarchy.java

@@ -0,0 +1,32 @@
+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.util.Date;
+
+/**
+ * 指导师层级关系
+ */
+@Data
+@TableName("guide_hierarchy")
+public class GuideHierarchy implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long parentId;       // 上级指导师ID
+
+    private Long childId;        // 下级指导师ID
+
+    private Integer level;       // 层级深度(1表示直接下级)
+
+    private String status;       // active/inactive
+
+    private Date createdAt;
+
+    private Date updatedAt;
+}

+ 40 - 0
zxyj-backend/src/main/java/com/zxyj/entity/GuideInviteCode.java

@@ -0,0 +1,40 @@
+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.util.Date;
+
+/**
+ * 指导师邀请码
+ */
+@Data
+@TableName("guide_invite_codes")
+public class GuideInviteCode implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long guideId;        // 指导师用户ID
+
+    private String code;         // 邀请码(唯一)
+
+    private String status;       // active/used/expired
+
+    private Long usedByUserId;   // 使用该邀请码的用户ID
+
+    private Date usedAt;         // 使用时间
+
+    private Date expireAt;       // 过期时间
+
+    private Integer useCount;    // 使用次数
+
+    private Integer maxUseCount; // 最大使用次数
+
+    private Date createdAt;
+
+    private Date updatedAt;
+}

+ 37 - 0
zxyj-backend/src/main/java/com/zxyj/entity/GuidePackage.java

@@ -0,0 +1,37 @@
+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("guide_packages")
+public class GuidePackage implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long guideId;        // 指导师ID
+
+    private Long packageId;      // 套餐ID(关联task_template_packages)
+
+    private String packageName;  // 套餐名称
+
+    private BigDecimal price;    // 指导师定价
+
+    private BigDecimal commissionRate; // 佣金比例(0.1表示10%)
+
+    private String status;       // active/inactive
+
+    private Date createdAt;
+
+    private Date updatedAt;
+}

+ 12 - 0
zxyj-backend/src/main/java/com/zxyj/mapper/GuideHierarchyMapper.java

@@ -0,0 +1,12 @@
+package com.zxyj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zxyj.entity.GuideHierarchy;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 指导师层级关系 Mapper
+ */
+@Mapper
+public interface GuideHierarchyMapper extends BaseMapper<GuideHierarchy> {
+}

+ 12 - 0
zxyj-backend/src/main/java/com/zxyj/mapper/GuideInviteCodeMapper.java

@@ -0,0 +1,12 @@
+package com.zxyj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zxyj.entity.GuideInviteCode;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 指导师邀请码 Mapper
+ */
+@Mapper
+public interface GuideInviteCodeMapper extends BaseMapper<GuideInviteCode> {
+}

+ 12 - 0
zxyj-backend/src/main/java/com/zxyj/mapper/GuidePackageMapper.java

@@ -0,0 +1,12 @@
+package com.zxyj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zxyj.entity.GuidePackage;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 指导师套餐 Mapper
+ */
+@Mapper
+public interface GuidePackageMapper extends BaseMapper<GuidePackage> {
+}

+ 90 - 0
zxyj-backend/src/main/java/com/zxyj/service/AssessmentPermissionService.java

@@ -0,0 +1,90 @@
+package com.zxyj.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zxyj.entity.DanAssessmentResult;
+import com.zxyj.entity.User;
+import com.zxyj.mapper.DanAssessmentResultMapper;
+import com.zxyj.mapper.UserMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 测评报告权限服务
+ */
+@Service
+public class AssessmentPermissionService {
+
+    @Autowired
+    private GuideHierarchyService guideHierarchyService;
+
+    @Autowired
+    private UserMapper userMapper;
+
+    /**
+     * 检查指导师是否有权限查看测评报告
+     */
+    public boolean canViewAssessment(Long guideId, Long assessmentId) {
+        DanAssessmentResult assessment = getAssessmentById(assessmentId);
+        if (assessment == null) {
+            return false;
+        }
+
+        // 检查是否是测评的指导师
+        if (assessment.getTeacherId().equals(guideId)) {
+            return true;
+        }
+
+        // 检查是否是下级指导师
+        return guideHierarchyService.isChildGuide(guideId, assessment.getTeacherId());
+    }
+
+    /**
+     * 获取指导师可以查看的测评报告列表
+     */
+    public List<DanAssessmentResult> getAccessibleAssessments(Long guideId) {
+        // 获取所有下级指导师
+        List<User> childGuides = guideHierarchyService.getAllChildGuides(guideId);
+        
+        if (childGuides.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 获取下级指导师的测评报告
+        List<Long> childGuideIds = childGuides.stream()
+            .map(User::getId)
+            .collect(Collectors.toList());
+
+        LambdaQueryWrapper<DanAssessmentResult> wrapper = new LambdaQueryWrapper<>();
+        wrapper.in(DanAssessmentResult::getTeacherId, childGuideIds)
+               .eq(DanAssessmentResult::getStatus, "completed")
+               .orderByDesc(DanAssessmentResult::getAssessmentDate);
+
+        return assessmentResultMapper.selectList(wrapper);
+    }
+
+    /**
+     * 获取指导师自己的测评报告
+     */
+    public List<DanAssessmentResult> getOwnAssessments(Long guideId) {
+        LambdaQueryWrapper<DanAssessmentResult> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(DanAssessmentResult::getTeacherId, guideId)
+               .eq(DanAssessmentResult::getStatus, "completed")
+               .orderByDesc(DanAssessmentResult::getAssessmentDate);
+
+        return assessmentResultMapper.selectList(wrapper);
+    }
+
+    /**
+     * 获取测评报告
+     */
+    public DanAssessmentResult getAssessmentById(Long assessmentId) {
+        return assessmentResultMapper.selectById(assessmentId);
+    }
+
+    @Autowired
+    private DanAssessmentResultMapper assessmentResultMapper;
+}

+ 151 - 0
zxyj-backend/src/main/java/com/zxyj/service/GuideHierarchyService.java

@@ -0,0 +1,151 @@
+package com.zxyj.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zxyj.entity.GuideHierarchy;
+import com.zxyj.entity.User;
+import com.zxyj.mapper.GuideHierarchyMapper;
+import com.zxyj.mapper.UserMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 指导师层级关系服务
+ */
+@Service
+public class GuideHierarchyService extends ServiceImpl<GuideHierarchyMapper, GuideHierarchy> {
+
+    @Autowired
+    private UserMapper userMapper;
+
+    /**
+     * 添加下级指导师
+     */
+    public boolean addChildGuide(Long parentId, Long childId) {
+        // 检查是否已经存在关系
+        LambdaQueryWrapper<GuideHierarchy> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(GuideHierarchy::getParentId, parentId)
+               .eq(GuideHierarchy::getChildId, childId);
+        
+        if (this.count(wrapper) > 0) {
+            return false; // 已经存在关系
+        }
+        
+        // 计算层级深度
+        Integer level = 1;
+        GuideHierarchy parentRelation = this.getOne(new LambdaQueryWrapper<GuideHierarchy>()
+            .eq(GuideHierarchy::getChildId, parentId));
+        
+        if (parentRelation != null) {
+            level = parentRelation.getLevel() + 1;
+        }
+        
+        GuideHierarchy hierarchy = new GuideHierarchy();
+        hierarchy.setParentId(parentId);
+        hierarchy.setChildId(childId);
+        hierarchy.setLevel(level);
+        hierarchy.setStatus("active");
+        
+        return this.save(hierarchy);
+    }
+
+    /**
+     * 获取下级指导师列表
+     */
+    public List<User> getChildGuides(Long guideId) {
+        LambdaQueryWrapper<GuideHierarchy> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(GuideHierarchy::getParentId, guideId)
+               .eq(GuideHierarchy::getStatus, "active");
+        
+        List<GuideHierarchy> hierarchies = this.list(wrapper);
+        
+        if (hierarchies.isEmpty()) {
+            return new ArrayList<>();
+        }
+        
+        List<Long> childIds = hierarchies.stream()
+            .map(GuideHierarchy::getChildId)
+            .collect(Collectors.toList());
+        
+        return userMapper.selectBatchIds(childIds);
+    }
+
+    /**
+     * 获取所有下级指导师(包括间接下级)
+     */
+    public List<User> getAllChildGuides(Long guideId) {
+        List<User> result = new ArrayList<>();
+        List<Long> currentLevelIds = new ArrayList<>();
+        currentLevelIds.add(guideId);
+        
+        while (!currentLevelIds.isEmpty()) {
+            LambdaQueryWrapper<GuideHierarchy> wrapper = new LambdaQueryWrapper<>();
+            wrapper.in(GuideHierarchy::getParentId, currentLevelIds)
+                   .eq(GuideHierarchy::getStatus, "active");
+            
+            List<GuideHierarchy> hierarchies = this.list(wrapper);
+            
+            if (hierarchies.isEmpty()) {
+                break;
+            }
+            
+            List<Long> childIds = hierarchies.stream()
+                .map(GuideHierarchy::getChildId)
+                .collect(Collectors.toList());
+            
+            result.addAll(userMapper.selectBatchIds(childIds));
+            currentLevelIds = childIds;
+        }
+        
+        return result;
+    }
+
+    /**
+     * 获取上级指导师
+     */
+    public User getParentGuide(Long guideId) {
+        GuideHierarchy hierarchy = this.getOne(new LambdaQueryWrapper<GuideHierarchy>()
+            .eq(GuideHierarchy::getChildId, guideId)
+            .eq(GuideHierarchy::getStatus, "active"));
+        
+        if (hierarchy == null) {
+            return null;
+        }
+        
+        return userMapper.selectById(hierarchy.getParentId());
+    }
+
+    /**
+     * 检查是否是下级指导师
+     */
+    public boolean isChildGuide(Long parentId, Long childId) {
+        List<Long> currentLevelIds = new ArrayList<>();
+        currentLevelIds.add(parentId);
+        
+        while (!currentLevelIds.isEmpty()) {
+            if (currentLevelIds.contains(childId)) {
+                return true;
+            }
+            
+            LambdaQueryWrapper<GuideHierarchy> wrapper = new LambdaQueryWrapper<>();
+            wrapper.in(GuideHierarchy::getParentId, currentLevelIds)
+                   .eq(GuideHierarchy::getStatus, "active");
+            
+            List<GuideHierarchy> hierarchies = this.list(wrapper);
+            
+            if (hierarchies.isEmpty()) {
+                break;
+            }
+            
+            currentLevelIds = hierarchies.stream()
+                .map(GuideHierarchy::getChildId)
+                .collect(Collectors.toList());
+        }
+        
+        return false;
+    }
+}

+ 109 - 0
zxyj-backend/src/main/java/com/zxyj/service/GuideInviteCodeService.java

@@ -0,0 +1,109 @@
+package com.zxyj.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zxyj.entity.GuideInviteCode;
+import com.zxyj.mapper.GuideInviteCodeMapper;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * 指导师邀请码服务
+ */
+@Service
+public class GuideInviteCodeService extends ServiceImpl<GuideInviteCodeMapper, GuideInviteCode> {
+
+    /**
+     * 生成邀请码
+     */
+    public GuideInviteCode generateInviteCode(Long guideId, Integer maxUseCount, Integer expireDays) {
+        GuideInviteCode inviteCode = new GuideInviteCode();
+        inviteCode.setGuideId(guideId);
+        inviteCode.setCode(generateCode());
+        inviteCode.setStatus("active");
+        inviteCode.setUseCount(0);
+        inviteCode.setMaxUseCount(maxUseCount != null ? maxUseCount : 1);
+        
+        if (expireDays != null && expireDays > 0) {
+            Date expireAt = new Date();
+            expireAt.setTime(expireAt.getTime() + expireDays * 24 * 60 * 60 * 1000L);
+            inviteCode.setExpireAt(expireAt);
+        }
+        
+        this.save(inviteCode);
+        return inviteCode;
+    }
+
+    /**
+     * 验证邀请码
+     */
+    public GuideInviteCode validateInviteCode(String code) {
+        LambdaQueryWrapper<GuideInviteCode> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(GuideInviteCode::getCode, code)
+               .eq(GuideInviteCode::getStatus, "active");
+        
+        GuideInviteCode inviteCode = this.getOne(wrapper);
+        
+        if (inviteCode == null) {
+            return null;
+        }
+        
+        // 检查是否过期
+        if (inviteCode.getExpireAt() != null && inviteCode.getExpireAt().before(new Date())) {
+            inviteCode.setStatus("expired");
+            this.updateById(inviteCode);
+            return null;
+        }
+        
+        // 检查使用次数
+        if (inviteCode.getUseCount() >= inviteCode.getMaxUseCount()) {
+            inviteCode.setStatus("used");
+            this.updateById(inviteCode);
+            return null;
+        }
+        
+        return inviteCode;
+    }
+
+    /**
+     * 使用邀请码
+     */
+    public boolean useInviteCode(String code, Long userId) {
+        GuideInviteCode inviteCode = validateInviteCode(code);
+        
+        if (inviteCode == null) {
+            return false;
+        }
+        
+        inviteCode.setUsedByUserId(userId);
+        inviteCode.setUsedAt(new Date());
+        inviteCode.setUseCount(inviteCode.getUseCount() + 1);
+        
+        // 如果达到最大使用次数,标记为已使用
+        if (inviteCode.getUseCount() >= inviteCode.getMaxUseCount()) {
+            inviteCode.setStatus("used");
+        }
+        
+        return this.updateById(inviteCode);
+    }
+
+    /**
+     * 获取指导师的邀请码列表
+     */
+    public java.util.List<GuideInviteCode> getGuideInviteCodes(Long guideId) {
+        LambdaQueryWrapper<GuideInviteCode> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(GuideInviteCode::getGuideId, guideId)
+               .orderByDesc(GuideInviteCode::getCreatedAt);
+        
+        return this.list(wrapper);
+    }
+
+    /**
+     * 生成随机邀请码
+     */
+    private String generateCode() {
+        return UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase();
+    }
+}

+ 111 - 0
zxyj-backend/src/main/java/com/zxyj/service/GuidePackageService.java

@@ -0,0 +1,111 @@
+package com.zxyj.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zxyj.entity.GuidePackage;
+import com.zxyj.entity.TaskTemplatePackage;
+import com.zxyj.mapper.GuidePackageMapper;
+import com.zxyj.mapper.TaskTemplatePackageMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 指导师套餐服务
+ */
+@Service
+public class GuidePackageService extends ServiceImpl<GuidePackageMapper, GuidePackage> {
+
+    @Autowired
+    private TaskTemplatePackageMapper taskTemplatePackageMapper;
+
+    /**
+     * 添加指导师套餐
+     */
+    public boolean addGuidePackage(Long guideId, Long packageId, BigDecimal price, BigDecimal commissionRate) {
+        // 检查是否已经存在
+        LambdaQueryWrapper<GuidePackage> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(GuidePackage::getGuideId, guideId)
+               .eq(GuidePackage::getPackageId, packageId);
+        
+        if (this.count(wrapper) > 0) {
+            return false; // 已经存在
+        }
+        
+        // 获取套餐信息
+        TaskTemplatePackage taskPackage = taskTemplatePackageMapper.selectById(packageId);
+        if (taskPackage == null) {
+            return false;
+        }
+        
+        GuidePackage guidePackage = new GuidePackage();
+        guidePackage.setGuideId(guideId);
+        guidePackage.setPackageId(packageId);
+        guidePackage.setPackageName(taskPackage.getName());
+        guidePackage.setPrice(price);
+        guidePackage.setCommissionRate(commissionRate != null ? commissionRate : new BigDecimal("0.1"));
+        guidePackage.setStatus("active");
+        
+        return this.save(guidePackage);
+    }
+
+    /**
+     * 获取指导师的套餐列表
+     */
+    public List<GuidePackage> getGuidePackages(Long guideId) {
+        LambdaQueryWrapper<GuidePackage> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(GuidePackage::getGuideId, guideId)
+               .eq(GuidePackage::getStatus, "active")
+               .orderByDesc(GuidePackage::getCreatedAt);
+        
+        return this.list(wrapper);
+    }
+
+    /**
+     * 获取指导师对特定套餐的定价
+     */
+    public GuidePackage getGuidePackage(Long guideId, Long packageId) {
+        LambdaQueryWrapper<GuidePackage> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(GuidePackage::getGuideId, guideId)
+               .eq(GuidePackage::getPackageId, packageId)
+               .eq(GuidePackage::getStatus, "active");
+        
+        return this.getOne(wrapper);
+    }
+
+    /**
+     * 更新指导师套餐价格
+     */
+    public boolean updateGuidePackagePrice(Long guideId, Long packageId, BigDecimal price) {
+        LambdaQueryWrapper<GuidePackage> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(GuidePackage::getGuideId, guideId)
+               .eq(GuidePackage::getPackageId, packageId);
+        
+        GuidePackage guidePackage = this.getOne(wrapper);
+        if (guidePackage == null) {
+            return false;
+        }
+        
+        guidePackage.setPrice(price);
+        return this.updateById(guidePackage);
+    }
+
+    /**
+     * 删除指导师套餐
+     */
+    public boolean deleteGuidePackage(Long guideId, Long packageId) {
+        LambdaQueryWrapper<GuidePackage> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(GuidePackage::getGuideId, guideId)
+               .eq(GuidePackage::getPackageId, packageId);
+        
+        GuidePackage guidePackage = this.getOne(wrapper);
+        if (guidePackage == null) {
+            return false;
+        }
+        
+        guidePackage.setStatus("inactive");
+        return this.updateById(guidePackage);
+    }
+}

+ 205 - 0
zxyj-backend/src/main/java/com/zxyj/service/PackagePurchaseService.java

@@ -0,0 +1,205 @@
+package com.zxyj.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zxyj.dto.PackagePurchaseDTO;
+import com.zxyj.entity.Family;
+import com.zxyj.entity.GuideInviteCode;
+import com.zxyj.entity.GuidePackage;
+import com.zxyj.entity.PackageOrder;
+import com.zxyj.entity.TaskTemplatePackage;
+import com.zxyj.entity.User;
+import com.zxyj.mapper.FamilyMapper;
+import com.zxyj.mapper.PackageOrderMapper;
+import com.zxyj.mapper.TaskTemplatePackageMapper;
+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.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 套餐购买服务
+ */
+@Service
+public class PackagePurchaseService {
+
+    @Autowired
+    private GuideInviteCodeService guideInviteCodeService;
+
+    @Autowired
+    private GuidePackageService guidePackageService;
+
+    @Autowired
+    private TaskTemplatePackageMapper taskTemplatePackageMapper;
+
+    @Autowired
+    private PackageOrderMapper packageOrderMapper;
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Autowired
+    private FamilyMapper familyMapper;
+
+    /**
+     * 获取套餐价格(含指导师定价)
+     */
+    public Map<String, Object> getPackagePrice(Long packageId, Long guideId, String inviteCode) {
+        // 获取套餐基础信息
+        TaskTemplatePackage taskPackage = taskTemplatePackageMapper.selectById(packageId);
+        if (taskPackage == null) {
+            return null;
+        }
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("packageId", packageId);
+        result.put("packageName", taskPackage.getName());
+        result.put("basePrice", taskPackage.getPrice());
+        result.put("platformFee", taskPackage.getPlatformFee());
+
+        // 如果有指导师ID,获取指导师定价
+        if (guideId != null) {
+            GuidePackage guidePackage = guidePackageService.getGuidePackage(guideId, packageId);
+            if (guidePackage != null) {
+                result.put("guidePrice", guidePackage.getPrice());
+                result.put("commissionRate", guidePackage.getCommissionRate());
+                result.put("totalPrice", guidePackage.getPrice());
+                
+                // 获取指导师信息
+                User guide = userMapper.selectById(guideId);
+                if (guide != null) {
+                    result.put("guideName", guide.getNickname());
+                    result.put("guideAvatar", guide.getAvatar());
+                }
+            } else {
+                // 指导师没有设置该套餐的价格,使用基础价格
+                result.put("guidePrice", taskPackage.getPrice());
+                result.put("commissionRate", new BigDecimal("0.1"));
+                result.put("totalPrice", taskPackage.getPrice());
+            }
+        } else if (inviteCode != null && !inviteCode.isEmpty()) {
+            // 如果有邀请码,获取邀请码关联的指导师定价
+            GuideInviteCode invite = guideInviteCodeService.validateInviteCode(inviteCode);
+            if (invite != null) {
+                GuidePackage guidePackage = guidePackageService.getGuidePackage(invite.getGuideId(), packageId);
+                if (guidePackage != null) {
+                    result.put("guidePrice", guidePackage.getPrice());
+                    result.put("commissionRate", guidePackage.getCommissionRate());
+                    result.put("totalPrice", guidePackage.getPrice());
+                    result.put("guideId", invite.getGuideId());
+                    
+                    // 获取指导师信息
+                    User guide = userMapper.selectById(invite.getGuideId());
+                    if (guide != null) {
+                        result.put("guideName", guide.getNickname());
+                        result.put("guideAvatar", guide.getAvatar());
+                    }
+                } else {
+                    // 指导师没有设置该套餐的价格,使用基础价格
+                    result.put("guidePrice", taskPackage.getPrice());
+                    result.put("commissionRate", new BigDecimal("0.1"));
+                    result.put("totalPrice", taskPackage.getPrice());
+                    result.put("guideId", invite.getGuideId());
+                }
+            } else {
+                // 邀请码无效,使用基础价格
+                result.put("guidePrice", taskPackage.getPrice());
+                result.put("commissionRate", new BigDecimal("0.1"));
+                result.put("totalPrice", taskPackage.getPrice());
+            }
+        } else {
+            // 没有指导师,使用基础价格
+            result.put("guidePrice", taskPackage.getPrice());
+            result.put("commissionRate", new BigDecimal("0.1"));
+            result.put("totalPrice", taskPackage.getPrice());
+        }
+
+        return result;
+    }
+
+    /**
+     * 获取可用的指导师列表(针对特定套餐)
+     */
+    public List<Map<String, Object>> getAvailableGuides(Long packageId) {
+        // 获取所有设置了该套餐价格的指导师
+        LambdaQueryWrapper<GuidePackage> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(GuidePackage::getPackageId, packageId)
+               .eq(GuidePackage::getStatus, "active");
+        
+        List<GuidePackage> guidePackages = guidePackageService.list(wrapper);
+        
+        return guidePackages.stream().map(guidePackage -> {
+            Map<String, Object> item = new HashMap<>();
+            item.put("guideId", guidePackage.getGuideId());
+            item.put("price", guidePackage.getPrice());
+            item.put("commissionRate", guidePackage.getCommissionRate());
+            
+            // 获取指导师信息
+            User guide = userMapper.selectById(guidePackage.getGuideId());
+            if (guide != null) {
+                item.put("guideName", guide.getNickname());
+                item.put("guideAvatar", guide.getAvatar());
+                item.put("teacherStatus", guide.getTeacherStatus());
+            }
+            
+            return item;
+        }).collect(Collectors.toList());
+    }
+
+    /**
+     * 创建套餐订单
+     */
+    public PackageOrder createOrder(PackagePurchaseDTO dto) {
+        // 获取套餐价格信息
+        Map<String, Object> priceInfo = getPackagePrice(dto.getPackageId(), dto.getGuideId(), dto.getInviteCode());
+        
+        if (priceInfo == null) {
+            return null;
+        }
+
+        // 确定指导师ID
+        Long guideId = dto.getGuideId();
+        if (guideId == null && priceInfo.containsKey("guideId")) {
+            guideId = (Long) priceInfo.get("guideId");
+        }
+
+        // 创建订单
+        PackageOrder order = new PackageOrder();
+        order.setOrderNo(generateOrderNo());
+        order.setUserId(dto.getUserId());
+        order.setFamilyId(dto.getFamilyId());
+        order.setPackageId(dto.getPackageId());
+        order.setPackageName((String) priceInfo.get("packageName"));
+        order.setPrice((BigDecimal) priceInfo.get("totalPrice"));
+        order.setPlatformFee((BigDecimal) priceInfo.get("platformFee"));
+        order.setGuideId(guideId);
+        order.setStatus("pending");
+        order.setPayMethod(dto.getPayMethod());
+        
+        packageOrderMapper.insert(order);
+        
+        return order;
+    }
+
+    /**
+     * 获取订单详情
+     */
+    public PackageOrder getOrder(String orderNo) {
+        return packageOrderMapper.selectOne(
+            new LambdaQueryWrapper<PackageOrder>()
+                .eq(PackageOrder::getOrderNo, orderNo)
+        );
+    }
+
+    /**
+     * 生成订单号
+     */
+    private String generateOrderNo() {
+        return "PKG" + System.currentTimeMillis() + (int)(Math.random() * 1000);
+    }
+}

+ 108 - 0
zxyj-backend/src/main/java/com/zxyj/service/PaymentService.java

@@ -0,0 +1,108 @@
+package com.zxyj.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zxyj.entity.PackageOrder;
+import com.zxyj.mapper.PackageOrderMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 支付服务
+ */
+@Service
+public class PaymentService {
+
+    @Autowired
+    private PackageOrderMapper packageOrderMapper;
+
+    /**
+     * 创建微信支付订单
+     */
+    public Map<String, Object> createWechatPayOrder(String orderNo, String description, Integer amount) {
+        // TODO: 调用微信支付API创建订单
+        // 这里简化处理,实际应该调用微信支付SDK
+        
+        Map<String, Object> result = new java.util.HashMap<>();
+        result.put("orderNo", orderNo);
+        result.put("prepayId", "wx_prepay_" + orderNo);
+        result.put("appId", "wx5ba8038ef16fb245");
+        result.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
+        result.put("nonceStr", generateNonceStr());
+        result.put("package", "prepay_id=wx_prepay_" + orderNo);
+        result.put("signType", "MD5");
+        result.put("paySign", generateSign());
+        
+        return result;
+    }
+
+    /**
+     * 创建支付宝订单
+     */
+    public Map<String, Object> createAlipayOrder(String orderNo, String description, Integer amount) {
+        // TODO: 调用支付宝支付API创建订单
+        // 这里简化处理,实际应该调用支付宝SDK
+        
+        Map<String, Object> result = new java.util.HashMap<>();
+        result.put("orderNo", orderNo);
+        result.put("orderString", generateOrderString(orderNo, description, amount));
+        
+        return result;
+    }
+
+    /**
+     * 处理支付回调
+     */
+    public boolean handlePaymentCallback(String orderNo, String transactionId, String payMethod) {
+        LambdaQueryWrapper<PackageOrder> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(PackageOrder::getOrderNo, orderNo);
+        
+        PackageOrder order = packageOrderMapper.selectOne(wrapper);
+        if (order == null) {
+            return false;
+        }
+        
+        // 更新订单状态
+        order.setStatus("paid");
+        order.setTransactionId(transactionId);
+        order.setPaidAt(new Date());
+        order.setUpdatedAt(new Date());
+        
+        return packageOrderMapper.updateById(order) > 0;
+    }
+
+    /**
+     * 查询订单状态
+     */
+    public PackageOrder getOrderStatus(String orderNo) {
+        LambdaQueryWrapper<PackageOrder> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(PackageOrder::getOrderNo, orderNo);
+        
+        return packageOrderMapper.selectOne(wrapper);
+    }
+
+    /**
+     * 生成随机字符串
+     */
+    private String generateNonceStr() {
+        return java.util.UUID.randomUUID().toString().replace("-", "").substring(0, 32);
+    }
+
+    /**
+     * 生成签名
+     */
+    private String generateSign() {
+        // TODO: 实际应该使用微信支付SDK生成签名
+        return "mock_sign_" + System.currentTimeMillis();
+    }
+
+    /**
+     * 生成支付宝订单字符串
+     */
+    private String generateOrderString(String orderNo, String description, Integer amount) {
+        // TODO: 实际应该使用支付宝SDK生成订单字符串
+        return "alipay_sdk_order_" + orderNo + "_" + amount;
+    }
+}

+ 132 - 0
zxyj-backend/src/main/resources/schema_guide.sql

@@ -0,0 +1,132 @@
+-- 指导师邀请码表
+CREATE TABLE IF NOT EXISTS `guide_invite_codes` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `guide_id` BIGINT NOT NULL COMMENT '指导师ID',
+  `code` VARCHAR(20) NOT NULL COMMENT '邀请码',
+  `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1-有效,0-已失效',
+  `use_count` INT NOT NULL DEFAULT 0 COMMENT '使用次数',
+  `max_use_count` INT NOT NULL DEFAULT 100 COMMENT '最大使用次数',
+  `expire_time` DATETIME COMMENT '过期时间',
+  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_code` (`code`),
+  KEY `idx_guide_id` (`guide_id`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='指导师邀请码表';
+
+-- 指导师层级关系表
+CREATE TABLE IF NOT EXISTS `guide_hierarchy` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `parent_id` BIGINT NOT NULL COMMENT '上级指导师ID',
+  `child_id` BIGINT NOT NULL COMMENT '下级指导师ID',
+  `level` INT NOT NULL DEFAULT 1 COMMENT '层级深度:1-直接下级,2-二级下级,以此类推',
+  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_parent_child` (`parent_id`, `child_id`),
+  KEY `idx_child_id` (`child_id`),
+  KEY `idx_level` (`level`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='指导师层级关系表';
+
+-- 指导师套餐表
+CREATE TABLE IF NOT EXISTS `guide_packages` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `guide_id` BIGINT NOT NULL COMMENT '指导师ID',
+  `package_id` BIGINT NOT NULL COMMENT '套餐ID',
+  `price` DECIMAL(10,2) NOT NULL COMMENT '指导师定价',
+  `commission_rate` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '上级佣金比例(%)',
+  `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1-上架,0-下架',
+  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_guide_package` (`guide_id`, `package_id`),
+  KEY `idx_package_id` (`package_id`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='指导师套餐表';
+
+-- 订单表
+CREATE TABLE IF NOT EXISTS `orders` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `order_no` VARCHAR(32) NOT NULL COMMENT '订单号',
+  `user_id` BIGINT NOT NULL COMMENT '用户ID',
+  `guide_id` BIGINT COMMENT '指导师ID',
+  `package_id` BIGINT NOT NULL COMMENT '套餐ID',
+  `package_name` VARCHAR(100) NOT NULL COMMENT '套餐名称',
+  `amount` DECIMAL(10,2) NOT NULL COMMENT '订单金额',
+  `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0-待支付,1-已支付,2-已取消,3-已退款',
+  `payment_method` VARCHAR(20) COMMENT '支付方式:wechat-微信支付,alipay-支付宝',
+  `payment_time` DATETIME COMMENT '支付时间',
+  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_order_no` (`order_no`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_guide_id` (`guide_id`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
+
+-- 支付记录表
+CREATE TABLE IF NOT EXISTS `payment_records` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `order_no` VARCHAR(32) NOT NULL COMMENT '订单号',
+  `transaction_id` VARCHAR(64) COMMENT '第三方交易号',
+  `payment_method` VARCHAR(20) NOT NULL COMMENT '支付方式:wechat-微信支付,alipay-支付宝',
+  `amount` DECIMAL(10,2) NOT NULL COMMENT '支付金额',
+  `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0-待支付,1-支付成功,2-支付失败',
+  `notify_time` DATETIME COMMENT '回调时间',
+  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_order_no` (`order_no`),
+  KEY `idx_transaction_id` (`transaction_id`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付记录表';
+
+-- 测评预约表
+CREATE TABLE IF NOT EXISTS `assessment_appointments` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `user_id` BIGINT NOT NULL COMMENT '用户ID',
+  `child_id` BIGINT COMMENT '孩子ID',
+  `guide_id` BIGINT NOT NULL COMMENT '指导师ID',
+  `package_id` BIGINT NOT NULL COMMENT '套餐ID',
+  `appointment_date` DATE NOT NULL COMMENT '预约日期',
+  `appointment_time` VARCHAR(20) NOT NULL COMMENT '预约时间段',
+  `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0-待确认,1-已确认,2-已完成,3-已取消',
+  `remark` VARCHAR(500) COMMENT '备注',
+  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_guide_id` (`guide_id`),
+  KEY `idx_appointment_date` (`appointment_date`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评预约表';
+
+-- 测评结果表
+CREATE TABLE IF NOT EXISTS `assessment_results` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `appointment_id` BIGINT COMMENT '预约ID',
+  `user_id` BIGINT NOT NULL COMMENT '用户ID',
+  `child_id` BIGINT COMMENT '孩子ID',
+  `child_name` VARCHAR(50) COMMENT '孩子姓名',
+  `guide_id` BIGINT NOT NULL COMMENT '指导师ID',
+  `guide_name` VARCHAR(50) COMMENT '指导师姓名',
+  `assessment_date` DATE NOT NULL COMMENT '测评日期',
+  `duration_minutes` INT COMMENT '测评时长(分钟)',
+  `dan_level` VARCHAR(20) COMMENT 'DAN等级',
+  `attention_score` DECIMAL(5,2) COMMENT '注意力得分',
+  `focus_score` DECIMAL(5,2) COMMENT '专注力得分',
+  `memory_score` DECIMAL(5,2) COMMENT '记忆力得分',
+  `logic_score` DECIMAL(5,2) COMMENT '逻辑思维得分',
+  `overall_score` DECIMAL(5,2) COMMENT '综合得分',
+  `analysis_report` TEXT COMMENT '分析报告',
+  `growth_suggestions` TEXT COMMENT '成长建议',
+  `next_review_date` DATE COMMENT '下次复测日期',
+  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_guide_id` (`guide_id`),
+  KEY `idx_assessment_date` (`assessment_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评结果表';

+ 43 - 7
zxyj-frontend/pages.json

@@ -205,13 +205,49 @@
     "navigationBarTitleText": "心愿管理"
     "navigationBarTitleText": "心愿管理"
   }
   }
 },
 },
-{
-  "path": "pages/parent/invite/index",
-  "style": {
-    "navigationBarTitleText": "邀请加入"
-  }
-}
-],
+    {
+      "path": "pages/parent/invite/index",
+      "style": {
+        "navigationBarTitleText": "邀请加入"
+      }
+    },
+    {
+      "path": "pages/guide/register/index",
+      "style": {
+        "navigationBarTitleText": "成为指导师"
+      }
+    },
+    {
+      "path": "pages/guide/invite/share",
+      "style": {
+        "navigationBarTitleText": "邀请分享"
+      }
+    },
+    {
+      "path": "pages/assessment/apply",
+      "style": {
+        "navigationBarTitleText": "申请测评"
+      }
+    },
+    {
+      "path": "pages/assessment/results",
+      "style": {
+        "navigationBarTitleText": "测评报告"
+      }
+    },
+    {
+      "path": "pages/assessment/purchase",
+      "style": {
+        "navigationBarTitleText": "购买测评"
+      }
+    },
+    {
+      "path": "pages/guide/hierarchy/index",
+      "style": {
+        "navigationBarTitleText": "下级指导师"
+      }
+    }
+  ],
   "globalStyle": {
   "globalStyle": {
     "navigationBarTextStyle": "black",
     "navigationBarTextStyle": "black",
     "navigationBarTitleText": "知行益家",
     "navigationBarTitleText": "知行益家",

+ 525 - 0
zxyj-frontend/pages/assessment/apply.vue

@@ -0,0 +1,525 @@
+<template>
+  <view class="container">
+    <view class="header">
+      <text class="title">申请测评</text>
+      <text class="subtitle">专业的DAN测评,了解孩子的专注力发展</text>
+    </view>
+
+    <!-- 指导师选择 -->
+    <view class="section">
+      <view class="section-title">选择指导师</view>
+      <view class="guide-selector">
+        <view 
+          class="guide-item" 
+          :class="{ 'selected': selectedGuideId === guide.guideId }"
+          v-for="guide in availableGuides" 
+          :key="guide.guideId"
+          @click="selectGuide(guide)"
+        >
+          <image class="guide-avatar" :src="guide.guideAvatar || '/static/default-avatar.png'" />
+          <view class="guide-info">
+            <text class="guide-name">{{ guide.guideName }}</text>
+            <text class="guide-price">¥{{ guide.price }}</text>
+          </view>
+          <view class="guide-check" v-if="selectedGuideId === guide.guideId">✓</view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 测评类型选择 -->
+    <view class="section">
+      <view class="section-title">测评类型</view>
+      <view class="type-selector">
+        <view 
+          class="type-item" 
+          :class="{ 'selected': selectedType === type.value }"
+          v-for="type in assessmentTypes" 
+          :key="type.value"
+          @click="selectType(type.value)"
+        >
+          <text class="type-icon">{{ type.icon }}</text>
+          <text class="type-name">{{ type.name }}</text>
+          <text class="type-desc">{{ type.desc }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 预约时间 -->
+    <view class="section">
+      <view class="section-title">预约时间</view>
+      <view class="date-picker">
+        <picker mode="date" :value="appointmentDate" @change="onDateChange">
+          <view class="picker-value">
+            <text>{{ appointmentDate || '选择日期' }}</text>
+            <text class="picker-arrow">›</text>
+          </view>
+        </picker>
+      </view>
+      <view class="time-picker">
+        <picker mode="time" :value="appointmentTime" @change="onTimeChange">
+          <view class="picker-value">
+            <text>{{ appointmentTime || '选择时间' }}</text>
+            <text class="picker-arrow">›</text>
+          </view>
+        </picker>
+      </view>
+    </view>
+
+    <!-- 孩子选择 -->
+    <view class="section">
+      <view class="section-title">选择孩子</view>
+      <view class="child-selector">
+        <view 
+          class="child-item" 
+          :class="{ 'selected': selectedChildId === child.id }"
+          v-for="child in children" 
+          :key="child.id"
+          @click="selectChild(child)"
+        >
+          <view class="child-avatar">{{ child.nickname ? child.nickname[0] : '?' }}</view>
+          <view class="child-info">
+            <text class="child-name">{{ child.nickname || '未命名' }}</text>
+            <text class="child-age">{{ child.age || 0 }}岁</text>
+          </view>
+          <view class="child-check" v-if="selectedChildId === child.id">✓</view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 价格信息 -->
+    <view class="section" v-if="selectedGuideId">
+      <view class="section-title">费用信息</view>
+      <view class="price-info">
+        <view class="price-row">
+          <text class="price-label">测评费用</text>
+          <text class="price-value">¥{{ totalPrice }}</text>
+        </view>
+        <view class="price-row">
+          <text class="price-label">指导师</text>
+          <text class="price-value">{{ selectedGuideName }}</text>
+        </view>
+        <view class="price-row" v-if="inviteCodeInfo">
+          <text class="price-label">邀请码优惠</text>
+          <text class="price-value discount">-¥{{ discountAmount }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 提交按钮 -->
+    <button class="btn-submit" @click="submit">提交申请</button>
+  </view>
+</template>
+
+<script>
+import { getAvailableGuides, createAssessmentAppointment, getChildren } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      availableGuides: [],
+      selectedGuideId: null,
+      selectedGuideName: '',
+      selectedType: 'initial',
+      appointmentDate: '',
+      appointmentTime: '',
+      selectedChildId: null,
+      children: [],
+      inviteCode: '',
+      inviteCodeInfo: null,
+      totalPrice: 0,
+      discountAmount: 0
+    }
+  },
+  onLoad(options) {
+    // 如果有邀请码参数,自动填充
+    if (options.inviteCode) {
+      this.inviteCode = options.inviteCode
+      this.validateInviteCode()
+    }
+
+    // 如果有套餐ID参数,自动选择
+    if (options.packageId) {
+      this.loadAvailableGuides(options.packageId)
+    } else {
+      this.loadAvailableGuides()
+    }
+
+    this.loadChildren()
+  },
+  methods: {
+    async loadAvailableGuides(packageId) {
+      try {
+        const res = await getAvailableGuides(packageId)
+        if (res.code === 200) {
+          this.availableGuides = res.data || []
+          // 如果有邀请码,自动选择对应的指导师
+          if (this.inviteCodeInfo && this.inviteCodeInfo.guideId) {
+            const guide = this.availableGuides.find(g => g.guideId === this.inviteInfo.guideId)
+            if (guide) {
+              this.selectGuide(guide)
+            }
+          }
+        }
+      } catch (e) {
+        console.error('获取指导师列表失败', e)
+      }
+    },
+
+    async loadChildren() {
+      try {
+        const res = await getChildren()
+        if (res.code === 200) {
+          this.children = res.data || []
+          // 如果只有一个孩子,自动选择
+          if (this.children.length === 1) {
+            this.selectedChildId = this.children[0].id
+          }
+        }
+      } catch (e) {
+        console.error('获取孩子列表失败', e)
+      }
+    },
+
+    selectGuide(guide) {
+      this.selectedGuideId = guide.guideId
+      this.selectedGuideName = guide.guideName
+      this.calculatePrice()
+    },
+
+    selectType(type) {
+      this.selectedType = type
+    },
+
+    onDateChange(e) {
+      this.appointmentDate = e.detail.value
+    },
+
+    onTimeChange(e) {
+      this.appointmentTime = e.detail.value
+    },
+
+    selectChild(child) {
+      this.selectedChildId = child.id
+    },
+
+    calculatePrice() {
+      if (!this.selectedGuideId) {
+        this.totalPrice = 0
+        return
+      }
+
+      const guide = this.availableGuides.find(g => g.guideId === this.selectedGuideId)
+      if (guide) {
+        this.totalPrice = guide.price
+        this.discountAmount = 0
+      }
+    },
+
+    async validateInviteCode() {
+      if (!this.inviteCode) {
+        return
+      }
+
+      try {
+        const res = await validateInviteCode(this.inviteCode)
+        if (res.code === 200) {
+          this.inviteCodeInfo = res.data
+          // 自动选择邀请码关联的指导师
+          if (this.inviteCodeInfo.guideId) {
+            const guide = this.availableGuides.find(g => g.guideId === this.inviteCodeInfo.guideId)
+            if (guide) {
+              this.selectGuide(guide)
+            }
+          }
+        }
+      } catch (e) {
+        console.error('验证邀请码失败', e)
+      }
+    },
+
+    async submit() {
+      // 验证表单
+      if (!this.selectedGuideId) {
+        uni.showToast({ title: '请选择指导师', icon: 'none' })
+        return
+      }
+
+      if (!this.selectedType) {
+        uni.showToast({ title: '请选择测评类型', icon: 'none' })
+        return
+      }
+
+      if (!this.appointmentDate) {
+        uni.showToast({ title: '请选择预约日期', icon: 'none' })
+        return
+      }
+
+      if (!this.appointmentTime) {
+        uni.showToast({ title: '请选择预约时间', icon: 'none' })
+        return
+      }
+
+      if (!this.selectedChildId) {
+        uni.showToast({ title: '请选择孩子', icon: 'none' })
+        return
+      }
+
+      try {
+        uni.showLoading({ title: '提交中...' })
+
+        const userId = uni.getStorageSync('userId')
+        const familyId = uni.getStorageSync('familyId')
+
+        const res = await createAssessmentAppointment({
+          userId: userId,
+          familyId: familyId,
+          childId: this.selectedChildId,
+          teacherId: this.selectedGuideId,
+          appointmentType: this.selectedType,
+          appointmentDate: this.appointmentDate,
+          appointmentTime: this.appointmentTime,
+          durationMinutes: 60
+        })
+
+        uni.hideLoading()
+
+        if (res.code === 200) {
+          uni.showModal({
+            title: '申请成功',
+            content: '您的测评申请已提交,请等待指导师确认',
+            showCancel: false,
+            success: () => {
+              uni.navigateBack()
+            }
+          })
+        } else {
+          uni.showToast({
+            title: res.message || '提交失败',
+            icon: 'none'
+          })
+        }
+      } catch (e) {
+        uni.hideLoading()
+        uni.showToast({
+          title: '提交失败',
+          icon: 'none'
+        })
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  padding: 30rpx;
+  background: #F8F8F8;
+  min-height: 100vh;
+}
+
+.header {
+  text-align: center;
+  padding: 60rpx 0 40rpx;
+}
+
+.title {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.subtitle {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.section {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 30rpx;
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+}
+
+.guide-selector,
+.type-selector,
+.child-selector {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20rpx;
+}
+
+.guide-item,
+.type-item,
+.child-item {
+  position: relative;
+  background: #F8F8F8;
+  border: 2rpx solid #E0E0E0;
+  border-radius: 10rpx;
+  padding: 20rpx;
+  min-width: 200rpx;
+  flex: 1;
+}
+
+.guide-item.selected,
+.type-item.selected,
+.child-item.selected {
+  border-color: #667eea;
+  background: #F0F4FF;
+}
+
+.guide-avatar {
+  width: 60rpx;
+  height: 60rpx;
+  border-radius: 50%;
+  object-fit: cover;
+  margin-bottom: 10rpx;
+}
+
+.guide-info,
+.child-info {
+  display: flex;
+  flex-direction: column;
+}
+
+.guide-name,
+.child-name {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 5rpx;
+}
+
+.guide-price {
+  font-size: 24rpx;
+  color: #FF6B6B;
+  font-weight: bold;
+}
+
+.child-age {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.guide-check,
+.child-check {
+  position: absolute;
+  top: 10rpx;
+  right: 10rpx;
+  width: 40rpx;
+  height: 40rpx;
+  background: #667eea;
+  color: #fff;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 24rpx;
+}
+
+.type-icon {
+  font-size: 48rpx;
+  display: block;
+  margin-bottom: 10rpx;
+}
+
+.type-name {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 5rpx;
+}
+
+.type-desc {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.child-avatar {
+  width: 60rpx;
+  height: 60rpx;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #667eea, #764ba2);
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 28rpx;
+  font-weight: bold;
+  margin-right: 20rpx;
+}
+
+.date-picker,
+.time-picker {
+  margin-bottom: 20rpx;
+}
+
+.picker-value {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  height: 80rpx;
+  background: #F8F8F8;
+  border-radius: 10rpx;
+  padding: 0 20rpx;
+  font-size: 28rpx;
+  color: #333;
+}
+
+.picker-arrow {
+  font-size: 40rpx;
+  color: #999;
+}
+
+.price-info {
+  background: #F8F8F8;
+  border-radius: 10rpx;
+  padding: 20rpx;
+}
+
+.price-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 15rpx;
+}
+
+.price-row:last-child {
+  margin-bottom: 0;
+}
+
+.price-label {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.price-value {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.price-value.discount {
+  color: #FF6B6B;
+}
+
+.btn-submit {
+  width: 100%;
+  height: 90rpx;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+  border-radius: 45rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+  margin-top: 40rpx;
+}
+</style>

+ 537 - 0
zxyj-frontend/pages/assessment/purchase.vue

@@ -0,0 +1,537 @@
+<template>
+  <view class="container">
+    <view class="header">
+      <text class="title">选择测评套餐</text>
+      <text class="subtitle">选择适合孩子的测评套餐</text>
+    </view>
+
+    <!-- 指导师选择 -->
+    <view class="section" v-if="!guideId">
+      <view class="section-title">选择指导师</view>
+      <view class="guide-selector">
+        <view 
+          class="guide-item" 
+          :class="{ 'selected': selectedGuideId === guide.guideId }"
+          v-for="guide in availableGuides" 
+          :key="guide.guideId"
+          @click="selectGuide(guide)"
+        >
+          <image class="guide-avatar" :src="guide.guideAvatar || '/static/default-avatar.png'" />
+          <view class="guide-info">
+            <text class="guide-name">{{ guide.guideName }}</text>
+            <text class="guide-price">¥{{ guide.price }}</text>
+          </view>
+          <view class="guide-check" v-if="selectedGuideId === guide.guideId">✓</view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 套餐列表 -->
+    <view class="section">
+      <view class="section-title">测评套餐</view>
+      <view class="package-list">
+        <view 
+          class="package-item" 
+          :class="{ 'selected': selectedPackageId === package.id }"
+          v-for="package in packages" 
+          :key="package.id"
+          @click="selectPackage(package)"
+        >
+          <view class="package-header">
+            <text class="package-name">{{ package.name }}</text>
+            <view class="package-price">
+              <text class="price">¥{{ package.price }}</text>
+              <text class="original-price" v-if="package.originalPrice">¥{{ package.originalPrice }}</text>
+            </view>
+          </view>
+          <view class="package-desc">{{ package.description }}</view>
+          <view class="package-features">
+            <view class="feature-item" v-for="(feature, index) in package.features" :key="index">
+              <text class="feature-icon">✓</text>
+              <text class="feature-text">{{ feature }}</text>
+            </view>
+          </view>
+          <view class="package-check" v-if="selectedPackageId === package.id">✓</view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 价格信息 -->
+    <view class="section" v-if="selectedPackageId">
+      <view class="section-title">费用明细</view>
+      <view class="price-breakdown">
+        <view class="price-row">
+          <text class="price-label">套餐价格</text>
+          <text class="price-value">¥{{ selectedPackage.price }}</text>
+        </view>
+        <view class="price-row" v-if="selectedGuideId">
+          <text class="price-label">指导师费用</text>
+          <text class="price-value">¥{{ guidePrice }}</text>
+        </view>
+        <view class="price-row" v-if="discountAmount > 0">
+          <text class="price-label">邀请码优惠</text>
+          <text class="price-value discount">-¥{{ discountAmount }}</text>
+        </view>
+        <view class="price-row total">
+          <text class="price-label">总计</text>
+          <text class="price-value">¥{{ totalPrice }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 提交按钮 -->
+    <button class="btn-submit" @click="submit" :disabled="!selectedPackageId">确认购买</button>
+  </view>
+</template>
+
+<script>
+import { getAvailableGuides, getPackagePrice, createOrder } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      guideId: null,
+      selectedGuideId: null,
+      selectedGuideName: '',
+      selectedPackageId: null,
+      selectedPackage: null,
+      availableGuides: [],
+      packages: [],
+      totalPrice: 0,
+      guidePrice: 0,
+      discountAmount: 0,
+      inviteCode: '',
+      inviteCodeInfo: null
+    }
+  },
+  onLoad(options) {
+    // 如果有邀请码参数,自动填充
+    if (options.inviteCode) {
+      this.inviteCode = options.inviteCode
+      this.validateInviteCode()
+    }
+
+    // 如果有指导师ID参数,自动选择
+    if (options.guideId) {
+      this.guideId = options.guideId
+    }
+
+    this.loadPackages()
+    this.loadAvailableGuides()
+  },
+  methods: {
+    async loadPackages() {
+      try {
+        // TODO: 从后端获取套餐列表
+        // 这里简化处理,使用模拟数据
+        this.packages = [
+          {
+            id: 1,
+            name: '基础测评套餐',
+            description: '包含注意力、专注力、记忆力三项基础测评',
+            price: 299,
+            originalPrice: 399,
+            features: ['注意力测评', '专注力测评', '记忆力测评', '测评报告', '成长建议']
+          },
+          {
+            id: 2,
+            name: '全面测评套餐',
+            description: '包含五项全面测评,提供详细分析报告',
+            price: 499,
+            originalPrice: 699,
+            features: ['注意力测评', '专注力测评', '记忆力测评', '逻辑思维测评', '综合分析报告', '个性化成长建议', '后续跟踪']
+          },
+          {
+            id: 3,
+            name: '高级测评套餐',
+            name: '高级测评套餐',
+            description: '包含所有测评项目,提供专家一对一解读',
+            price: 799,
+            originalPrice: 999,
+            features: ['所有测评项目', '专家一对一解读', '详细分析报告', '个性化成长方案', '后续跟踪服务', '家长指导建议']
+          }
+        ]
+      } catch (e) {
+        console.error('获取套餐列表失败', e)
+      }
+    },
+
+    async loadAvailableGuides() {
+      try {
+        const packageId = this.selectedPackageId ? this.selectedPackageId : 1
+        const res = await getAvailableGuides(packageId)
+        if (res.code === 200) {
+          this.availableGuides = res.data || []
+          
+          // 如果有邀请码,自动选择对应的指导师
+          if (this.inviteCodeInfo && this.inviteCodeInfo.guideId) {
+            const guide = this.availableGuides.find(g => g.guideId === this.inviteCodeInfo.guideId)
+            if (guide) {
+              this.selectGuide(guide)
+            }
+          } else if (this.guideId) {
+            // 如果有指导师ID参数,自动选择
+            const guide = this.availableGuides.find(g => g.guideId === this.guideId)
+            if (guide) {
+              this.selectGuide(guide)
+            }
+          }
+        }
+      } catch (e) {
+        console.error('获取指导师列表失败', e)
+      }
+    },
+
+    selectGuide(guide) {
+      this.selectedGuideId = guide.guideId
+      this.selectedGuideName = guide.guideName
+      this.guidePrice = guide.price
+      this.calculatePrice()
+    },
+
+    selectPackage(package) {
+      this.selectedPackageId = package.id
+      this.selectedPackage = package
+      this.calculatePrice()
+    },
+
+    calculatePrice() {
+      if (!this.selectedPackage) {
+        this.totalPrice = 0
+        return
+      }
+
+      this.totalPrice = this.selectedPackage.price
+      this.guidePrice = 0
+      this.discountAmount = 0
+
+      if (this.selectedGuideId) {
+        const guide = this.availableGuides.find(g => g.guideId === this.selectedGuideId)
+        if (guide) {
+          this.guidePrice = guide.price
+          this.totalPrice += guide.price
+        }
+      }
+
+      if (this.inviteCodeInfo && this.inviteCodeInfo.discountAmount) {
+        this.discountAmount = this.inviteCodeInfo.discountAmount
+        this.totalPrice -= this.discountAmount
+      }
+    },
+
+    async validateInviteCode() {
+      if (!this.inviteCode) {
+        return
+      }
+
+      try {
+        const res = await validateInviteCode(this.inviteCode)
+        if (res.code === 200) {
+          this.inviteCodeInfo = res.data
+          // 自动选择邀请码关联的指导师
+          if (this.inviteCodeInfo.guideId) {
+            const guide = this.availableGuides.find(g => g.guideId === this.inviteCodeInfo.guideId)
+            if (guide) {
+              this.selectGuide(guide)
+            }
+          }
+        }
+      } catch (e) {
+        console.error('验证邀请码失败', e)
+      }
+    },
+
+    async submit() {
+      if (!this.selectedPackageId) {
+        uni.showToast({ title: '请选择套餐', icon: 'none' })
+        return
+      }
+
+      try {
+        uni.showLoading({ title: '创建订单中...' })
+
+        const userId = uni.getStorageSync('userId')
+        const familyId = uni.getStorageSync('familyId')
+
+        const res = await createOrder({
+          userId: userId,
+          familyId: familyId,
+          packageId: this.selectedPackageId,
+          guideId: this.selectedGuideId,
+          inviteCode: this.inviteCode,
+          price: this.totalPrice,
+          payMethod: 'wechat'
+        })
+
+        uni.hideLoading()
+
+        if (res.code === 200) {
+          // 跳转到支付页面
+          uni.navigateTo({
+            url: `/pages/assessment/purchase?orderNo=${res.data.orderNo}&totalPrice=${this.totalPrice}`
+          })
+        } else {
+          uni.showToast({
+            title: res.message || '创建订单失败',
+            icon: 'none'
+          })
+        }
+      } catch (e) {
+        uni.hideLoading()
+        uni.showToast({
+          title: '创建订单失败',
+          icon: 'none'
+        })
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  padding: 30rpx;
+  background: #F8F8F8;
+  min-height: 100vh;
+}
+
+.header {
+  text-align: center;
+  padding: 60rpx 0 40rpx;
+}
+
+.title {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.subtitle {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.section {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 30rpx;
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+}
+
+.guide-selector,
+.package-list {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+
+.guide-item {
+  position: relative;
+  display: flex;
+  align-items: center;
+  background: #F8F8F8;
+  border: 2rpx solid #E0E0E0;
+  border-radius: 10rpx;
+  padding: 20rpx;
+}
+
+.guide-item.selected {
+  border-color: #667eea;
+  background: #F0F4FF;
+}
+
+.guide-avatar {
+  width: 60rpx;
+  height: 60rpx;
+  border-radius: 50%;
+  object-fit: cover;
+  margin-right: 20rpx;
+}
+
+.guide-info {
+  flex: 1;
+}
+
+.guide-name {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 5rpx;
+}
+
+.guide-price {
+  font-size: 24rpx;
+  color: #FF6B6B;
+  font-weight: bold;
+}
+
+.guide-check {
+  width: 40rpx;
+  height: 40rpx;
+  background: #667eea;
+  color: #fff;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 24rpx;
+}
+
+.package-item {
+  position: relative;
+  background: #F8F8F8;
+  border: 2rpx solid #E0E0E0;
+  border-radius: 10rpx;
+  padding: 20rpx;
+}
+
+.package-item.selected {
+  border-color: #667eea;
+  background: #F0F4FF;
+}
+
+.package-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 15rpx;
+}
+
+.package-name {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.package-price {
+  text-align: right;
+}
+
+.price {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #FF6B6B;
+  display: block;
+}
+
+.original-price {
+  font-size: 24rpx;
+  color: #999;
+  text-decoration: line-through;
+  margin-left: 10rpx;
+}
+
+.package-desc {
+  font-size: 26rpx;
+  color: #666;
+  margin-bottom: 15rpx;
+}
+
+.package-features {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10rpx;
+}
+
+.feature-item {
+  display: flex;
+  align-items: center;
+  background: #E8F5E9;
+  padding: 8rpx 12rpx;
+  border-radius: 20rpx;
+}
+
+.feature-icon {
+  color: #4CAF50;
+  font-size: 24rpx;
+  margin-right: 8rpx;
+}
+
+.feature-text {
+  font-size: 24rpx;
+  color: #333;
+}
+
+.package-check {
+  position: absolute;
+  top: 20rpx;
+  right: 20rpx;
+  width: 40rpx;
+  height: 40rpx;
+  background: #667eea;
+  color: #fff;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 24rpx;
+}
+
+.price-breakdown {
+  background: #F8F8F8;
+  border-radius: 10rpx;
+  padding: 20rpx;
+}
+
+.price-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 15rpx;
+}
+
+.price-row:last-child {
+  margin-bottom: 0;
+}
+
+.price-label {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.price-value {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.price-value.discount {
+  color: #FF6B6B;
+}
+
+.price-row.total .price-label {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.price-row.total .price-value {
+  font-size: 36rpx;
+  color: #FF6B6B;
+}
+
+.btn-submit {
+  width: 100%;
+  height: 90rpx;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+  border-radius: 45rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+  margin-top: 40rpx;
+}
+
+.btn-submit[disabled] {
+  background: #CCCCCC;
+}
+</style>

+ 489 - 0
zxyj-frontend/pages/assessment/results.vue

@@ -0,0 +1,489 @@
+<template>
+  <view class="container">
+    <view class="header">
+      <text class="title">测评报告</text>
+      <text class="subtitle">查看下级指导师的测评报告</text>
+    </view>
+
+    <!-- 标签页 -->
+    <view class="tabs">
+      <view 
+        class="tab-item" 
+        :class="{ 'active': currentTab === 'my' }"
+        @click="switchTab('my')"
+      >
+        <text class="tab-text">我的报告</text>
+      </view>
+      <view 
+        class="tab-item" 
+        :class="{ 'active': currentTab === 'child' }"
+        @click="switchTab('child')"
+      >
+        <text class="tab-text">下级报告</text>
+      </view>
+    </view>
+
+    <!-- 我的报告列表 -->
+    <view class="section" v-if="currentTab === 'my'">
+      <view class="section-title">我的测评报告</view>
+      <view class="report-list" v-if="myReports.length > 0">
+        <view 
+          class="report-item" 
+          v-for="report in myReports" 
+          :key="report.id"
+          @click="viewReportDetail(report)"
+        >
+          <view class="report-header">
+            <text class="report-child">{{ report.childName || '未命名' }}</text>
+            <text class="report-date">{{ formatDate(report.assessmentDate) }}</text>
+          </view>
+          <view class="report-scores">
+            <view class="score-item">
+              <text class="score-label">DAN等级</text>
+              <text class="score-value">{{ report.danLevel }}</text>
+            </view>
+            <view class="score-item">
+              <text class="score-label">综合得分</text>
+              <text class="score-value">{{ report.overallScore }}</text>
+            </view>
+          </view>
+          <view class="report-preview">
+            <text class="preview-text">{{ report.analysisReport?.substring(0, 50) }}...</text>
+          </view>
+        </view>
+      </view>
+      <view class="empty-state" v-else>
+        <text class="empty-icon">📊</text>
+        <text class="empty-text">还没有测评报告</text>
+        <text class="empty-hint">完成测评后会生成报告</text>
+      </view>
+    </view>
+
+    <!-- 下级报告列表 -->
+    <view class="section" v-if="currentTab === 'child'">
+      <view class="section-title">下级指导师的报告</view>
+      <view class="report-list" v-if="childReports.length > 0">
+        <view 
+          class="report-item" 
+          v-for="report in childReports" 
+          :key="report.id"
+          @click="viewReportDetail(report)"
+        >
+          <view class="report-header">
+            <text class="report-child">{{ report.childName || '未命名' }}</text>
+            <text class="report-date">{{ formatDate(report.assessmentDate) }}</text>
+            <text class="report-guide">指导师: {{ report.guideName || '未知' }}</text>
+          </view>
+          <view class="report-scores">
+            <view class="score-item">
+              <text class="score-label">DAN等级</text>
+              <text class="score-value">{{ report.danLevel }}</text>
+            </view>
+            <view class="score-item">
+              <text class="score-label">综合得分</text>
+              <score-value">{{ report.overallScore }}</text>
+            </view>
+          </view>
+          <view class="report-preview">
+            <text class="preview-text">{{ report.analysisReport?.substring(0, 50) }}...</text>
+          </view>
+        </view>
+      </view>
+      <view class="empty-state" v-else>
+        <text class="empty-icon">📊</text>
+        <text class="empty-text">还没有下级指导师的报告</text>
+        <text class="empty-hint">下级指导师完成测评后会显示</text>
+      </view>
+    </view>
+
+    <!-- 报告详情弹窗 -->
+    <view class="report-modal" v-if="showReportModal" @click="showReportModal = false">
+      <view class="modal-content" @click.stop>
+        <view class="modal-header">
+          <text class="modal-title">测评报告详情</text>
+          <text class="modal-close" @click="showReportModal = false">✕</text>
+        </view>
+        
+        <view class="report-detail">
+          <view class="detail-section">
+            <text class="detail-title">基本信息</text>
+            <view class="detail-row">
+              <text class="detail-label">孩子姓名</text>
+              <text class="detail-value">{{ currentReport.childName || '未命名' }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">测评日期</text>
+              <text class="detail-value">{{ formatDate(currentReport.assessmentDate) }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">测评时长</text>
+              <text class="detail-value">{{ currentReport.durationMinutes }}分钟</text>
+            </view>
+          </view>
+
+          <view class="detail-section">
+            <text class="detail-title">DAN测评结果</text>
+            <view class="detail-row">
+              <text class="detail-label">DAN等级</text>
+              <text class="detail-value">{{ currentReport.danLevel }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">注意力得分</text>
+              <text class="detail-value">{{ currentReport.attentionScore }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">专注力得分</text>
+              <text class="detail-value">{{ currentReport.focusScore }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">记忆力得分</text>
+              <text class="detail-value">{{ currentReport.memoryScore }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">逻辑思维得分</text>
+              <text class="detail-value">{{ currentReport.logicScore }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">综合得分</text>
+              <text class="detail-value">{{ currentReport.overallScore }}</text>
+            </view>
+          </view>
+
+          <view class="detail-section">
+            <text class="detail-title">分析报告</text>
+            <view class="detail-content">
+              <text class="content-text">{{ currentReport.analysisReport }}</text>
+            </view>
+          </view>
+
+          <view class="detail-section">
+            <text class="detail-title">成长建议</text>
+            <view class="detail-content">
+              <text class="content-text">{{ currentReport.growthSuggestions }}</text>
+            </view>
+          </view>
+
+          <view class="detail-section" v-if="currentReport.nextReviewDate">
+            <text class="detail-title">下次复测日期</text>
+            <view class="detail-row">
+              <text class="detail-label">建议复测时间</text>
+              <text class="detail-value">{{ formatDate(currentReport.nextReviewDate) }}</text>
+            </view>
+          </view>
+        </view>
+
+        <view class="modal-actions">
+          <button class="btn-close" @click="showReportModal = false">关闭</button>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getMyAssessmentResults, getChildAssessmentResults } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      currentTab: 'my',
+      myReports: [],
+      childReports: [],
+      currentReport: null,
+      showReportModal: false
+    }
+  },
+  onLoad() {
+    this.loadData()
+  },
+  methods: {
+    async loadData() {
+      try {
+        const userId = uni.getStorageSync('userId')
+        
+        // 并行获取数据
+        const [myRes, childRes] = await Promise.all([
+          getMyAssessmentResults(userId),
+          getChildAssessmentResults(userId)
+        ])
+
+        if (myRes.code === 200) {
+          this.myReports = myRes.data || []
+        }
+
+        if (childRes.code === 200) {
+          this.childReports = childRes.data || []
+        }
+      } catch (e) {
+        console.error('加载数据失败', e)
+      }
+    },
+
+    switchTab(tab) {
+      this.currentTab = tab
+    },
+
+    viewReportDetail(report) {
+      this.currentReport = report
+      this.showReportModal = true
+    },
+
+    formatDate(dateStr) {
+      if (!dateStr) return ''
+      const date = new Date(dateStr)
+      return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  padding: 30rpx;
+  background: #F8F8F8;
+  min-height: 100vh;
+}
+
+.header {
+  text-align: center;
+  padding: 60rpx 0 40rpx;
+}
+
+.title {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.subtitle {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.tabs {
+  display: flex;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 10rpx;
+  margin-bottom: 30rpx;
+}
+
+.tab-item {
+  flex: 1;
+  text-align: center;
+  padding: 20rpx;
+  border-radius: 10rpx;
+  font-size: 28rpx;
+  color: #666;
+}
+
+.tab-item.active {
+  background: #667eea;
+  color: #fff;
+}
+
+.section {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 30rpx;
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+}
+
+.report-list {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+
+.report-item {
+  background: #F8F8F8;
+  border-radius: 10rpx;
+  padding: 20rpx;
+}
+
+.report-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 15rpx;
+}
+
+.report-child {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.report-date {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.report-guide {
+  font-size: 24rpx;
+  color: #667eea;
+}
+
+.report-scores {
+  display: flex;
+  gap: 20rpx;
+  margin-bottom: 15rpx;
+}
+
+.score-item {
+  background: #E8F5E9;
+  padding: 10rpx 15rpx;
+  border-radius: 10rpx;
+}
+
+.score-label {
+  font-size: 24rpx;
+  color: #666;
+  display: block;
+  margin-bottom: 5rpx;
+}
+
+.score-value {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.report-preview {
+  font-size: 26rpx;
+  color: #999;
+  line-height: 1.5;
+}
+
+.empty-state {
+  text-align: center;
+  padding: 60rpx 0;
+}
+
+.empty-icon {
+  font-size: 80rpx;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.empty-text {
+  font-size: 28rpx;
+  color: #999;
+  display: block;
+  margin-bottom: 10rpx;
+}
+
+.empty-hint {
+  font-size: 24rpx;
+  color: #CCC;
+}
+
+.report-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 999;
+}
+
+.modal-content {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 40rpx;
+  width: 90%;
+  max-height: 80vh;
+  overflow-y: auto;
+}
+
+.modal-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 30rpx;
+}
+
+.modal-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.modal-close {
+  font-size: 40rpx;
+  color: #999;
+  padding: 10rpx;
+}
+
+.report-detail {
+  margin-bottom: 30rpx;
+}
+
+.detail-section {
+  margin-bottom: 30rpx;
+}
+
+.detail-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 15rpx;
+}
+
+.detail-row {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 10rpx;
+}
+
+.detail-label {
+  font-size: 26rpx;
+  color: #666;
+}
+
+.detail-value {
+  font-size: 26rpx;
+  color: #333;
+  font-weight: bold;
+}
+
+.detail-content {
+  background: #F8F8F8;
+  padding: 20rpx;
+  border-radius: 10rpx;
+}
+
+.content-text {
+  font-size: 26rpx;
+  color: #333;
+  line-height: 1.6;
+}
+
+.modal-actions {
+  display: flex;
+  gap: 20rpx;
+}
+
+.btn-close {
+  flex: 1;
+  height: 80rpx;
+  background: #F0F0F0;
+  color: #333;
+  border-radius: 40rpx;
+  font-size: 28rpx;
+  font-weight: bold;
+}
+</style>

+ 295 - 0
zxyj-frontend/pages/guide/hierarchy/index.vue

@@ -0,0 +1,295 @@
+<template>
+  <view class="container">
+    <view class="header">
+      <text class="title">我的下级指导师</text>
+      <text class="subtitle">查看和管理下级指导师</text>
+    </view>
+
+    <!-- 下级指导师列表 -->
+    <view class="section">
+      <view class="section-title">直接下级</view>
+      <view class="guide-list" v-if="childGuides.length > 0">
+        <view 
+          class="guide-item" 
+          v-for="guide in childGuides" 
+          :key="guide.id"
+          @click="viewGuideDetail(guide)"
+        >
+          <image class="guide-avatar" :src="guide.avatar || '/static/default-avatar.png'" />
+          <view class="guide-info">
+            <text class="guide-name">{{ guide.nickname || '未命名' }}</text>
+            <text class="guide-stats">服务家庭: {{ guide.familyCount || 0 }}</text>
+            <text class="guide-stats">测评次数: {{ guide.assessmentCount || 0 }}</text>
+          </view>
+          <view class="guide-actions">
+            <text class="action-btn" @click.stop="viewReports(guide)">查看报告</text>
+          </view>
+        </view>
+      </view>
+      <view class="empty-state" v-else>
+        <text class="empty-icon">👥</text>
+        <text class="empty-text">还没有下级指导师</text>
+        <text class="empty-hint">分享邀请码邀请其他指导师加入</text>
+      </view>
+    </view>
+
+    <!-- 所有下级指导师 -->
+    <view class="section" v-if="allChildGuides.length > 0">
+      <view class="section-title">所有下级(含间接)</view>
+      <view class="guide-list">
+        <view 
+          class="guide-item" 
+          v-for="guide in allChildGuides" 
+          :key="guide.id"
+          @click="viewGuideDetail(guide)"
+        >
+          <image class="guide-avatar" :src="guide.avatar || '/static/default-avatar.png'" />
+          <view class="guide-info">
+            <text class="guide-name">{{ guide.nickname || '未命名' }}</text>
+            <text class="guide-stats">服务家庭: {{ guide.familyCount || 0 }}</text>
+            <text class="guide-stats">测评次数: {{ guide.assessmentCount || 0 }}</text>
+          </view>
+          <view class="guide-actions">
+            <text class="action-btn" @click.stop="viewReports(guide)">查看报告</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 上级指导师 -->
+    <view class="section" v-if="parentGuide">
+      <view class="section-title">我的上级指导师</view>
+      <view class="parent-guide-card">
+        <image class="guide-avatar" :src="parentGuide.avatar || '/static/default-avatar.png'" />
+        <view class="guide-info">
+          <text class="guide-name">{{ parentGuide.nickname || '未命名' }}</text>
+          <text class="guide-desc">我的上级指导师</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 邀请码分享 -->
+    <view class="section">
+      <view class="section-title">邀请下级指导师</view>
+      <button class="btn-share" @click="goToShare">
+        <text class="btn-icon">📤</text>
+        <text class="btn-text">分享邀请码</text>
+      </button>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getChildGuides, getAllChildGuides, getParentGuide } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      childGuides: [],
+      allChildGuides: [],
+      parentGuide: null
+    }
+  },
+  onLoad() {
+    this.loadData()
+  },
+  onShow() {
+    this.loadData()
+  },
+  methods: {
+    async loadData() {
+      try {
+        const userId = uni.getStorageSync('userId')
+        
+        // 并行获取数据
+        const [childRes, allChildRes, parentRes] = await Promise.all([
+          getChildGuides(userId),
+          getAllChildGuides(userId),
+          getParentGuide(userId)
+        ])
+
+        if (childRes.code === 200) {
+          this.childGuides = childRes.data || []
+        }
+
+        if (allChildRes.code === 200) {
+          this.allChildGuides = allChildRes.data || []
+        }
+
+        if (parentRes.code === 200) {
+          this.parentGuide = parentRes.data
+        }
+      } catch (e) {
+        console.error('加载数据失败', e)
+      }
+    },
+
+    viewGuideDetail(guide) {
+      uni.showModal({
+        title: '指导师详情',
+        content: `姓名: ${guide.nickname || '未命名'}\n服务家庭: ${guide.familyCount || 0}\n测评次数: ${guide.assessmentCount || 0}`,
+        showCancel: false
+      })
+    },
+
+    viewReports(guide) {
+      uni.navigateTo({
+        url: `/pages/assessment/results?guideId=${guide.id}`
+      })
+    },
+
+    goToShare() {
+      uni.navigateTo({
+        url: '/pages/guide/invite/share'
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  padding: 30rpx;
+  background: #F8F8F8;
+  min-height: 100vh;
+}
+
+.header {
+  text-align: center;
+  padding: 60rpx 0 40rpx;
+}
+
+.title {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.subtitle {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.section {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 30rpx;
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+}
+
+.guide-list {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+
+.guide-item {
+  display: flex;
+  align-items: center;
+  background: #F8F8F8;
+  border-radius: 10rpx;
+  padding: 20rpx;
+}
+
+.guide-avatar {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  object-fit: cover;
+  margin-right: 20rpx;
+}
+
+.guide-info {
+  flex: 1;
+}
+
+.guide-name {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 8rpx;
+}
+
+.guide-stats {
+  font-size: 24rpx;
+  color: #999;
+  display: block;
+  margin-bottom: 4rpx;
+}
+
+.guide-actions {
+  display: flex;
+  gap: 10rpx;
+}
+
+.action-btn {
+  font-size: 24rpx;
+  color: #667eea;
+  padding: 8rpx 16rpx;
+  background: #F0F4FF;
+  border-radius: 20rpx;
+}
+
+.empty-state {
+  text-align: center;
+  padding: 60rpx 0;
+}
+
+.empty-icon {
+  font-size: 80rpx;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.empty-text {
+  font-size: 28rpx;
+  color: #999;
+  display: block;
+  margin-bottom: 10rpx;
+}
+
+.empty-hint {
+  font-size: 24rpx;
+  color: #CCC;
+}
+
+.parent-guide-card {
+  display: flex;
+  align-items: center;
+  background: #F0F4FF;
+  border-radius: 10rpx;
+  padding: 20rpx;
+}
+
+.btn-share {
+  width: 100%;
+  height: 80rpx;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+  border-radius: 40rpx;
+  font-size: 28rpx;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 10rpx;
+}
+
+.btn-icon {
+  font-size: 32rpx;
+}
+
+.btn-text {
+  font-size: 28rpx;
+}
+</style>

+ 548 - 0
zxyj-frontend/pages/guide/invite/share.vue

@@ -0,0 +1,548 @@
+<template>
+  <view class="container">
+    <view class="header">
+      <text class="title">邀请分享</text>
+      <text class="subtitle">邀请家长或新指导师加入</text>
+    </view>
+
+    <!-- 邀请码列表 -->
+    <view class="invite-codes-section">
+      <view class="section-title">我的邀请码</view>
+      <view class="invite-codes-list">
+        <view 
+          class="invite-code-item" 
+          v-for="code in inviteCodes" 
+          :key="code.id"
+        >
+          <view class="code-info">
+            <text class="code-text">{{ code.code }}</text>
+            <text class="code-status" :class="'status-' + code.status">
+              {{ getStatusText(code.status) }}
+            </text>
+          </view>
+          <view class="code-stats">
+            <text class="stat">已使用: {{ code.useCount }}/{{ code.maxUseCount }}</text>
+            <text class="stat" v-if="code.expireAt">过期: {{ formatDate(code.expireAt) }}</text>
+          </view>
+        </view>
+      </view>
+      <button class="btn-generate" @click="generateInviteCode">生成新邀请码</button>
+    </view>
+
+    <!-- 分享方式 -->
+    <view class="share-section">
+      <view class="section-title">分享方式</view>
+      
+      <!-- 复制链接 -->
+      <view class="share-item" @click="copyInviteLink">
+        <view class="share-icon">🔗</view>
+        <view class="share-content">
+          <text class="share-title">复制邀请链接</text>
+          <text class="share-desc">分享链接给家长或新指导师</text>
+        </view>
+        <text class="share-arrow">›</text>
+      </view>
+
+      <!-- 生成二维码 -->
+      <view class="share-item" @click="showQrcode">
+        <view class="share-icon">📱</view>
+        <view class="share-content">
+          <text class="share-title">生成二维码</text>
+          <text class="share-desc">扫码即可加入</text>
+        </view>
+        <text class="share-arrow">›</text>
+      </view>
+
+      <!-- 转发给朋友 -->
+      <view class="share-item" @click="shareToFriend">
+        <view class="share-icon">📤</view>
+        <view class="share-content">
+          <text class="share-title">转发给朋友</text>
+          <text class="share-desc">直接分享到微信</text>
+        </view>
+        <text class="share-arrow">›</text>
+      </view>
+    </view>
+
+    <!-- 使用说明 -->
+    <view class="tips-section">
+      <view class="section-title">使用说明</view>
+      <view class="tips-list">
+        <view class="tip-item">
+          <text class="tip-icon">💡</text>
+          <text class="tip-text">家长通过邀请码注册后,会自动关联到您</text>
+        </view>
+        <view class="tip-item">
+          <text class="tip-icon">💡</text>
+          <text class="tip-text">新指导师通过邀请码注册,会成为您的下级</text>
+        </view>
+        <view class="tip-item">
+          <text class="tip-icon">💡</text>
+          <text class="tip-text">您可以查看所有下级指导师的测评报告</text>
+        </view>
+        <view class="item">
+          <text class="tip-icon">💡</text>
+          <text class="tip-text">邀请码可以设置使用次数和过期时间</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 二维码弹窗 -->
+    <view class="qrcode-modal" v-if="showQrcodeModal" @click="showQrcodeModal = false">
+      <view class="modal-content" @click.stop>
+        <view class="modal-header">
+          <text class="modal-title">邀请二维码</text>
+          <text class="modal-close" @click="showQrcodeModal = false">✕</text>
+        </view>
+        <view class="qrcode-container">
+          <canvas canvas-id="qrcodeCanvas" class="qrcode-canvas"></canvas>
+        </view>
+        <view class="modal-actions">
+          <button class="btn-save" @click="saveQrcode">保存图片</button>
+          <button class="btn-close" @click="showQrcodeModal = false">关闭</button>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getInviteCodes, generateInviteCode } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      inviteCodes: [],
+      currentInviteCode: null,
+      showQrcodeModal: false
+    }
+  },
+  onLoad() {
+    this.loadInviteCodes()
+  },
+  onShow() {
+    this.loadInviteCodes()
+  },
+  methods: {
+    async loadInviteCodes() {
+      try {
+        const res = await getInviteCodes()
+        if (res.code === 200) {
+          this.inviteCodes = res.data || []
+          // 如果有可用的邀请码,使用第一个
+          const activeCode = this.inviteCodes.find(code => code.status === 'active')
+          if (activeCode) {
+            this.currentInviteCode = activeCode
+          }
+        }
+      } catch (e) {
+        console.error('获取邀请码失败', e)
+      }
+    },
+
+    async generateInviteCode() {
+      try {
+        uni.showLoading({ title: '生成中...' })
+        const res = await generateInviteCode()
+        uni.hideLoading()
+
+        if (res.code === 200) {
+          this.currentInviteCode = res.data
+          uni.showToast({
+            title: '生成成功',
+            icon: 'success'
+          })
+          this.loadInviteCodes()
+        } else {
+          uni.showToast({
+            title: res.message || '生成失败',
+            icon: 'none'
+          })
+        }
+      } catch (e) {
+        uni.hideLoading()
+        uni.showToast({
+          title: '生成失败',
+          icon: 'none'
+        })
+      }
+    },
+
+    copyInviteLink() {
+      if (!this.currentInviteCode) {
+        uni.showToast({
+          title: '请先生成邀请码',
+          icon: 'none'
+        })
+        return
+      }
+
+      const inviteUrl = `pages/guide/register/index?inviteCode=${this.currentInviteCode.code}`
+      
+      uni.setClipboardData({
+        data: inviteUrl,
+        success: () => {
+          uni.showToast({
+            title: '链接已复制',
+            icon: 'success'
+          })
+        }
+      })
+    },
+
+    showQrcode() {
+      if (!this.currentInviteCode) {
+        uni.showToast({
+          title: '请先生成邀请码',
+          icon: 'none'
+        })
+        return
+      }
+
+      this.showQrcodeModal = true
+      this.$nextTick(() => {
+        this.drawQrcode()
+      })
+    },
+
+    drawQrcode() {
+      // TODO: 使用二维码生成库生成二维码
+      // 这里简化处理,使用 Canvas 绘制占位符
+      const ctx = uni.createCanvasContext('qrcodeCanvas')
+      const canvasWidth = 300
+      const canvasHeight = 300
+
+      ctx.setFillStyle('#FFFFFF')
+      ctx.fillRect(0, 0, canvasWidth, canvasHeight)
+
+      ctx.setFillStyle('#333333')
+      ctx.font = '20px sans-serif'
+      ctx.textAlign = 'center'
+      ctx.fillText('邀请码', canvasWidth / 2, 100)
+
+      ctx.font = 'bold 40px sans-serif'
+      ctx.fillText(this.currentInviteCode.code, canvasWidth / 2, 150)
+
+      ctx.font = '16px sans-serif'
+      ctx.fillText('扫码成为指导师', canvasWidth / 2, 220)
+
+      ctx.draw()
+    },
+
+    saveQrcode() {
+      uni.canvasToTempFilePath({
+        canvasId: 'qrcodeCanvas',
+        success: (res) => {
+          uni.saveImageToPhotosAlbum({
+            filePath: res.tempFilePath,
+            success: () => {
+              uni.showToast({
+                title: '保存成功',
+                icon: 'success'
+              })
+            },
+            fail: () => {
+              uni.showToast({
+                title: '保存失败',
+                icon: 'none'
+              })
+            }
+          })
+        }
+      })
+    },
+
+    shareToFriend() {
+      if (!this.currentInviteCode) {
+        uni.showToast({
+          title: '请先生成邀请码',
+          icon: 'none'
+        })
+        return
+      }
+
+      const inviteUrl = `pages/guide/register/index?inviteCode=${this.currentInviteCode.code}`
+      
+      uni.share({
+        provider: 'weixin',
+        scene: {
+          type: 0,
+          title: '成为指导师',
+          path: inviteUrl
+        },
+        success: () => {
+          uni.showToast({
+            title: '分享成功',
+            icon: 'success'
+          })
+        },
+        fail: () => {
+          uni.showToast({
+            title: '分享失败',
+            icon: 'none'
+          })
+        }
+      })
+    },
+
+    getStatusText(status) {
+      const statusMap = {
+        'active': '可用',
+        'used': '已用完',
+        'expired': '已过期'
+      }
+      return statusMap[status] || status
+    },
+
+    formatDate(dateStr) {
+      if (!dateStr) return ''
+      const date = new Date(dateStr)
+      return `${date.getMonth() + 1}月${date.getDate()}日`
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  padding: 30rpx;
+  background: #F8F8F8;
+  min-height: 100vh;
+}
+
+.header {
+  text-align: center;
+  padding: 60rpx 0 40rpx;
+}
+
+.title {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.subtitle {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+}
+
+.invite-codes-section,
+.share-section,
+.tips-section {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 30rpx;
+}
+
+.invite-codes-list {
+  margin-bottom: 20rpx;
+}
+
+.invite-code-item {
+  background: #F8F8F8;
+  border-radius: 10rpx;
+  padding: 20rpx;
+  margin-bottom: 20rpx;
+}
+
+.code-info {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10rpx;
+}
+
+.code-text {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  font-family: monospace;
+}
+
+.code-status {
+  font-size: 24rpx;
+  padding: 4rpx 12rpx;
+  border-radius: 20rpx;
+}
+
+.status-active {
+  background: #E8F5E9;
+  color: #4CAF50;
+}
+
+.status-used {
+  background: #FFF3E0;
+  color: #FF9800;
+}
+
+.status-expired {
+  background: #FFEBEE;
+  color: #F44336;
+}
+
+.code-stats {
+  display: flex;
+  justify-content: space-between;
+  font-size: 24rpx;
+  color: #999;
+}
+
+.stat {
+  margin-right: 20rpx;
+}
+
+.btn-generate {
+  width: 100%;
+  height: 80rpx;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+  border-radius: 40rpx;
+  font-size: 28rpx;
+  font-weight: bold;
+}
+
+.share-item {
+  display: flex;
+  align-items: center;
+  padding: 30rpx 0;
+  border-bottom: 1rpx solid #F0F0F0;
+}
+
+.share-item:last-child {
+  border-bottom: none;
+}
+
+.share-icon {
+  font-size: 48rpx;
+  margin-right: 20rpx;
+}
+
+.share-content {
+  flex: 1;
+}
+
+.share-title {
+  font-size: 30rpx;
+  color: #333;
+  display: block;
+  margin-bottom: 8rpx;
+}
+
+.share-desc {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.share-arrow {
+  font-size: 40rpx;
+  color: #CCC;
+}
+
+.tips-list {
+  margin-top: 20rpx;
+}
+
+.tip-item {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 20rpx;
+}
+
+.tip-icon {
+  font-size: 32rpx;
+  margin-right: 12rpx;
+}
+
+.tip-text {
+  font-size: 26rpx;
+  color: #666;
+  line-height: 1.5;
+}
+
+.qrcode-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 999;
+}
+
+.modal-content {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 40rpx;
+  width: 80%;
+  max-width: 400rpx;
+}
+
+.modal-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 30rpx;
+}
+
+.modal-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.modal-close {
+  font-size: 40rpx;
+  color: #999;
+  padding: 10rpx;
+}
+
+.qrcode-container {
+  display: flex;
+  justify-content: center;
+  margin-bottom: 30rpx;
+}
+
+.qrcode-canvas {
+  width: 300rpx;
+  height: 300rpx;
+  border: 1rpx solid #E0E0E0;
+  border-radius: 10rpx;
+}
+
+.modal-actions {
+  display: flex;
+  gap: 20rpx;
+}
+
+.btn-save,
+.btn-close {
+  flex: 1;
+  height: 80rpx;
+  border-radius: 40rpx;
+  font-size: 28rpx;
+  font-weight: bold;
+}
+
+.btn-save {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+}
+
+.btn-close {
+  background: #F0F0F0;
+  color: #333;
+}
+</style>

+ 360 - 0
zxyj-frontend/pages/guide/register/index.vue

@@ -0,0 +1,360 @@
+<template>
+  <view class="container">
+    <view class="header">
+      <text class="title">成为指导师</text>
+      <text class="subtitle">分享专业知识,帮助更多家庭</text>
+    </view>
+
+    <view class="form">
+      <!-- 邀请码输入 -->
+      <view class="form-item">
+        <text class="label">邀请码(可选)</text>
+        <input 
+          class="input" 
+          v-model="formData.inviteCode" 
+          placeholder="输入邀请码可关联上级指导师"
+          @blur="validateInviteCode"
+        />
+        <text class="hint" v-if="inviteCodeInfo">关联指导师:{{ inviteCodeInfo.guideName }}</text>
+      </view>
+
+      <!-- 真实姓名 -->
+      <view class="form-item">
+        <text class="label">真实姓名</text>
+        <input 
+          class="input" 
+          v-model="formData.realName" 
+          placeholder="请输入真实姓名"
+        />
+      </view>
+
+      <!-- 手机号 -->
+      <view class="form-item">
+        <text class="label">手机号</text>
+        <input 
+          class="input" 
+          v-model="formData.phone" 
+          type="number"
+          placeholder="请输入手机号"
+          maxlength="11"
+        />
+      </view>
+
+      <!-- 身份证号 -->
+      <view class="form-item">
+        <text class="label">身份证号</text>
+        <input 
+          class="input" 
+          v-model="formData.idCard" 
+          placeholder="请输入身份证号"
+          maxlength="18"
+        />
+      </view>
+
+      <!-- 指导师证书号 -->
+      <view class="form-item">
+        <text class="label">指导师证书号</text>
+        <input 
+          class="input" 
+          v-model="formData.teacherNo" 
+          placeholder="请输入指导师证书号"
+        />
+      </view>
+
+      <!-- 指导师照片 -->
+      <view class="form-item">
+        <text class="label">指导师照片</text>
+        <view class="upload-section">
+          <image 
+            v-if="formData.teacherPhoto" 
+            :src="formData.teacherPhoto" 
+            class="preview-image"
+            @click="chooseImage('teacherPhoto')"
+          />
+          <view v-else class="upload-placeholder" @click="chooseImage('teacherPhoto')">
+            <text class="upload-icon">📷</text>
+            <text class="upload-text">上传照片</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 证书照片 -->
+      <view class="form-item">
+        <text class="label">证书照片</text>
+        <view class="upload-section">
+          <image 
+            v-if="formData.certificateImage" 
+            :src="formData.certificateImage" 
+            class="preview-image"
+            @click="chooseImage('certificateImage')"
+          />
+          <view v-else class="upload-placeholder" @click="chooseImage('certificateImage')">
+            <text class="upload-icon">📷</text>
+            <text class="upload-text">上传证书</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 提交按钮 -->
+      <button class="btn-submit" @click="submit">提交申请</button>
+    </view>
+  </view>
+</template>
+
+<script>
+import { registerGuide, validateInviteCode } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      formData: {
+        inviteCode: '',
+        realName: '',
+        phone: '',
+        idCard: '',
+        teacherNo: '',
+        teacherPhoto: '',
+        certificateImage: ''
+      },
+      inviteCodeInfo: null
+    }
+  },
+  onLoad(options) {
+    // 如果有邀请码参数,自动填充
+    if (options.inviteCode) {
+      this.formData.inviteCode = options.inviteCode
+      this.validateInviteCode()
+    }
+  },
+  methods: {
+    async validateInviteCode() {
+      if (!this.formData.inviteCode) {
+        this.inviteCodeInfo = null
+        return
+      }
+
+      try {
+        const res = await validateInviteCode(this.formData.inviteCode)
+        if (res.code === 200) {
+          this.inviteCodeInfo = res.data
+          uni.showToast({
+            title: '邀请码有效',
+            icon: 'success'
+          })
+        } else {
+          this.inviteCodeInfo = null
+          uni.showToast({
+            title: '邀请码无效',
+            icon: 'none'
+          })
+        }
+      } catch (e) {
+        this.inviteCodeInfo = null
+        uni.showToast({
+          title: '验证失败',
+          icon: 'none'
+        })
+      }
+    },
+
+    chooseImage(field) {
+      uni.chooseImage({
+        count: 1,
+        sizeType: ['compressed'],
+        sourceType: ['album', 'camera'],
+        success: (res) => {
+          const tempFilePath = res.tempFilePaths[0]
+          this.uploadImage(field, tempFilePath)
+        }
+      })
+    },
+
+    uploadImage(field, filePath) {
+      uni.showLoading({ title: '上传中...' })
+      
+      // TODO: 实际应该上传到服务器
+      // 这里简化处理,直接使用本地路径
+      setTimeout(() => {
+        this.formData[field] = filePath
+        uni.hideLoading()
+        uni.showToast({
+          title: '上传成功',
+          icon: 'success'
+        })
+      }, 1000)
+    },
+
+    async submit() {
+      // 验证表单
+      if (!this.formData.realName) {
+        uni.showToast({ title: '请输入真实姓名', icon: 'none' })
+        return
+      }
+
+      if (!this.formData.phone) {
+        uni.showToast({ title: '请输入手机号', icon: 'none' })
+        return
+      }
+
+      if (!this.formData.idCard) {
+        uni.showToast({ title: '请输入身份证号', icon: 'none' })
+        return
+      }
+
+      if (!this.formData.teacherNo) {
+        uni.showToast({ title: '请输入指导师证书号', icon: 'none' })
+        return
+      }
+
+      if (!this.formData.teacherPhoto) {
+        uni.showToast({ title: '请上传指导师照片', icon: 'none' })
+        return
+      }
+
+      if (!this.formData.certificateImage) {
+        uni.showToast({ title: '请上传证书照片', icon: 'none' })
+        return
+      }
+
+      try {
+        uni.showLoading({ title: '提交中...' })
+        
+        const userId = uni.getStorageSync('userId')
+        const res = await registerGuide({
+          userId: userId,
+          ...this.formData
+        })
+
+        uni.hideLoading()
+
+        if (res.code === 200) {
+          uni.showModal({
+            title: '申请成功',
+            content: '您的指导师申请已提交,请等待审核',
+            showCancel: false,
+            success: () => {
+              uni.navigateBack()
+            }
+          })
+        } else {
+          uni.showToast({
+            title: res.message || '提交失败',
+            icon: 'none'
+          })
+        }
+      } catch (e) {
+        uni.hideLoading()
+        uni.showToast({
+          title: '提交失败',
+          icon: 'none'
+        })
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  padding: 30rpx;
+  background: #F8F8F8;
+  min-height: 100vh;
+}
+
+.header {
+  text-align: center;
+  padding: 60rpx 0 40rpx;
+}
+
+.title {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.subtitle {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.form {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 40rpx;
+}
+
+.form-item {
+  margin-bottom: 40rpx;
+}
+
+.label {
+  font-size: 28rpx;
+  color: #333;
+  display: block;
+  margin-bottom: 20rpx;
+  font-weight: bold;
+}
+
+.input {
+  width: 100%;
+  height: 80rpx;
+  border: 1rpx solid #E0E0E0;
+  border-radius: 10rpx;
+  padding: 0 20rpx;
+  font-size: 28rpx;
+}
+
+.hint {
+  font-size: 24rpx;
+  color: #FF6B6B;
+  margin-top: 10rpx;
+  display: block;
+}
+
+.upload-section {
+  display: flex;
+  align-items: center;
+}
+
+.preview-image {
+  width: 200rpx;
+  height: 200rpx;
+  border-radius: 10rpx;
+  object-fit: cover;
+}
+
+.upload-placeholder {
+  width: 200rpx;
+  height: 200rpx;
+  border: 2rpx dashed #E0E0E0;
+  border-radius: 10rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background: #F8F8F8;
+}
+
+.upload-icon {
+  font-size: 60rpx;
+  margin-bottom: 10rpx;
+}
+
+.upload-text {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.btn-submit {
+  width: 100%;
+  height: 90rpx;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+  border-radius: 45rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+  margin-top: 40rpx;
+}
+</style>

+ 183 - 0
zxyj-frontend/utils/api.js

@@ -405,6 +405,189 @@ export const getPackageItems = (packageId) => {
   return request(`/api/packages/${packageId}/items`, 'POST')
   return request(`/api/packages/${packageId}/items`, 'POST')
 }
 }
 
 
+// 获取公开套餐列表
+export const getPublicPackages = (category) => {
+  return request('/api/market/packages', 'POST', { category })
+}
+
+// 通过分享码获取套餐
+export const getPackageByShareCode = (code) => {
+  return request(`/api/market/share/${code}`, 'POST')
+}
+
+// 购买并应用套餐
+export const purchasePackage = (packageId, data) => {
+  return request(`/api/market/packages/${packageId}/purchase`, 'POST', data)
+}
+
+// 获取我的任务计划
+export const getMyPlans = () => {
+  return request('/api/market/plans', 'POST')
+}
+
+// 获取计划详情
+export const getPlanDetail = (id) => {
+  return request(`/api/market/plans/${id}`, 'POST')
+}
+
+// 取消计划
+export const cancelPlan = (id) => {
+  return request(`/api/market/plans/${id}/cancel`, 'POST')
+}
+
+// 确认绑定指导师
+export const acceptBind = (data) => {
+  return request('/api/bind/accept', 'POST', data)
+}
+
+// 获取绑定的指导师列表
+export const getBoundGuides = () => {
+  return request('/api/bind/guides', 'POST')
+}
+
+// 指导师注册
+export const registerGuide = (data) => {
+  return request('/api/guide/register', 'POST', data)
+}
+
+// 生成邀请码
+export const generateInviteCode = () => {
+  return request('/api/guide/invite-code/generate', 'POST')
+}
+
+// 获取邀请码列表
+export const getInviteCodes = () => {
+  return request('/api/guide/invite-codes', 'GET')
+}
+
+// 验证邀请码
+export const validateInviteCode = (code) => {
+  return request('/api/guide/invite-code/validate', 'GET', null, { code })
+}
+
+// 获取下级指导师列表
+export const getChildGuides = () => {
+  return request('/api/guide/child-guides', 'GET')
+}
+
+// 获取所有下级指导师
+export const getAllChildGuides = () => {
+  return request('/api/guide/all-child-guides', 'GET')
+}
+
+// 获取上级指导师
+export const getParentGuide = () => {
+  return request('/api/guide/parent-guide', 'GET')
+}
+
+// 添加指导师套餐
+export const addGuidePackage = (guideId, packageId, price, commissionRate) => {
+  return request('/api/guide/package/add', 'POST', { guideId, packageId, price, commissionRate })
+}
+
+// 获取指导师套餐列表
+export const getGuidePackages = (guideId) => {
+  return request('/api/guide/packages', 'GET', null, { guideId })
+}
+
+// 获取指导师套餐定价
+export const getGuidePackage = (guideId, packageId) => {
+  return request('/api/guide/package/' + packageId, 'GET', null, { guideId })
+}
+
+// 更新指导师套餐价格
+export const updateGuidePackagePrice = (guideId, packageId, price) => {
+  return request('/api/guide/package/' + packageId, 'PUT', { guideId, price })
+}
+
+// 删除指导师套餐
+export const deleteGuidePackage = (guideId, packageId) => {
+  return request('/api/guide/package/' + packageId, 'DELETE', null, { guideId })
+}
+
+// 获取套餐价格
+export const getPackagePrice = (packageId, guideId, inviteCode) => {
+  return request('/api/package/price', 'GET', null, { packageId, guideId, inviteCode })
+}
+
+// 获取可用的指导师列表
+export const getAvailableGuides = (packageId) => {
+  return request('/api/package/available-guides', 'GET', null, { packageId })
+}
+
+// 创建订单
+export const createOrder = (data) => {
+  return request('/api/package/order/create', 'POST', data)
+}
+
+// 获取订单状态
+export const getOrderStatus = (orderNo) => {
+  return request('/api/payment/order/status/' + orderNo, 'GET')
+}
+
+// 创建微信支付订单
+export const createWechatPayOrder = (orderNo, description, amount) => {
+  return request('/api/payment/wechat/create', 'POST', { orderNo, description, amount })
+}
+
+// 创建支付宝订单
+export const createAlipayOrder = (orderNo, description, amount) => {
+  return request('/api/payment/alipay/create', 'POST', { orderNo, description, amount })
+}
+
+// 处理微信支付回调
+export const handleWechatCallback = (params) => {
+  return request('/api/payment/wechat/callback', 'POST', params)
+}
+
+// 处理支付宝回调
+export const handleAlipayCallback = (params) => {
+  return request('/api/payment/alipay/callback', 'POST', params)
+}
+
+// 获取我的测评报告
+export const getMyAssessmentResults = () => {
+  return request('/api/assessment/my-results', 'GET')
+}
+
+// 获取下级指导师的测评报告
+export const getChildAssessmentResults = () => {
+  return request('/api/assessment/child-results', 'GET')
+}
+
+// 获取测评报告详情
+export const getAssessmentResult = (id) => {
+  return request('/api/assessment/result/' + id, 'GET')
+}
+
+// 创建测评预约
+export const createAssessmentAppointment = (data) => {
+  return request('/api/assessment/appointment/create', 'POST', data)
+}
+
+// 获取孩子列表
+export const getChildren = () => {
+  return request('/api/user/children/list', 'POST')
+}
+export const addPackageItem = (packageId, data) => {
+  return request(`/api/packages/${packageId}/items`, 'POST', data)
+}
+
+// 更新任务项
+export const updatePackageItem = (itemId, data) => {
+  return request(`/api/packages/items/${itemId}`, 'PUT', data)
+}
+
+// 删除任务项
+export const deletePackageItem = (itemId) => {
+  return request(`/api/packages/items/${itemId}`, 'DELETE')
+}
+
+// 获取套餐任务项
+export const getPackageItems = (packageId) => {
+  return request(`/api/packages/${packageId}/items`, 'POST')
+}
+
 // =============================================
 // =============================================
 // 家长-套餐市场模块
 // 家长-套餐市场模块
 // =============================================
 // =============================================