Ver Fonte

Merge branch 'sit-three-categories' into sit

lutong há 3 meses atrás
pai
commit
ecbf5a4422
100 ficheiros alterados com 9120 adições e 1002 exclusões
  1. 1 0
      .gitignore
  2. 11 0
      alien-entity/src/main/java/shop/alien/entity/store/BarPerformance.java
  3. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/LifeLikeRecord.java
  4. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/SportsFacilityArea.java
  5. 117 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreCuisine.java
  6. 69 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreCuisineCombo.java
  7. 113 0
      alien-entity/src/main/java/shop/alien/entity/store/StorePrice.java
  8. 96 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreStaffComment.java
  9. 20 32
      alien-entity/src/main/java/shop/alien/entity/store/StoreStaffConfig.java
  10. 100 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreStaffReview.java
  11. 103 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreStaffTitle.java
  12. 74 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreVideo.java
  13. 30 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/CategoryGroupDto.java
  14. 74 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineComboDto.java
  15. 40 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineDetailDto.java
  16. 31 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineItemDto.java
  17. 23 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffCommentRequestDto.java
  18. 0 3
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffConfigListQueryDto.java
  19. 35 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffReplyDto.java
  20. 47 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffReviewDto.java
  21. 79 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceDetailVo.java
  22. 49 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceGroupByDateVo.java
  23. 37 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceGroupListVo.java
  24. 60 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceGuestVo.java
  25. 66 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceListVo.java
  26. 54 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceScheduleVo.java
  27. 50 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StaffTitleGroupVo.java
  28. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOfficialAlbumImgVo.java
  29. 76 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffCommentVo.java
  30. 44 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffDetailWithPerformanceVo.java
  31. 29 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffReviewDetailVo.java
  32. 25 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffReviewListWithStaffVo.java
  33. 87 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffReviewVo.java
  34. 19 0
      alien-entity/src/main/java/shop/alien/mapper/StoreCuisineComboMapper.java
  35. 19 0
      alien-entity/src/main/java/shop/alien/mapper/StoreCuisineMapper.java
  36. 13 0
      alien-entity/src/main/java/shop/alien/mapper/StoreImgMapper.java
  37. 17 0
      alien-entity/src/main/java/shop/alien/mapper/StorePriceMapper.java
  38. 54 0
      alien-entity/src/main/java/shop/alien/mapper/StoreStaffCommentMapper.java
  39. 52 0
      alien-entity/src/main/java/shop/alien/mapper/StoreStaffReviewMapper.java
  40. 14 0
      alien-entity/src/main/java/shop/alien/mapper/StoreStaffTitleMapper.java
  41. 16 0
      alien-entity/src/main/java/shop/alien/mapper/StoreVideoMapper.java
  42. 29 0
      alien-entity/src/main/resources/mapper/StoreCuisineComboMapper.xml
  43. 41 0
      alien-entity/src/main/resources/mapper/StoreCuisineMapper.xml
  44. 184 0
      alien-entity/src/main/resources/mapper/StoreStaffCommentMapper.xml
  45. 132 0
      alien-entity/src/main/resources/mapper/StoreStaffReviewMapper.xml
  46. 5 4
      alien-gateway/src/main/resources/bootstrap-dev.yml
  47. 1 0
      alien-gateway/src/main/resources/bootstrap-prod.yml
  48. 7 6
      alien-gateway/src/main/resources/bootstrap-test.yml
  49. 1 0
      alien-gateway/src/main/resources/bootstrap-uat.yml
  50. 1 1
      alien-gateway/src/main/resources/bootstrap.yml
  51. 175 172
      alien-gateway/src/main/resources/logback-spring.xml
  52. 49 14
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformDiscussionController.java
  53. 5 2
      alien-store-platform/src/main/java/shop/alien/storeplatform/entity/StorePlatformDiscussion.java
  54. 2 2
      alien-store-platform/src/main/java/shop/alien/storeplatform/mapper/StorePlatformDiscussionMapper.java
  55. 0 30
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StoreDiscussionService.java
  56. 57 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformDiscussionService.java
  57. 0 36
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreDiscussionServiceImpl.java
  58. 191 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformDiscussionServiceImpl.java
  59. 42 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/vo/StorePlatformDiscussionReplyVo.java
  60. 26 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/vo/StorePlatformDiscussionUserVo.java
  61. 197 0
      alien-store/src/main/java/shop/alien/store/controller/PerformanceListController.java
  62. 190 0
      alien-store/src/main/java/shop/alien/store/controller/StoreCuisineController.java
  63. 20 0
      alien-store/src/main/java/shop/alien/store/controller/StoreImgController.java
  64. 23 2
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  65. 14 6
      alien-store/src/main/java/shop/alien/store/controller/StoreOfficialAlbumController.java
  66. 228 0
      alien-store/src/main/java/shop/alien/store/controller/StorePriceController.java
  67. 207 0
      alien-store/src/main/java/shop/alien/store/controller/StoreStaffCommentController.java
  68. 213 103
      alien-store/src/main/java/shop/alien/store/controller/StoreStaffConfigController.java
  69. 185 0
      alien-store/src/main/java/shop/alien/store/controller/StoreStaffReviewController.java
  70. 252 0
      alien-store/src/main/java/shop/alien/store/controller/StoreStaffTitleController.java
  71. 176 0
      alien-store/src/main/java/shop/alien/store/controller/StoreVideoController.java
  72. 56 0
      alien-store/src/main/java/shop/alien/store/service/PerformanceListService.java
  73. 36 0
      alien-store/src/main/java/shop/alien/store/service/StoreCuisineComboService.java
  74. 46 0
      alien-store/src/main/java/shop/alien/store/service/StoreCuisineService.java
  75. 11 0
      alien-store/src/main/java/shop/alien/store/service/StoreImgService.java
  76. 11 0
      alien-store/src/main/java/shop/alien/store/service/StoreInfoService.java
  77. 5 2
      alien-store/src/main/java/shop/alien/store/service/StoreOfficialAlbumService.java
  78. 15 0
      alien-store/src/main/java/shop/alien/store/service/StorePriceService.java
  79. 88 0
      alien-store/src/main/java/shop/alien/store/service/StoreStaffCommentService.java
  80. 50 0
      alien-store/src/main/java/shop/alien/store/service/StoreStaffConfigService.java
  81. 95 0
      alien-store/src/main/java/shop/alien/store/service/StoreStaffReviewService.java
  82. 73 0
      alien-store/src/main/java/shop/alien/store/service/StoreStaffTitleService.java
  83. 33 0
      alien-store/src/main/java/shop/alien/store/service/StoreVideoService.java
  84. 906 0
      alien-store/src/main/java/shop/alien/store/service/impl/PerformanceListServiceImpl.java
  85. 55 74
      alien-store/src/main/java/shop/alien/store/service/impl/SportsEquipmentFacilityServiceImpl.java
  86. 46 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreCuisineComboServiceImpl.java
  87. 206 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreCuisineServiceImpl.java
  88. 20 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreImgServiceImpl.java
  89. 175 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java
  90. 107 38
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOfficialAlbumServiceImpl.java
  91. 23 0
      alien-store/src/main/java/shop/alien/store/service/impl/StorePriceServiceImpl.java
  92. 519 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffCommentServiceImpl.java
  93. 969 470
      alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffConfigServiceImpl.java
  94. 523 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffReviewServiceImpl.java
  95. 315 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffTitleServiceImpl.java
  96. 57 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreVideoServiceImpl.java
  97. 22 0
      alien-store/src/main/java/shop/alien/store/util/CommonConstant.java
  98. 146 0
      alien-store/src/main/java/shop/alien/store/util/ai/AiGetPriceUtil.java
  99. 5 4
      alien-store/src/main/resources/bootstrap-dev.yml
  100. 1 0
      alien-store/src/main/resources/bootstrap-prod.yml

+ 1 - 0
.gitignore

@@ -18,6 +18,7 @@ target/
 *.iws
 *.iml
 *.ipr
+*.log.gz
 
 ### NetBeans ###
 /nbproject/private/

+ 11 - 0
alien-entity/src/main/java/shop/alien/entity/store/BarPerformance.java

@@ -8,6 +8,7 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.time.LocalTime;
 import java.util.Date;
 
 /**
@@ -139,4 +140,14 @@ public class BarPerformance implements Serializable {
     @TableField(value = "updated_time", fill = FieldFill.UPDATE)
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date updatedTime;
+
+    @ApiModelProperty(value = "演出开始时间")
+    @TableField(value = "daily_start_time")
+    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
+    private LocalTime dailyStartTime;
+
+    @ApiModelProperty(value = "演出结束时间")
+    @TableField(value = "daily_end_time")
+    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
+    private LocalTime dailyEndTime;
 }

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/store/LifeLikeRecord.java

@@ -23,7 +23,7 @@ public class LifeLikeRecord {
 
     private String huifuId;
 
-    @ApiModelProperty(value = "1-评论 2-社区动态 3-活动 4-推荐菜 5-店铺打卡 6-二手商品 7-律师评分 8-点赞员工")
+    @ApiModelProperty(value = "1-评论 2-社区动态 3-活动 4-推荐菜 5-店铺打卡 6-二手商品 7-律师评分 8-点赞员工 9-员工评价点赞")
     private String type;
 
     private Integer dianzanCount;

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/SportsFacilityArea.java

@@ -63,5 +63,9 @@ public class SportsFacilityArea implements Serializable {
     @ApiModelProperty(value = "修改人ID")
     @TableField(value = "updated_user_id", fill = FieldFill.INSERT_UPDATE)
     private Integer updatedUserId;
+
+    @ApiModelProperty(value = "区域logo图地址")
+    @TableField("area_logo_img")
+    private String areaLogoImg;
 }
 

+ 117 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreCuisine.java

@@ -0,0 +1,117 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 美食价目表
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+@Data
+@JsonInclude
+@TableName("store_cuisine")
+@ApiModel(value = "StoreCuisine对象", description = "美食价目表")
+public class StoreCuisine {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "商户id")
+    @TableField("stroe_id")
+    private Integer stroeId;
+
+    @ApiModelProperty(value = "菜名")
+    @TableField("name")
+    private String name;
+
+    @ApiModelProperty(value = "总价")
+    @TableField("total_price")
+    private BigDecimal totalPrice;
+
+    @ApiModelProperty(value = "图片列表,最多 9 张 URL")
+    @TableField("images")
+    private String images;
+
+    @ApiModelProperty(value = "图文详情-图片")
+    @TableField("image_content")
+    private String imageContent;
+
+    @ApiModelProperty(value = "菜品原料json(原料名称:name,所需重量:height,成本价:cost,推荐价格:suggest)")
+    @TableField("raw_json")
+    private String rawJson;
+
+    @ApiModelProperty(value = "图文详情-文字")
+    @TableField("detail_content")
+    private String detailContent;
+
+    @ApiModelProperty(value = "补充说明")
+    @TableField("extra_note")
+    private String extraNote;
+
+    @ApiModelProperty(value = "是否需要预约:0=否,1=是")
+    @TableField("need_reserve")
+    private Integer needReserve;
+
+    @ApiModelProperty(value = "预约规则")
+    @TableField("reserve_rule")
+    private String reserveRule;
+
+    @ApiModelProperty(value = "适用人数")
+    @TableField("people_limit")
+    private Integer peopleLimit;
+
+    @ApiModelProperty(value = "使用规则")
+    @TableField("usage_rule")
+    private String usageRule;
+
+    @ApiModelProperty(value = "状态:0-待审核 1-审核通过 2-审核拒绝")
+    @TableField("status")
+    private Integer status;
+
+    @ApiModelProperty(value = "上下架状态:1-上架,2-下架")
+    @TableField("shelf_status")
+    private Integer shelfStatus;
+
+    @ApiModelProperty(value = "拒绝原因")
+    @TableField("rejection_reason")
+    private String rejectionReason;
+
+    @ApiModelProperty(value = "美食类型: 1-单品,2-套餐")
+    @TableField("cuisine_type")
+    private Integer cuisineType;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建人")
+    @TableField(value = "created_user_id", fill = FieldFill.INSERT)
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "更新人")
+    @TableField(value = "updated_user_id", fill = FieldFill.UPDATE)
+    private Integer updatedUserId;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "更新时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+}
+
+

+ 69 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreCuisineCombo.java

@@ -0,0 +1,69 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 美食套餐表(辅助详情查询)
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+@Data
+@JsonInclude
+@TableName("store_cuisine_combo")
+@ApiModel(value = "StoreCuisineCombo对象", description = "美食套餐表(辅助详情查询)")
+public class StoreCuisineCombo {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "单品id")
+    @TableField("sid")
+    private Integer sid;
+
+    @ApiModelProperty(value = "套餐id")
+    @TableField("cid")
+    private Integer cid;
+
+    @ApiModelProperty(value = "套餐包含该单品的数量")
+    @TableField("snum")
+    private Integer snum;
+
+    @ApiModelProperty(value = "类别")
+    @TableField("category")
+    private String category;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建人")
+    @TableField(value = "created_user_id", fill = FieldFill.INSERT)
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "更新人")
+    @TableField(value = "updated_user_id", fill = FieldFill.UPDATE)
+    private Integer updatedUserId;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "更新时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+}
+
+
+

+ 113 - 0
alien-entity/src/main/java/shop/alien/entity/store/StorePrice.java

@@ -0,0 +1,113 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 美食价目表
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+@Data
+@JsonInclude
+@TableName("store_price")
+@ApiModel(value = "StorePrice", description = "通用价目表")
+public class StorePrice {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "商户id")
+    @TableField("stroe_id")
+    private Integer stroeId;
+
+    @ApiModelProperty(value = "名字")
+    @TableField("name")
+    private String name;
+
+    @ApiModelProperty(value = "总价")
+    @TableField("total_price")
+    private BigDecimal totalPrice;
+
+    @ApiModelProperty(value = "图片列表,最多 9 张 URL")
+    @TableField("images")
+    private String images;
+
+    @ApiModelProperty(value = "图文详情-图片")
+    @TableField("image_content")
+    private String imageContent;
+
+    @ApiModelProperty(value = "服务项目json(服务名称:name,数量:num,单位:unit,服务说明:details)")
+    @TableField("service_json")
+    private String serviceJson;
+
+    @ApiModelProperty(value = "图文详情-文字")
+    @TableField("detail_content")
+    private String detailContent;
+
+    @ApiModelProperty(value = "补充说明")
+    @TableField("extra_note")
+    private String extraNote;
+
+    @ApiModelProperty(value = "是否需要预约:0=否,1=是")
+    @TableField("need_reserve")
+    private Integer needReserve;
+
+    @ApiModelProperty(value = "预约规则")
+    @TableField("reserve_rule")
+    private String reserveRule;
+
+    @ApiModelProperty(value = "适用人数")
+    @TableField("people_limit")
+    private Integer peopleLimit;
+
+    @ApiModelProperty(value = "使用规则")
+    @TableField("usage_rule")
+    private String usageRule;
+
+    @ApiModelProperty(value = "状态:0-待审核 1-审核通过 2-审核拒绝")
+    @TableField("status")
+    private Integer status;
+
+    @ApiModelProperty(value = "上下架状态:1-上架,2-下架")
+    @TableField("shelf_status")
+    private Integer shelfStatus;
+
+    @ApiModelProperty(value = "拒绝原因")
+    @TableField("rejection_reason")
+    private String rejectionReason;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建人")
+    @TableField(value = "created_user_id", fill = FieldFill.INSERT)
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "更新人")
+    @TableField(value = "updated_user_id", fill = FieldFill.UPDATE)
+    private Integer updatedUserId;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "更新时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+}
+
+

+ 96 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreStaffComment.java

@@ -0,0 +1,96 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 员工评论回复表
+ * 其他用户对员工评价的评论
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("store_staff_comment")
+@ApiModel(value = "StoreStaffComment对象", description = "员工评论回复")
+public class StoreStaffComment {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "评价ID")
+    @TableField("review_id")
+    private Integer reviewId;
+
+    @ApiModelProperty(value = "发送用户类型:1-普通用户,2-员工")
+    @TableField("send_user_type")
+    private Integer sendUserType;
+
+    @ApiModelProperty(value = "评论用户ID")
+    @TableField("send_user_id")
+    private Integer sendUserId;
+
+    @ApiModelProperty(value = "接收用户类型:1-普通用户,2-员工")
+    @TableField("receive_user_type")
+    private Integer receiveUserType;
+
+    @ApiModelProperty(value = "接收用户ID")
+    @TableField("receive_user_id")
+    private Integer receiveUserId;
+
+    @ApiModelProperty(value = "评论内容")
+    @TableField("comment_content")
+    private String commentContent;
+
+    @ApiModelProperty(value = "点赞数")
+    @TableField("like_count")
+    private Integer likeCount;
+
+    @ApiModelProperty(value = "回复数")
+    @TableField("reply_count")
+    private Integer replyCount;
+
+    @ApiModelProperty(value = "首评标记, 0:是, 1:否")
+    @TableField("head_type")
+    private Integer headType;
+
+    @ApiModelProperty(value = "首评ID(仅子评论存在该属性)")
+    @TableField("head_id")
+    private Integer headId;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+
+    @ApiModelProperty(value = "登陆人")
+    @TableField(exist = false)
+    private Integer userId;
+}
+

+ 20 - 32
alien-entity/src/main/java/shop/alien/entity/store/StoreStaffConfig.java

@@ -8,10 +8,8 @@ import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import shop.alien.entity.store.dto.StoreStaffFitnessCourseGroup;
 
 import java.util.Date;
-import java.util.List;
 
 /**
  * @Author: fcw
@@ -47,11 +45,11 @@ public class StoreStaffConfig extends Model<StoreStaffConfig> {
     @TableField("staff_image")
     private String staffImage;
 
-    @ApiModelProperty(value = "擅长标签id(字典表proficient_tag的dict_id逗号分隔)")
-    @TableField("proficient_id")
-    private String proficientId;
+    // @ApiModelProperty(value = "擅长标签id(字典表proficient_tag的dict_id逗号分隔)----废弃!")
+    // @TableField("proficient_id")
+    // private String proficientId;
 
-    @ApiModelProperty(value = "擅长标签名称(逗号分隔)")
+    @ApiModelProperty(value = "擅长项目")
     @TableField("proficient_projects")
     private String proficientProjects;
 
@@ -119,37 +117,27 @@ public class StoreStaffConfig extends Model<StoreStaffConfig> {
     @TableField("online_status")
     private Integer onlineStatus;
 
-    @ApiModelProperty(value = "经营板块id(词典表 键为 business_section)")
-    @TableField("business_section")
-    private Integer businessSection;
+    // @ApiModelProperty(value = "经营板块id(词典表 键为 business_section)----废弃!")
+    // @TableField("business_section")
+    // private Integer businessSection;
 
-    // ===== 运动健身业务扩展信息(当 businessSection 对应“运动健身”时生效) =====
-
-    @ApiModelProperty(value = "运动健身-课程信息列表")
-    @TableField(exist = false)
-    private List<StoreStaffFitnessCourse> fitnessCourseList;
-
-    @ApiModelProperty(value = "运动健身-课程分组(前端推荐使用:课程类型下多项目)")
-    @TableField(exist = false)
-    private List<StoreStaffFitnessCourseGroup> fitnessCourseGroupList;
-
-    @ApiModelProperty(value = "运动健身-职业认证列表")
-    @TableField(exist = false)
-    private List<StoreStaffFitnessCertification> fitnessCertificationList;
+    @ApiModelProperty(value = "点赞数量")
+    @TableField("like_count")
+    private Integer likeCount;
 
-    @ApiModelProperty(value = "运动健身-荣誉奖项列表")
-    @TableField(exist = false)
-    private List<StoreStaffFitnessCertification> fitnessHonorList;
+    @ApiModelProperty(value = "标题id")
+    @TableField("title_id")
+    private Integer titleId;
 
-    @ApiModelProperty(value = "运动健身-从业经历列表")
+    @ApiModelProperty(value = "今日是否有演出(true-有演出,false-无演出)")
     @TableField(exist = false)
-    private List<StoreStaffFitnessExperience> fitnessExperienceList;
+    private Boolean hasPerformanceToday;
 
-    @ApiModelProperty(value = "运动健身-基本信息")
+    @ApiModelProperty(value = "好评数量")
     @TableField(exist = false)
-    private StoreStaffFitnessBase fitnessBase;
+    private Integer positiveCommentsCount;
 
-    @ApiModelProperty(value = "点赞数量")
-    @TableField("like_count")
-    private Integer likeCount;
+    @ApiModelProperty(value = "平均评分(1-5星,支持0.5星)")
+    @TableField("service_score")
+    private java.math.BigDecimal serviceScore;
 }

+ 100 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreStaffReview.java

@@ -0,0 +1,100 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 员工评价表
+ * 用户对员工的评价和评分
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("store_staff_review")
+@ApiModel(value = "StoreStaffReview对象", description = "员工评价")
+public class StoreStaffReview {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "评价用户ID(订单用户)")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "员工用户ID")
+    @TableField("staff_user_id")
+    private Integer staffUserId;
+
+    @ApiModelProperty(value = "总体评分(1-5星,支持0.5星)")
+    @TableField("overall_rating")
+    private java.math.BigDecimal overallRating;
+
+    @ApiModelProperty(value = "服务态度评分(1-5星,支持0.5星)")
+    @TableField("service_attitude_rating")
+    private java.math.BigDecimal serviceAttitudeRating;
+
+    @ApiModelProperty(value = "响应时间评分(1-5星,支持0.5星)")
+    @TableField("response_time_rating")
+    private java.math.BigDecimal responseTimeRating;
+
+    @ApiModelProperty(value = "专业能力评分(1-5星,支持0.5星)")
+    @TableField("professional_ability_rating")
+    private java.math.BigDecimal professionalAbilityRating;
+
+    @ApiModelProperty(value = "评价内容")
+    @TableField("review_content")
+    private String reviewContent;
+
+    @ApiModelProperty(value = "评价图片(JSON数组格式存储图片URL)")
+    @TableField("review_images")
+    private String reviewImages;
+
+    @ApiModelProperty(value = "是否匿名评价,0:否,1:是")
+    @TableField("is_anonymous")
+    private Integer isAnonymous;
+
+    @ApiModelProperty(value = "点赞数")
+    @TableField("like_count")
+    private Integer likeCount;
+
+    @ApiModelProperty(value = "评论数")
+    @TableField("comment_count")
+    private Integer commentCount;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+
+    @ApiModelProperty(value = "申诉id (如果该评价被申诉 申诉id会有值)")
+    @TableField("appeal_id")
+    private String appealId;
+}
+

+ 103 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreStaffTitle.java

@@ -0,0 +1,103 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 员工标题实体类
+ * 对应表: store_staff_title
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@TableName("store_staff_title")
+@ApiModel(value = "StoreStaffTitle对象", description = "员工标题")
+public class StoreStaffTitle extends Model<StoreStaffTitle> {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 门店ID
+     */
+    @ApiModelProperty(value = "门店ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    /**
+     * 员工IDs(逗号分隔)
+     */
+    @ApiModelProperty(value = "员工IDs(逗号分隔)")
+    @TableField("staff_ids")
+    private String staffIds;
+
+    /**
+     * 标题名称
+     */
+    @ApiModelProperty(value = "标题名称")
+    @TableField("title_name")
+    private String titleName;
+
+    /**
+     * 员工数量
+     */
+    @ApiModelProperty(value = "员工数量")
+    @TableField("staff_count")
+    private Integer staffCount;
+
+    /**
+     * 删除状态(0-未删除,1-已删除)
+     */
+    @ApiModelProperty(value = "删除状态(0-未删除,1-已删除)")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    /**
+     * 更新时间
+     */
+    @ApiModelProperty(value = "更新时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    /**
+     * 创建人ID
+     */
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    /**
+     * 修改人ID
+     */
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}
+

+ 74 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreVideo.java

@@ -0,0 +1,74 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 门店视频
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@JsonInclude
+@TableName("store_video")
+@ApiModel(value = "StoreVideo对象", description = "门店视频")
+public class StoreVideo extends Model<StoreVideo> {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "门店id")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "视频描述")
+    @TableField("img_description")
+    private String imgDescription;
+
+    @ApiModelProperty(value = "视频排序")
+    @TableField("img_sort")
+    private Integer imgSort;
+
+    @ApiModelProperty(value = "视频链接")
+    @TableField("img_url")
+    private String imgUrl;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+
+    @ApiModelProperty(value = "业务ID|相册(store_official_album)ID")
+    @TableField("business_id")
+    private Integer businessId;
+}
+

+ 30 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/CategoryGroupDto.java

@@ -0,0 +1,30 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 套餐分类组 DTO
+ * 用于解析前端传入的 groupJson
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel(value = "CategoryGroupDto", description = "套餐分类组")
+public class CategoryGroupDto {
+
+    @ApiModelProperty(value = "分类名称")
+    private String categoryName;
+
+    @ApiModelProperty(value = "该分类下的菜品列表")
+    private List<CuisineItemDto> items;
+}
+

+ 74 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineComboDto.java

@@ -0,0 +1,74 @@
+package shop.alien.entity.store.dto;
+
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+@ApiModel(value = "CuisineComboDto", description = "添加美食套餐dto")
+public class CuisineComboDto {
+
+    @ApiModelProperty(value = "id,修改时必传")
+    @TableField("id")
+    private Integer id;
+
+    @ApiModelProperty(value = "价目表内容的json数组,添加修改 套餐 时必传")
+    @TableField("group_json")
+    private String groupJson;
+
+    @ApiModelProperty(value = "菜品原料json(原料名称:name,所需重量:height,成本价:cost,推荐价格:suggest),添加修改 单品 时必传")
+    @TableField("raw_json")
+    private String rawJson;
+
+    @ApiModelProperty(value = "美食类型: 1-单品,2-套餐")
+    @TableField("cuisine_type")
+    private Integer cuisineType;
+
+    @ApiModelProperty(value = "商户id")
+    @TableField("stroe_id")
+    private Integer stroeId;
+
+    @ApiModelProperty(value = "菜名/套餐名")
+    @TableField("name")
+    private String name;
+
+    @ApiModelProperty(value = "总价")
+    @TableField("total_price")
+    private BigDecimal totalPrice;
+
+    @ApiModelProperty(value = "图片列表,最多 9 张 URL")
+    @TableField("images")
+    private String images;
+
+    @ApiModelProperty(value = "图文详情-图片")
+    @TableField("image_content")
+    private String imageContent;
+
+    @ApiModelProperty(value = "图文详情-文字")
+    @TableField("detail_content")
+    private String detailContent;
+
+    @ApiModelProperty(value = "补充说明")
+    @TableField("extra_note")
+    private String extraNote;
+
+    @ApiModelProperty(value = "是否需要预约:0=否,1=是")
+    @TableField("need_reserve")
+    private Integer needReserve;
+
+    @ApiModelProperty(value = "预约规则")
+    @TableField("reserve_rule")
+    private String reserveRule;
+
+    @ApiModelProperty(value = "适用人数")
+    @TableField("people_limit")
+    private Integer peopleLimit;
+
+    @ApiModelProperty(value = "使用规则")
+    @TableField("usage_rule")
+    private String usageRule;
+}

+ 40 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineDetailDto.java

@@ -0,0 +1,40 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.store.StoreCuisine;
+
+import java.util.List;
+
+/**
+ * 美食单品 / 套餐详情 DTO
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+@Data
+@ApiModel(value = "CuisineDetailDto", description = "美食单品或套餐详情")
+public class CuisineDetailDto {
+
+    @ApiModelProperty("主信息:单品或套餐(store_cuisine 表中的一条记录)")
+    private StoreCuisine baseInfo;
+
+    @ApiModelProperty("如果是套餐:包含的单品明细列表;如果是单品:为空列表")
+    private List<CuisineItemDetail> items;
+
+    @Data
+    @ApiModel(value = "CuisineItemDetail", description = "套餐中包含的单品明细")
+    public static class CuisineItemDetail {
+
+        @ApiModelProperty("单品信息")
+        private StoreCuisine single;
+
+        @ApiModelProperty("数量(中间表 snum)")
+        private Integer quantity;
+
+        @ApiModelProperty("分类名称(中间表 category)")
+        private String category;
+    }
+}
+

+ 31 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineItemDto.java

@@ -0,0 +1,31 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 套餐菜品项 DTO
+ * 用于解析前端传入的 groupJson 中的菜品信息
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel(value = "CuisineItemDto", description = "套餐菜品项")
+public class CuisineItemDto {
+
+    @ApiModelProperty(value = "菜品ID")
+    private Integer cuisineId;
+
+    @ApiModelProperty(value = "菜品名称")
+    private String cuisineName;
+
+    @ApiModelProperty(value = "数量")
+    private Integer quantity;
+}
+

+ 23 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffCommentRequestDto.java

@@ -0,0 +1,23 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 员工评论请求DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreStaffCommentRequestDto对象", description = "员工评论请求DTO")
+public class StoreStaffCommentRequestDto {
+
+    @ApiModelProperty(value = "评论ID")
+    private Integer commentId;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+}
+

+ 0 - 3
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffConfigListQueryDto.java

@@ -25,9 +25,6 @@ public class StoreStaffConfigListQueryDto {
     @ApiModelProperty(value = "员工状态(0-待审核 1-审核通过 2-审核拒绝)", example = "1")
     private String status;
 
-    @ApiModelProperty(value = "经营板块id(词典表 键为 business_section)")
-    private Integer businessSection;
-
     @ApiModelProperty(value = "上线状态(0-上线 1-下线)")
     private Integer onlineStatus;
 

+ 35 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffReplyDto.java

@@ -0,0 +1,35 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 员工评论回复DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreStaffReplyDto对象", description = "员工评论回复DTO")
+public class StoreStaffReplyDto {
+
+    @ApiModelProperty(value = "首评ID")
+    private Integer commentId;
+
+    @ApiModelProperty(value = "被回复用户ID")
+    private Integer replyToUserId;
+
+    @ApiModelProperty(value = "回复内容")
+    private String replyContent;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "发送用户类型:1-普通用户,2-员工(可选,不填时使用首评的接收用户类型)")
+    private Integer sendUserType;
+
+    @ApiModelProperty(value = "接收用户类型:1-普通用户,2-员工(可选,不填时使用首评的发送用户类型)")
+    private Integer receiveUserType;
+}
+

+ 47 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffReviewDto.java

@@ -0,0 +1,47 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 员工评价DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreStaffReviewDto对象", description = "员工评价DTO")
+public class StoreStaffReviewDto {
+
+    @ApiModelProperty(value = "员工用户ID")
+    private Integer staffUserId;
+
+    @ApiModelProperty(value = "总体评分(1-5星,支持0.5星)")
+    private BigDecimal overallRating;
+
+    @ApiModelProperty(value = "服务态度评分(1-5星,支持0.5星)")
+    private BigDecimal serviceAttitudeRating;
+
+    @ApiModelProperty(value = "响应时间评分(1-5星,支持0.5星)")
+    private BigDecimal responseTimeRating;
+
+    @ApiModelProperty(value = "专业能力评分(1-5星,支持0.5星)")
+    private BigDecimal professionalAbilityRating;
+
+    @ApiModelProperty(value = "评价内容")
+    private String reviewContent;
+
+    @ApiModelProperty(value = "评价图片列表")
+    private List<String> reviewImages;
+
+    @ApiModelProperty(value = "是否匿名评价,0:否,1:是")
+    private Integer isAnonymous;
+
+    @ApiModelProperty(value = "用户id")
+    private Integer userId;
+}
+

+ 79 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceDetailVo.java

@@ -0,0 +1,79 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 演出详情VO
+ * 用于展示演出详细信息
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "PerformanceDetailVo对象", description = "演出详情")
+public class PerformanceDetailVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 演出ID
+     */
+    @ApiModelProperty(value = "演出ID")
+    private Integer id;
+
+    /**
+     * 演出名称
+     */
+    @ApiModelProperty(value = "演出名称")
+    private String performanceName;
+
+    /**
+     * 演出海报路径
+     */
+    @ApiModelProperty(value = "演出海报路径")
+    private String performancePoster;
+
+    /**
+     * 演出时间(格式:2025.11.11-2027.01.31每周一、四19:30-次日00:00)
+     */
+    @ApiModelProperty(value = "演出时间(格式:2025.11.11-2027.01.31每周一、四19:30-次日00:00)")
+    private String performanceTime;
+
+    /**
+     * 演出风格(格式:流行、民谣)
+     */
+    @ApiModelProperty(value = "演出风格(格式:流行、民谣)")
+    private String performanceStyle;
+
+    /**
+     * 演出详情描述
+     */
+    @ApiModelProperty(value = "演出详情描述")
+    private String performanceContent;
+
+    /**
+     * 演出嘉宾列表
+     */
+    @ApiModelProperty(value = "演出嘉宾列表")
+    private List<PerformanceGuestVo> guestList;
+
+    /**
+     * 演出人员数量
+     */
+    @ApiModelProperty(value = "演出人员数量")
+    private Integer guestCount;
+
+    /**
+     * 演出类型(0-特邀演出,1-常规演出)
+     */
+    @ApiModelProperty(value = "演出类型(0-特邀演出,1-常规演出)")
+    private Integer performanceType;
+}
+

+ 49 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceGroupByDateVo.java

@@ -0,0 +1,49 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 按日期分组的演出列表VO
+ * 用于展示按日期分组的演出列表,每个日期组包含该日期下的所有演出
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "PerformanceGroupByDateVo对象", description = "按日期分组的演出列表")
+public class PerformanceGroupByDateVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 日期标题(格式:12-08 今天 或 12-12 周五)
+     */
+    @ApiModelProperty(value = "日期标题(格式:12-08 今天 或 12-12 周五)")
+    private String dateTitle;
+
+    /**
+     * 日期(格式:yyyy-MM-dd)
+     */
+    @ApiModelProperty(value = "日期(格式:yyyy-MM-dd)")
+    private String date;
+
+    /**
+     * 是否为今天
+     */
+    @ApiModelProperty(value = "是否为今天")
+    private Boolean isToday;
+
+    /**
+     * 该日期下的演出列表
+     */
+    @ApiModelProperty(value = "该日期下的演出列表")
+    private List<PerformanceListVo> performanceList;
+}
+

+ 37 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceGroupListVo.java

@@ -0,0 +1,37 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 按日期分组的演出列表返回VO
+ * 包含所有日期分组和演出列表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "PerformanceGroupListVo对象", description = "按日期分组的演出列表")
+public class PerformanceGroupListVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 按日期分组的演出列表
+     */
+    @ApiModelProperty(value = "按日期分组的演出列表")
+    private List<PerformanceGroupByDateVo> groupList;
+
+    /**
+     * 总记录数
+     */
+    @ApiModelProperty(value = "总记录数")
+    private Long total;
+}
+

+ 60 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceGuestVo.java

@@ -0,0 +1,60 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 演出嘉宾VO
+ * 用于展示演出嘉宾信息
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "PerformanceGuestVo对象", description = "演出嘉宾")
+public class PerformanceGuestVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 员工ID
+     */
+    @ApiModelProperty(value = "员工ID")
+    private Integer staffId;
+
+    /**
+     * 员工姓名
+     */
+    @ApiModelProperty(value = "员工姓名")
+    private String name;
+
+    /**
+     * 员工头像
+     */
+    @ApiModelProperty(value = "员工头像")
+    private String staffImage;
+
+    /**
+     * 员工职位/角色(如:贝斯)
+     */
+    @ApiModelProperty(value = "员工职位/角色(如:贝斯)")
+    private String staffPosition;
+
+    /**
+     * 擅长项目/风格(如:民谣)
+     */
+    @ApiModelProperty(value = "擅长项目/风格(如:民谣)")
+    private String proficientProjects;
+
+    /**
+     * 点赞数量
+     */
+    @ApiModelProperty(value = "点赞数量")
+    private Integer likeCount;
+}
+

+ 66 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceListVo.java

@@ -0,0 +1,66 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 演出列表VO
+ * 用于展示演出列表信息
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "PerformanceListVo对象", description = "演出列表")
+public class PerformanceListVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 演出ID
+     */
+    @ApiModelProperty(value = "演出ID")
+    private Integer id;
+
+    /**
+     * 演出名称
+     */
+    @ApiModelProperty(value = "演出名称")
+    private String performanceName;
+
+    /**
+     * 演出海报路径
+     */
+    @ApiModelProperty(value = "演出海报路径")
+    private String performancePoster;
+
+    /**
+     * 演出者信息(如:刘能等7人)
+     */
+    @ApiModelProperty(value = "演出者信息(如:刘能等7人)")
+    private String performersInfo;
+
+    /**
+     * 日期范围(格式:2025.11.11-2027.01.31)
+     */
+    @ApiModelProperty(value = "日期范围(格式:2025.11.11-2027.01.31)")
+    private String dateRange;
+
+    /**
+     * 演出时间安排(格式:每周一、二、三、四19:30-次日00:00)
+     */
+    @ApiModelProperty(value = "演出时间安排(格式:每周一、二、三、四19:30-次日00:00)")
+    private String scheduleInfo;
+
+    /**
+     * 演出类型(0-特邀演出,1-常规演出)
+     */
+    @ApiModelProperty(value = "演出类型(0-特邀演出,1-常规演出)")
+    private Integer performanceType;
+}
+

+ 54 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceScheduleVo.java

@@ -0,0 +1,54 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 演出安排VO
+ * 用于展示员工的演出时间安排
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "PerformanceScheduleVo对象", description = "演出安排")
+public class PerformanceScheduleVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 演出日期(格式:12月12日)
+     */
+    @ApiModelProperty(value = "演出日期(格式:12月12日)")
+    private String dateStr;
+
+    /**
+     * 星期几(格式:周五)
+     */
+    @ApiModelProperty(value = "星期几(格式:周五)")
+    private String weekDayStr;
+
+    /**
+     * 演出时间范围(格式:20:30-次日00:00)
+     */
+    @ApiModelProperty(value = "演出时间范围(格式:20:30-次日00:00)")
+    private String timeRange;
+
+    /**
+     * 演出ID
+     */
+    @ApiModelProperty(value = "演出ID")
+    private Integer performanceId;
+
+    /**
+     * 演出名称
+     */
+    @ApiModelProperty(value = "演出名称")
+    private String performanceName;
+}
+

+ 50 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StaffTitleGroupVo.java

@@ -0,0 +1,50 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.store.StoreStaffConfig;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 员工标题分组VO
+ * 用于按标题分组展示员工列表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StaffTitleGroupVo对象", description = "员工标题分组")
+public class StaffTitleGroupVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 标题ID
+     */
+    @ApiModelProperty(value = "标题ID")
+    private Integer titleId;
+
+    /**
+     * 标题名称(人员阵容名称)
+     */
+    @ApiModelProperty(value = "标题名称(人员阵容名称)")
+    private String titleName;
+
+    /**
+     * 员工数量
+     */
+    @ApiModelProperty(value = "员工数量")
+    private Integer staffCount;
+
+    /**
+     * 员工列表
+     */
+    @ApiModelProperty(value = "员工列表")
+    private List<StoreStaffConfig> staffList;
+}
+

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOfficialAlbumImgVo.java

@@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.StoreVideo;
 
 import java.util.List;
 
@@ -25,5 +26,8 @@ public class StoreOfficialAlbumImgVo {
     @ApiModelProperty(value = "图片总数")
     private Integer totalCount;
 
+    @ApiModelProperty(value = "视频列表")
+    private List<StoreVideo> videoList;
+
 }
 

+ 76 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffCommentVo.java

@@ -0,0 +1,76 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 员工评论VO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreStaffCommentVo对象", description = "员工评论VO")
+public class StoreStaffCommentVo {
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "评价ID")
+    private Integer reviewId;
+
+    @ApiModelProperty(value = "发送用户类型:1-普通用户,2-员工")
+    private Integer sendUserType;
+
+    @ApiModelProperty(value = "发送用户ID")
+    private Integer sendUserId;
+
+    @ApiModelProperty(value = "发送用户名称")
+    private String sendUserName;
+
+    @ApiModelProperty(value = "发送用户头像")
+    private String sendUserAvatar;
+
+    @ApiModelProperty(value = "接收用户类型:1-普通用户,2-员工")
+    private Integer receiveUserType;
+
+    @ApiModelProperty(value = "接收用户ID")
+    private Integer receiveUserId;
+
+    @ApiModelProperty(value = "接收用户名称")
+    private String receiveUserName;
+
+    @ApiModelProperty(value = "接收用户头像")
+    private String receiveUserAvatar;
+
+    @ApiModelProperty(value = "评论内容")
+    private String commentContent;
+
+    @ApiModelProperty(value = "点赞数")
+    private Integer likeCount;
+
+    @ApiModelProperty(value = "回复数")
+    private Integer replyCount;
+
+    @ApiModelProperty(value = "首评标记, 0:是, 1:否")
+    private Integer headType;
+
+    @ApiModelProperty(value = "首评ID(仅子评论存在该属性)")
+    private Integer headId;
+
+    @ApiModelProperty(value = "当前用户是否已点赞,0:未点赞,1:已点赞")
+    private Integer isLiked;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "回复列表(仅首评存在)")
+    private List<StoreStaffCommentVo> replyList;
+}
+

+ 44 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffDetailWithPerformanceVo.java

@@ -0,0 +1,44 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.store.StoreStaffConfig;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 员工详情(包含演出列表)VO
+ * 用于返回员工基本信息和演出安排列表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreStaffDetailWithPerformanceVo对象", description = "员工详情(包含演出列表)")
+public class StoreStaffDetailWithPerformanceVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 员工基本信息
+     */
+    @ApiModelProperty(value = "员工基本信息")
+    private StoreStaffConfig staffInfo;
+
+    /**
+     * 演出安排列表
+     */
+    @ApiModelProperty(value = "演出安排列表")
+    private List<PerformanceScheduleVo> performanceScheduleList;
+
+    /**
+     * 是否今日演出(true-今日有演出,false-今日无演出)
+     */
+    @ApiModelProperty(value = "是否今日演出(true-今日有演出,false-今日无演出)")
+    private Boolean hasPerformanceToday;
+}
+

+ 29 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffReviewDetailVo.java

@@ -0,0 +1,29 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.store.StoreStaffConfig;
+
+import java.util.List;
+
+/**
+ * 员工评价详情VO(包含评论和回复)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreStaffReviewDetailVo对象", description = "员工评价详情VO")
+public class StoreStaffReviewDetailVo {
+
+    @ApiModelProperty(value = "评价信息")
+    private StoreStaffReviewVo review;
+
+    @ApiModelProperty(value = "评论列表(包含回复)")
+    private List<StoreStaffCommentVo> commentList;
+
+    @ApiModelProperty(value = "人员信息")
+    private StoreStaffConfig staffInfo;
+}
+

+ 25 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffReviewListWithStaffVo.java

@@ -0,0 +1,25 @@
+package shop.alien.entity.store.vo;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.store.StoreStaffConfig;
+
+/**
+ * 员工评价列表(包含人员信息)VO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreStaffReviewListWithStaffVo对象", description = "员工评价列表(包含人员信息)VO")
+public class StoreStaffReviewListWithStaffVo {
+
+    @ApiModelProperty(value = "人员信息")
+    private StoreStaffConfig staffInfo;
+
+    @ApiModelProperty(value = "评价列表")
+    private IPage<StoreStaffReviewVo> reviewList;
+}
+

+ 87 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffReviewVo.java

@@ -0,0 +1,87 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 员工评价VO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreStaffReviewVo对象", description = "员工评价VO")
+public class StoreStaffReviewVo {
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "评价用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "评价用户名称")
+    private String userName;
+
+    @ApiModelProperty(value = "评价用户头像")
+    private String userAvatar;
+
+    @ApiModelProperty(value = "员工用户ID")
+    private Integer staffUserId;
+
+    @ApiModelProperty(value = "员工名称")
+    private String staffName;
+
+    @ApiModelProperty(value = "员工头像")
+    private String staffAvatar;
+
+    @ApiModelProperty(value = "总体评分")
+    private BigDecimal overallRating;
+
+    @ApiModelProperty(value = "服务态度评分")
+    private BigDecimal serviceAttitudeRating;
+
+    @ApiModelProperty(value = "响应时间评分")
+    private BigDecimal responseTimeRating;
+
+    @ApiModelProperty(value = "专业能力评分")
+    private BigDecimal professionalAbilityRating;
+
+    @ApiModelProperty(value = "评价内容")
+    private String reviewContent;
+
+    @ApiModelProperty(value = "评价图片列表")
+    private List<String> reviewImages;
+
+    @ApiModelProperty(value = "评价图片JSON字符串(内部使用,不对外暴露)")
+    @com.fasterxml.jackson.annotation.JsonIgnore
+    private String reviewImagesJson;
+
+    @ApiModelProperty(value = "是否匿名评价,0:否,1:是")
+    private Integer isAnonymous;
+
+    @ApiModelProperty(value = "点赞数")
+    private Integer likeCount;
+
+    @ApiModelProperty(value = "评论数")
+    private Integer commentCount;
+
+    @ApiModelProperty(value = "当前用户是否已点赞,0:未点赞,1:已点赞")
+    private Integer isLiked;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "逻辑删除")
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "申诉id (如果该评价被申诉 申诉id会有值)")
+    private String appealId;
+}
+

+ 19 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreCuisineComboMapper.java

@@ -0,0 +1,19 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StoreCuisineCombo;
+
+/**
+ * 美食套餐表(辅助详情查询) Mapper 接口
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+@Mapper
+public interface StoreCuisineComboMapper extends BaseMapper<StoreCuisineCombo> {
+
+}
+
+
+

+ 19 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreCuisineMapper.java

@@ -0,0 +1,19 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StoreCuisine;
+
+/**
+ * 美食价目表 Mapper 接口
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+@Mapper
+public interface StoreCuisineMapper extends BaseMapper<StoreCuisine> {
+
+}
+
+
+

+ 13 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreImgMapper.java

@@ -40,4 +40,17 @@ public interface StoreImgMapper extends BaseMapper<StoreImg> {
     @Select("SELECT * " +
             "FROM store_img ${ew.customSqlSegment}")
     List<StoreImg> getImgsByGoodsIds(@Param(Constants.WRAPPER) QueryWrapper<StoreImg> queryWrapper);
+
+
+
+    /**
+     * 获取封面图片
+     *
+     * @param queryWrapper 商品id
+     * @return 商品图片
+     */
+    @Select("SELECT * " +
+            "FROM store_img ${ew.customSqlSegment}")
+    List<StoreImg> getByCover(@Param(Constants.WRAPPER) QueryWrapper<StoreImg> queryWrapper);
+
 }

+ 17 - 0
alien-entity/src/main/java/shop/alien/mapper/StorePriceMapper.java

@@ -0,0 +1,17 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StorePrice;
+
+/**
+ * 通用价目表 Mapper 接口
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+@Mapper
+public interface StorePriceMapper extends BaseMapper<StorePrice> {
+
+}
+

+ 54 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreStaffCommentMapper.java

@@ -0,0 +1,54 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.store.StoreStaffComment;
+import shop.alien.entity.store.vo.StoreStaffCommentVo;
+
+import java.util.List;
+
+/**
+ * 员工评论 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StoreStaffCommentMapper extends BaseMapper<StoreStaffComment> {
+
+    /**
+     * 根据评价ID查询评论列表(包含用户信息)
+     *
+     * @param reviewId 评价ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return 评论列表
+     */
+    List<StoreStaffCommentVo> getCommentListByReviewId(@Param("reviewId") Integer reviewId, @Param("currentUserId") Integer currentUserId);
+
+    /**
+     * 根据评价ID查询评论数量(只统计首评论)
+     *
+     * @param reviewId 评价ID
+     * @return 评论数量
+     */
+    Integer getCommentCountByReviewId(@Param("reviewId") Integer reviewId);
+
+    /**
+     * 根据评价ID查询总评论数量(包括首评论和子评论)
+     *
+     * @param reviewId 评价ID
+     * @return 总评论数量
+     */
+    Integer getTotalCommentCountByReviewId(@Param("reviewId") Integer reviewId);
+
+    /**
+     * 根据首评ID查询回复列表(包含用户信息)
+     *
+     * @param headId 首评ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return 回复列表
+     */
+    List<StoreStaffCommentVo> getReplyListByHeadId(@Param("headId") Integer headId, @Param("currentUserId") Integer currentUserId);
+}
+

+ 52 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreStaffReviewMapper.java

@@ -0,0 +1,52 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.store.StoreStaffReview;
+import shop.alien.entity.store.vo.StoreStaffReviewVo;
+
+/**
+ * 员工评价 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StoreStaffReviewMapper extends BaseMapper<StoreStaffReview> {
+
+    /**
+     * 分页查询评价列表(包含用户和员工信息)
+     *
+     * @param page 分页对象
+     * @param staffUserId 员工用户ID
+     * @param userId 评价用户ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return 分页结果
+     */
+    IPage<StoreStaffReviewVo> getReviewListWithUser(
+            IPage<StoreStaffReviewVo> page,
+            @Param("staffUserId") Integer staffUserId,
+            @Param("userId") Integer userId,
+            @Param("currentUserId") Integer currentUserId
+    );
+
+    /**
+     * 根据评价ID查询评价详情(包含用户和员工信息)
+     *
+     * @param reviewId 评价ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return 评价详情
+     */
+    StoreStaffReviewVo getReviewDetailById(@Param("reviewId") Integer reviewId, @Param("currentUserId") Integer currentUserId);
+
+    /**
+     * 计算员工的平均评分(overallRating的平均值)
+     *
+     * @param staffUserId 员工用户ID
+     * @return 平均评分(1-5星)
+     */
+    java.math.BigDecimal getAverageRatingByStaffUserId(@Param("staffUserId") Integer staffUserId);
+}
+

+ 14 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreStaffTitleMapper.java

@@ -0,0 +1,14 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import shop.alien.entity.store.StoreStaffTitle;
+
+/**
+ * 员工标题Mapper接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreStaffTitleMapper extends BaseMapper<StoreStaffTitle> {
+}
+

+ 16 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreVideoMapper.java

@@ -0,0 +1,16 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StoreVideo;
+
+/**
+ * 门店视频 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StoreVideoMapper extends BaseMapper<StoreVideo> {
+}
+

+ 29 - 0
alien-entity/src/main/resources/mapper/StoreCuisineComboMapper.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.StoreCuisineComboMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.StoreCuisineCombo">
+        <id column="id" property="id" />
+        <result column="sid" property="sid" />
+        <result column="cid" property="cid" />
+        <result column="snum" property="snum" />
+        <result column="category" property="category" />
+        <result column="delete_flag" property="deleteFlag" />
+        <result column="created_user_id" property="createdUserId" />
+        <result column="updated_user_id" property="updatedUserId" />
+        <result column="created_time" property="createdTime" />
+        <result column="updated_time" property="updatedTime" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, sid, cid, snum, category, delete_flag, created_user_id, updated_user_id, created_time, updated_time
+    </sql>
+
+</mapper>
+
+
+

+ 41 - 0
alien-entity/src/main/resources/mapper/StoreCuisineMapper.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.StoreCuisineMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.StoreCuisine">
+        <id column="id" property="id" />
+        <result column="stroe_id" property="stroeId" />
+        <result column="name" property="name" />
+        <result column="total_price" property="totalPrice" />
+        <result column="images" property="images" />
+        <result column="image_content" property="imageContent" />
+        <result column="raw_json" property="rawJson" />
+        <result column="detail_content" property="detailContent" />
+        <result column="extra_note" property="extraNote" />
+        <result column="need_reserve" property="needReserve" />
+        <result column="reserve_rule" property="reserveRule" />
+        <result column="people_limit" property="peopleLimit" />
+        <result column="usage_rule" property="usageRule" />
+        <result column="status" property="status" />
+        <result column="rejection_reason" property="rejectionReason" />
+        <result column="shelf_status" property="shelfStatus" />
+        <result column="delete_flag" property="deleteFlag" />
+        <result column="created_user_id" property="createdUserId" />
+        <result column="updated_user_id" property="updatedUserId" />
+        <result column="created_time" property="createdTime" />
+        <result column="updated_time" property="updatedTime" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, stroe_id, name, total_price, images, image_content, raw_json, detail_content,
+        extra_note, need_reserve, reserve_rule, people_limit, usage_rule, status, shelf_status,
+        rejection_reason, delete_flag, created_user_id, updated_user_id, created_time, updated_time
+    </sql>
+
+</mapper>
+
+

+ 184 - 0
alien-entity/src/main/resources/mapper/StoreStaffCommentMapper.xml

@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.StoreStaffCommentMapper">
+
+    <!-- 评论列表查询结果映射 -->
+    <resultMap id="StoreStaffCommentVoResultMap" type="shop.alien.entity.store.vo.StoreStaffCommentVo">
+        <id column="id" property="id" />
+        <result column="review_id" property="reviewId" />
+        <result column="send_user_id" property="sendUserId" />
+        <result column="receive_user_id" property="receiveUserId" />
+        <result column="send_user_name" property="sendUserName" />
+        <result column="send_user_avatar" property="sendUserAvatar" />
+        <result column="receive_user_name" property="receiveUserName" />
+        <result column="receive_user_avatar" property="receiveUserAvatar" />
+        <result column="send_user_type" property="sendUserType" />
+        <result column="receive_user_type" property="receiveUserType" />
+        <result column="comment_content" property="commentContent" />
+        <result column="like_count" property="likeCount" />
+        <result column="reply_count" property="replyCount" />
+        <result column="head_type" property="headType" />
+        <result column="head_id" property="headId" />
+        <result column="is_liked" property="isLiked" />
+        <result column="created_time" property="createdTime" />
+    </resultMap>
+
+    <!-- 根据评价ID查询评论列表(包含用户信息) -->
+    <select id="getCommentListByReviewId" resultMap="StoreStaffCommentVoResultMap">
+        SELECT
+            ssc.id,
+            ssc.review_id,
+            ssc.send_user_id,
+            ssc.receive_user_id,
+            -- 发送用户名称:根据 sendUserType 分别查询普通用户表和员工表
+            CASE
+                WHEN ssc.send_user_type = 2 THEN sp_send.personnel_name
+                WHEN ssc.send_user_type = 1 OR ssc.send_user_type IS NULL THEN lu_send.user_name
+                ELSE lu_send.user_name
+            END AS send_user_name,
+            -- 发送用户头像:根据 sendUserType 分别查询普通用户表和员工表
+            CASE
+                WHEN ssc.send_user_type = 2 THEN si_send.img_url
+                WHEN ssc.send_user_type = 1 OR ssc.send_user_type IS NULL THEN lu_send.user_image
+                ELSE lu_send.user_image
+            END AS send_user_avatar,
+            -- 接收用户名称:根据 receiveUserType 分别查询普通用户表和员工表
+            CASE
+                WHEN ssc.receive_user_type = 2 THEN sp_receive.personnel_name
+                WHEN ssc.receive_user_type = 1 OR ssc.receive_user_type IS NULL THEN lu_receive.user_name
+                ELSE NULL
+            END AS receive_user_name,
+            -- 接收用户头像:根据 receiveUserType 分别查询普通用户表和员工表
+            CASE
+                WHEN ssc.receive_user_type = 2 THEN si_receive.img_url
+                WHEN ssc.receive_user_type = 1 OR ssc.receive_user_type IS NULL THEN lu_receive.user_image
+                ELSE NULL
+            END AS receive_user_avatar,
+            ssc.send_user_type,
+            ssc.receive_user_type,
+            ssc.comment_content,
+            ssc.like_count,
+            ssc.reply_count,
+            ssc.head_type,
+            ssc.head_id,
+            CASE 
+                WHEN #{currentUserId} IS NOT NULL AND llr.id IS NOT NULL THEN 1
+                ELSE 0
+            END AS is_liked,
+            ssc.created_time
+        FROM store_staff_comment ssc
+        -- 发送用户:普通用户表
+        LEFT JOIN life_user lu_send ON lu_send.id = ssc.send_user_id 
+            AND lu_send.delete_flag = 0
+        -- 发送用户:员工表
+        LEFT JOIN store_personnel sp_send ON sp_send.id = ssc.send_user_id 
+            AND sp_send.delete_flag = 0
+        LEFT JOIN store_img si_send ON si_send.id = sp_send.img_id 
+            AND si_send.delete_flag = 0
+        -- 接收用户:普通用户表
+        LEFT JOIN life_user lu_receive ON lu_receive.id = ssc.receive_user_id 
+            AND lu_receive.delete_flag = 0
+        -- 接收用户:员工表
+        LEFT JOIN store_personnel sp_receive ON sp_receive.id = ssc.receive_user_id 
+            AND sp_receive.delete_flag = 0
+        LEFT JOIN store_img si_receive ON si_receive.id = sp_receive.img_id 
+            AND si_receive.delete_flag = 0
+        LEFT JOIN life_like_record llr ON CONVERT(llr.huifu_id, CHAR) = CONVERT(ssc.id, CHAR)
+            AND llr.type = '10' 
+            AND CONVERT(llr.dianzan_id, CHAR) = CONVERT(#{currentUserId}, CHAR)
+            AND llr.delete_flag = 0
+        WHERE ssc.delete_flag = 0
+        AND ssc.review_id = #{reviewId}
+        AND ssc.head_type = 0
+        ORDER BY ssc.created_time DESC
+    </select>
+
+    <!-- 根据首评ID查询回复列表(包含用户信息) -->
+    <select id="getReplyListByHeadId" resultMap="StoreStaffCommentVoResultMap">
+        SELECT
+            ssc.id,
+            ssc.review_id,
+            ssc.send_user_id,
+            ssc.receive_user_id,
+            -- 发送用户名称:根据 sendUserType 分别查询普通用户表和员工表
+            CASE
+                WHEN ssc.send_user_type = 2 THEN sp_send.personnel_name
+                WHEN ssc.send_user_type = 1 OR ssc.send_user_type IS NULL THEN lu_send.user_name
+                ELSE lu_send.user_name
+            END AS send_user_name,
+            -- 发送用户头像:根据 sendUserType 分别查询普通用户表和员工表
+            CASE
+                WHEN ssc.send_user_type = 2 THEN si_send.img_url
+                WHEN ssc.send_user_type = 1 OR ssc.send_user_type IS NULL THEN lu_send.user_image
+                ELSE lu_send.user_image
+            END AS send_user_avatar,
+            -- 接收用户名称:根据 receiveUserType 分别查询普通用户表和员工表
+            CASE
+                WHEN ssc.receive_user_type = 2 THEN sp_receive.personnel_name
+                WHEN ssc.receive_user_type = 1 OR ssc.receive_user_type IS NULL THEN lu_receive.user_name
+                ELSE NULL
+            END AS receive_user_name,
+            -- 接收用户头像:根据 receiveUserType 分别查询普通用户表和员工表
+            CASE
+                WHEN ssc.receive_user_type = 2 THEN si_receive.img_url
+                WHEN ssc.receive_user_type = 1 OR ssc.receive_user_type IS NULL THEN lu_receive.user_image
+                ELSE NULL
+            END AS receive_user_avatar,
+            ssc.send_user_type,
+            ssc.receive_user_type,
+            ssc.comment_content,
+            ssc.like_count,
+            ssc.reply_count,
+            ssc.head_type,
+            ssc.head_id,
+            CASE 
+                WHEN #{currentUserId} IS NOT NULL AND llr.id IS NOT NULL THEN 1
+                ELSE 0
+            END AS is_liked,
+            ssc.created_time
+        FROM store_staff_comment ssc
+        -- 发送用户:普通用户表
+        LEFT JOIN life_user lu_send ON lu_send.id = ssc.send_user_id 
+            AND lu_send.delete_flag = 0
+        -- 发送用户:员工表
+        LEFT JOIN store_personnel sp_send ON sp_send.id = ssc.send_user_id 
+            AND sp_send.delete_flag = 0
+        LEFT JOIN store_img si_send ON si_send.id = sp_send.img_id 
+            AND si_send.delete_flag = 0
+        -- 接收用户:普通用户表
+        LEFT JOIN life_user lu_receive ON lu_receive.id = ssc.receive_user_id 
+            AND lu_receive.delete_flag = 0
+        -- 接收用户:员工表
+        LEFT JOIN store_personnel sp_receive ON sp_receive.id = ssc.receive_user_id 
+            AND sp_receive.delete_flag = 0
+        LEFT JOIN store_img si_receive ON si_receive.id = sp_receive.img_id 
+            AND si_receive.delete_flag = 0
+        LEFT JOIN life_like_record llr ON CONVERT(llr.huifu_id, CHAR) = CONVERT(ssc.id, CHAR)
+            AND llr.type = '10' 
+            AND CONVERT(llr.dianzan_id, CHAR) = CONVERT(#{currentUserId}, CHAR)
+            AND llr.delete_flag = 0
+        WHERE ssc.delete_flag = 0
+        AND ssc.head_id = #{headId}
+        AND ssc.head_type = 1
+        ORDER BY ssc.created_time ASC
+    </select>
+
+    <!-- 根据评价ID查询评论数量(只统计首评论) -->
+    <select id="getCommentCountByReviewId" resultType="java.lang.Integer">
+        SELECT COUNT(*)
+        FROM store_staff_comment
+        WHERE delete_flag = 0
+        AND review_id = #{reviewId}
+        AND head_type = 0
+    </select>
+
+    <!-- 根据评价ID查询总评论数量(包括首评论和子评论) -->
+    <select id="getTotalCommentCountByReviewId" resultType="java.lang.Integer">
+        SELECT COUNT(*)
+        FROM store_staff_comment
+        WHERE delete_flag = 0
+        AND review_id = #{reviewId}
+    </select>
+
+</mapper>
+

+ 132 - 0
alien-entity/src/main/resources/mapper/StoreStaffReviewMapper.xml

@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.StoreStaffReviewMapper">
+
+    <!-- 评价列表查询结果映射 -->
+    <resultMap id="StoreStaffReviewVoResultMap" type="shop.alien.entity.store.vo.StoreStaffReviewVo">
+        <id column="id" property="id" />
+        <result column="user_id" property="userId" />
+        <result column="user_name" property="userName" />
+        <result column="user_avatar" property="userAvatar" />
+        <result column="staff_user_id" property="staffUserId" />
+        <result column="staff_name" property="staffName" />
+        <result column="staff_avatar" property="staffAvatar" />
+        <result column="overall_rating" property="overallRating" />
+        <result column="service_attitude_rating" property="serviceAttitudeRating" />
+        <result column="response_time_rating" property="responseTimeRating" />
+        <result column="professional_ability_rating" property="professionalAbilityRating" />
+        <result column="review_content" property="reviewContent" />
+        <result column="review_images" property="reviewImagesJson" />
+        <result column="is_anonymous" property="isAnonymous" />
+        <result column="like_count" property="likeCount" />
+        <result column="comment_count" property="commentCount" />
+        <result column="is_liked" property="isLiked" />
+        <result column="created_time" property="createdTime" />
+        <result column="appeal_id" property="appealId" />
+    </resultMap>
+
+    <!-- 分页查询评价列表(包含用户和员工信息) -->
+    <select id="getReviewListWithUser" resultMap="StoreStaffReviewVoResultMap">
+        SELECT
+            ssr.id,
+            ssr.user_id,
+            CASE 
+                WHEN ssr.is_anonymous = 1 THEN '匿名用户'
+                ELSE lu.user_name
+            END AS user_name,
+            CASE 
+                WHEN ssr.is_anonymous = 1 THEN NULL
+                ELSE lu.user_image
+            END AS user_avatar,
+            ssr.staff_user_id,
+            sp.personnel_name AS staff_name,
+            si.img_url AS staff_avatar,
+            ssr.overall_rating,
+            ssr.service_attitude_rating,
+            ssr.response_time_rating,
+            ssr.professional_ability_rating,
+            ssr.review_content,
+            ssr.is_anonymous,
+            ssr.like_count,
+            ssr.comment_count,
+            ssr.review_images,
+            CASE 
+                WHEN #{currentUserId} IS NOT NULL AND llr.id IS NOT NULL THEN 1
+                ELSE 0
+            END AS is_liked,
+            ssr.created_time,
+            ssr.appeal_id
+        FROM store_staff_review ssr
+        LEFT JOIN life_user lu ON lu.id = ssr.user_id AND lu.delete_flag = 0
+        LEFT JOIN store_personnel sp ON sp.id = ssr.staff_user_id AND sp.delete_flag = 0
+        LEFT JOIN store_img si ON si.id = sp.img_id AND si.delete_flag = 0
+        LEFT JOIN life_like_record llr ON CONVERT(llr.huifu_id, CHAR) = CONVERT(ssr.id, CHAR)
+            AND llr.type = '9' 
+            AND CONVERT(llr.dianzan_id, CHAR) = CONVERT(#{currentUserId}, CHAR)
+            AND llr.delete_flag = 0
+        WHERE ssr.delete_flag = 0
+        <if test="staffUserId != null">
+            AND ssr.staff_user_id = #{staffUserId}
+        </if>
+        <if test="userId != null">
+            AND ssr.user_id = #{userId}
+        </if>
+        ORDER BY ssr.created_time DESC
+    </select>
+
+    <!-- 根据评价ID查询评价详情(包含用户和员工信息) -->
+    <select id="getReviewDetailById" resultMap="StoreStaffReviewVoResultMap">
+        SELECT
+            ssr.id,
+            ssr.user_id,
+            CASE 
+                WHEN ssr.is_anonymous = 1 THEN '匿名用户'
+                ELSE lu.user_name
+            END AS user_name,
+            CASE 
+                WHEN ssr.is_anonymous = 1 THEN NULL
+                ELSE lu.user_image
+            END AS user_avatar,
+            ssr.staff_user_id,
+            sp.personnel_name AS staff_name,
+            si.img_url AS staff_avatar,
+            ssr.overall_rating,
+            ssr.service_attitude_rating,
+            ssr.response_time_rating,
+            ssr.professional_ability_rating,
+            ssr.review_content,
+            ssr.is_anonymous,
+            ssr.like_count,
+            ssr.comment_count,
+            ssr.review_images,
+            CASE 
+                WHEN #{currentUserId} IS NOT NULL AND llr.id IS NOT NULL THEN 1
+                ELSE 0
+            END AS is_liked,
+            ssr.created_time,
+            ssr.appeal_id
+        FROM store_staff_review ssr
+        LEFT JOIN life_user lu ON lu.id = ssr.user_id AND lu.delete_flag = 0
+        LEFT JOIN store_personnel sp ON sp.id = ssr.staff_user_id AND sp.delete_flag = 0
+        LEFT JOIN store_img si ON si.id = sp.img_id AND si.delete_flag = 0
+        LEFT JOIN life_like_record llr ON CONVERT(llr.huifu_id, CHAR) = CONVERT(ssr.id, CHAR)
+            AND llr.type = '9' 
+            AND CONVERT(llr.dianzan_id, CHAR) = CONVERT(#{currentUserId}, CHAR)
+            AND llr.delete_flag = 0
+        WHERE ssr.delete_flag = 0
+        AND ssr.id = #{reviewId}
+        LIMIT 1
+    </select>
+
+    <!-- 计算员工的平均评分(overallRating的平均值) -->
+    <select id="getAverageRatingByStaffUserId" resultType="java.math.BigDecimal">
+        SELECT 
+            COALESCE(AVG(overall_rating), 0) AS average_rating
+        FROM store_staff_review
+        WHERE staff_user_id = #{staffUserId}
+          AND delete_flag = 0
+          AND overall_rating IS NOT NULL
+    </select>
+
+</mapper>
+

+ 5 - 4
alien-gateway/src/main/resources/bootstrap-dev.yml

@@ -6,15 +6,16 @@ spring:
     nacos:
       #注册中心
       discovery:
-        server-addr: 192.168.2.252:8848
+        server-addr: 8.130.79.5:8848
         username: nacos
-        password: ngfriend198092
+        password: Alien123456
 
       #配置中心
       config:
         enabled: true
-        server-addr: 192.168.2.252:8848
+        refresh-enabled: true
+        server-addr: 8.130.79.5:8848
         username: nacos
-        password: ngfriend198092
+        password: Alien123456
         group: DEFAULT_GROUP
         file-extension: yml

+ 1 - 0
alien-gateway/src/main/resources/bootstrap-prod.yml

@@ -14,6 +14,7 @@ spring:
       #配置中心
       config:
         enabled: true
+        refresh-enabled: true
         server-addr: localhost:8848
         username: nacos
         password: ngfriend198092

+ 7 - 6
alien-gateway/src/main/resources/bootstrap-test.yml

@@ -6,17 +6,18 @@ spring:
     nacos:
       #注册中心
       discovery:
-        server-addr: 192.168.2.252:8848
+        server-addr: 8.130.79.5:8848
         username: nacos
-        password: ngfriend198092
-        namespace: 0e1e2d77-56e8-422c-8317-6f71d7285e59
+        password: Alien123456
+        namespace: 00af4930-5bd5-4994-a6f6-591675a2949d
 
       #配置中心
       config:
         enabled: true
-        server-addr: 192.168.2.252:8848
+        refresh-enabled: true
+        server-addr: 8.130.79.5:8848
         username: nacos
-        password: ngfriend198092
+        password: Alien123456
         group: DEFAULT_GROUP
         file-extension: yml
-        namespace: 0e1e2d77-56e8-422c-8317-6f71d7285e59
+        namespace: 00af4930-5bd5-4994-a6f6-591675a2949d

+ 1 - 0
alien-gateway/src/main/resources/bootstrap-uat.yml

@@ -14,6 +14,7 @@ spring:
       #配置中心
       config:
         enabled: true
+        refresh-enabled: true
         server-addr: localhost:8848
         username: nacos
         password: ngfriend198092

+ 1 - 1
alien-gateway/src/main/resources/bootstrap.yml

@@ -1,3 +1,3 @@
 spring:
   profiles:
-    active: test
+    active: dev

+ 175 - 172
alien-gateway/src/main/resources/logback-spring.xml

@@ -1,173 +1,176 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
-<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
-<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
-<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
-<!-- 该信息是由于设置了当配置文件变化时重新加载,所以每当达到扫描时间的时候就会检查配置文件是否错误。但是由于一般配置文件都放在了JAR包中,
-    而扫描的时候无法扫描JAR包内,因此会提示没有可以检查的文件,所以每隔一段时间就输出一次-->
-<configuration scan="false" scanPeriod="60 seconds" debug="true">
-    <contextName>logback-spring</contextName>
-
-    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
-    <!-- 定义全局参数常量 -->
-    <property name="log.level" value="debug"/>
-    <property name="log.maxHistory" value="30"/><!-- 30表示30个 -->
-    <springProperty scope="context" name="logging.path" source="logging.path"/>
-    <!--输出文件前缀-->
-    <property name="FILENAME" value="xiaokuihua_gateway"/>
-
-    <!--0. 日志格式和颜色渲染 -->
-    <!-- 彩色日志依赖的渲染类 -->
-    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
-    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
-    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
-
-    <!-- 文件输出格式 -->
-    <property name="FILE_LOG_PATTERN" value="[%d{MM/dd HH:mm:ss.SSS}][%-10.10thread][%-5level][%-40.40c{1}:%5line]:[%15method] || %m%n"/>
-    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
-
-    <!--1. 输出到控制台-->
-    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
-        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
-        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
-            <level>${log.level}</level>
-        </filter>
-        <encoder>
-            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
-            <!-- 设置字符集 -->
-            <charset>UTF-8</charset>
-        </encoder>
-    </appender>
-
-    <!--2. 输出到文档-->
-    <!-- DEBUG 日志 -->
-    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
-        <!-- 当前的日志文件存放路径 -->
-        <file>${logging.path}/DEBUG.log</file>
-        <!-- 日志滚动策略 -->
-        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <!-- 历史日志文件的存放路径和名称 -->
-            <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_DEBUG.log.gz</fileNamePattern>
-            <!-- 日志文件最大的保存历史 数量-->
-            <maxHistory>${log.maxHistory}</maxHistory>
-        </rollingPolicy>
-        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
-            <pattern>${FILE_LOG_PATTERN}</pattern>
-        </encoder>
-        <!--日志文件最大的大小-->
-        <!--        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
-        <!--            <MaxFileSize>10MB</MaxFileSize>-->
-        <!--        </triggeringPolicy>-->
-        <!-- 此日志文档只记录debug级别的 -->
-        <filter class="ch.qos.logback.classic.filter.LevelFilter">
-            <level>DEBUG</level>
-            <onMatch>ACCEPT</onMatch>  <!-- 用过滤器,只接受DEBUG级别的日志信息,其余全部过滤掉 -->
-            <onMismatch>DENY</onMismatch>
-        </filter>
-    </appender>
-
-    <!-- INFO 日志 -->
-    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
-        <file>${logging.path}/INFO.log</file>
-        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_INFO.log.gz</fileNamePattern>
-            <maxHistory>${log.maxHistory}</maxHistory>
-        </rollingPolicy>
-        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <pattern>${FILE_LOG_PATTERN}</pattern>
-        </encoder>
-        <filter class="ch.qos.logback.classic.filter.LevelFilter">
-            <level>INFO</level>
-            <onMatch>ACCEPT</onMatch>
-            <onMismatch>DENY</onMismatch>
-        </filter>
-    </appender>
-
-    <!-- WARN 日志 -->
-    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
-        <file>${logging.path}/WARN.log</file>
-        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_WARN.log.gz</fileNamePattern>
-            <maxHistory>${log.maxHistory}</maxHistory>
-        </rollingPolicy>
-        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <pattern>${FILE_LOG_PATTERN}</pattern>
-        </encoder>
-        <filter class="ch.qos.logback.classic.filter.LevelFilter">
-            <level>WARN</level>
-            <onMatch>ACCEPT</onMatch>
-            <onMismatch>DENY</onMismatch>
-        </filter>
-    </appender>
-
-    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
-        <file>${logging.path}/ERROR.log</file>
-        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_ERROR.log.gz</fileNamePattern>
-            <maxHistory>${log.maxHistory}</maxHistory>
-        </rollingPolicy>
-        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <pattern>${FILE_LOG_PATTERN}</pattern>
-        </encoder>
-        <filter class="ch.qos.logback.classic.filter.LevelFilter">
-            <level>ERROR</level>
-            <onMatch>ACCEPT</onMatch>
-            <onMismatch>DENY</onMismatch>
-        </filter>
-    </appender>
-
-    <!--
-      <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
-      以及指定<appender>。<logger>仅有一个name属性,
-      一个可选的level和一个可选的addtivity属性。
-      name:用来指定受此logger约束的某一个包或者具体的某一个类。
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-         还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
-         如果未设置此属性,那么当前logger将会继承上级的级别。
-      addtivity:是否向上级logger传递打印信息。默认是true。
-      <logger name="org.springframework.web" level="info"/>
-      <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
-    -->
-
-    <!--
-      使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
-      第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
-      第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
-      【logging.level.org.mybatis=debug logging.level.dao=debug】
-     -->
-    <!-- mybatis显示sql,修改此处扫描包名 -->
-
-
-    <!--
-      root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-      不能设置为INHERITED或者同义词NULL。默认是DEBUG
-      可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-    -->
-
-    <!-- 4. 最终的策略 -->
-    <!-- 4.1 开发环境:打印控制台-->
-    <!--打印sql-->
-    <!--    <logger name="com.veryhappy.music.dao" level="debug"/>-->
-
-    <!--打印log-->
-    <root level="info">
-        <appender-ref ref="CONSOLE"/>
-        <appender-ref ref="DEBUG_FILE"/>
-        <appender-ref ref="INFO_FILE"/>
-        <appender-ref ref="WARN_FILE"/>
-        <appender-ref ref="ERROR_FILE"/>
-    </root>
-
-    <!--   4.2 生产环境:输出到文档-->
-    <springProfile name="pro">
-        <root level="info">
-            <appender-ref ref="CONSOLE"/>
-            <appender-ref ref="DEBUG_FILE"/>
-            <appender-ref ref="INFO_FILE"/>
-            <appender-ref ref="ERROR_FILE"/>
-            <appender-ref ref="WARN_FILE"/>
-        </root>
-    </springProfile>
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
+<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
+<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
+<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
+<!-- 该信息是由于设置了当配置文件变化时重新加载,所以每当达到扫描时间的时候就会检查配置文件是否错误。但是由于一般配置文件都放在了JAR包中,
+    而扫描的时候无法扫描JAR包内,因此会提示没有可以检查的文件,所以每隔一段时间就输出一次-->
+<configuration scan="false" scanPeriod="60 seconds" debug="true">
+    <contextName>logback-spring</contextName>
+
+    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
+    <!-- 定义全局参数常量 -->
+    <property name="log.level" value="debug"/>
+    <property name="log.maxHistory" value="30"/><!-- 30表示30个 -->
+    <springProperty scope="context" name="logging.path" source="logging.path"   defaultValue="D:/project/ext/log"/>
+    <!--输出文件前缀-->
+    <property name="FILENAME" value="xiaokuihua_gateway"/>
+
+    <!--0. 日志格式和颜色渲染 -->
+    <!-- 彩色日志依赖的渲染类 -->
+    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
+    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
+    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+
+    <!-- 文件输出格式 -->
+    <property name="FILE_LOG_PATTERN" value="[%d{MM/dd HH:mm:ss.SSS}][%-10.10thread][%-5level][%-40.40c{1}:%5line]:[%15method] || %m%n"/>
+    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+
+    <!--1. 输出到控制台-->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>${log.level}</level>
+        </filter>
+        <encoder>
+            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
+            <!-- 设置字符集 -->
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!--2. 输出到文档-->
+    <!-- DEBUG 日志 -->
+    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 当前的日志文件存放路径 -->
+        <file>${logging.path}/DEBUG.log</file>
+        <!-- 日志滚动策略 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 历史日志文件的存放路径和名称 -->
+            <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_DEBUG.log.gz</fileNamePattern>
+            <!-- 日志文件最大的保存历史 数量-->
+            <maxHistory>${log.maxHistory}</maxHistory>
+        </rollingPolicy>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
+            <pattern>${FILE_LOG_PATTERN}</pattern>
+        </encoder>
+        <!--日志文件最大的大小-->
+        <!--        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
+        <!--            <MaxFileSize>10MB</MaxFileSize>-->
+        <!--        </triggeringPolicy>-->
+        <!-- 此日志文档只记录debug级别的 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>DEBUG</level>
+            <onMatch>ACCEPT</onMatch>  <!-- 用过滤器,只接受DEBUG级别的日志信息,其余全部过滤掉 -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- INFO 日志 -->
+    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${logging.path}/INFO.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_INFO.log.gz</fileNamePattern>
+            <maxHistory>${log.maxHistory}</maxHistory>
+        </rollingPolicy>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>${FILE_LOG_PATTERN}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>INFO</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- WARN 日志 -->
+    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${logging.path}/WARN.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_WARN.log.gz</fileNamePattern>
+            <maxHistory>${log.maxHistory}</maxHistory>
+        </rollingPolicy>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>${FILE_LOG_PATTERN}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>WARN</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${logging.path}/ERROR.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_ERROR.log.gz</fileNamePattern>
+            <maxHistory>${log.maxHistory}</maxHistory>
+        </rollingPolicy>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>${FILE_LOG_PATTERN}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!--
+      <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
+      以及指定<appender>。<logger>仅有一个name属性,
+      一个可选的level和一个可选的addtivity属性。
+      name:用来指定受此logger约束的某一个包或者具体的某一个类。
+      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+         还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
+         如果未设置此属性,那么当前logger将会继承上级的级别。
+      addtivity:是否向上级logger传递打印信息。默认是true。
+      <logger name="org.springframework.web" level="info"/>
+      <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
+    -->
+
+    <!--
+      使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
+      第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
+      第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
+      【logging.level.org.mybatis=debug logging.level.dao=debug】
+     -->
+    <!-- mybatis显示sql,修改此处扫描包名 -->
+
+
+    <!--
+      root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
+      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+      不能设置为INHERITED或者同义词NULL。默认是DEBUG
+      可以包含零个或多个元素,标识这个appender将会添加到这个logger。
+    -->
+
+    <!-- 关闭 Swagger/SpringDoc CachingOperationNameGenerator 的 INFO 日志 -->
+    <logger name="d.s.w.r.o.CachingOperationNameGenerator" level="WARN"/>
+
+    <!-- 4. 最终的策略 -->
+    <!-- 4.1 开发环境:打印控制台-->
+    <!--打印sql-->
+    <!--    <logger name="com.veryhappy.music.dao" level="debug"/>-->
+
+    <!--打印log-->
+    <root level="info">
+        <appender-ref ref="CONSOLE"/>
+        <appender-ref ref="DEBUG_FILE"/>
+        <appender-ref ref="INFO_FILE"/>
+        <appender-ref ref="WARN_FILE"/>
+        <appender-ref ref="ERROR_FILE"/>
+    </root>
+
+    <!--   4.2 生产环境:输出到文档-->
+    <springProfile name="pro">
+        <root level="info">
+            <appender-ref ref="CONSOLE"/>
+            <appender-ref ref="DEBUG_FILE"/>
+            <appender-ref ref="INFO_FILE"/>
+            <appender-ref ref="ERROR_FILE"/>
+            <appender-ref ref="WARN_FILE"/>
+        </root>
+    </springProfile>
 </configuration>

+ 49 - 14
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformDiscussionController.java

@@ -1,5 +1,6 @@
 package shop.alien.storeplatform.controller;
 
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -7,8 +8,10 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
-import shop.alien.storeplatform.entity.StoreDiscussion;
-import shop.alien.storeplatform.service.StoreDiscussionService;
+import shop.alien.storeplatform.entity.StorePlatformDiscussion;
+import shop.alien.storeplatform.service.StorePlatformDiscussionService;
+import shop.alien.storeplatform.vo.StorePlatformDiscussionReplyVo;
+import shop.alien.storeplatform.vo.StorePlatformDiscussionUserVo;
 
 import java.util.List;
 
@@ -25,34 +28,66 @@ import java.util.List;
 @RequiredArgsConstructor
 public class StorePlatformDiscussionController {
 
-    private final StoreDiscussionService storeDiscussionService;
+    private final StorePlatformDiscussionService storePlatformDiscussionService;
 
-    @ApiOperation("发表讨论或回复")
-    @PostMapping("/post")
-    public R<Boolean> post(@RequestBody StoreDiscussion discussion) {
-        log.info("StorePlatformDiscussionController.post?discussion={}", discussion);
+    @ApiOperation("发布一级问答讨论 (主贴)")
+    @PostMapping("/postTopic")
+    public R<Boolean> postTopic(@RequestBody StorePlatformDiscussion discussion) {
+        log.info("StorePlatformDiscussionController.postTopic?discussion={}", discussion);
         try {
-            boolean success = storeDiscussionService.postDiscussion(discussion);
-            return success ? R.success("发表成功") : R.fail("发表失败");
+            boolean success = storePlatformDiscussionService.postTopic(discussion);
+            return success ? R.success("发布成功") : R.fail("发布失败");
         } catch (Exception e) {
-            log.error("发讨论失败", e);
+            log.error("发讨论失败", e);
             return R.fail(e.getMessage());
         }
     }
 
+    @ApiOperation("回复问答讨论")
+    @PostMapping("/postReply")
+    public R<Boolean> postReply(@RequestBody StorePlatformDiscussion discussion) {
+        log.info("StorePlatformDiscussionController.postReply?discussion={}", discussion);
+        try {
+            boolean success = storePlatformDiscussionService.postReply(discussion);
+            return success ? R.success("回复成功") : R.fail("回复失败");
+        } catch (Exception e) {
+            log.error("回复讨论失败", e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("分页获取店铺一级问答讨论列表")
+    @GetMapping("/pageRoot")
+    public R<IPage<StorePlatformDiscussionUserVo>> pageRoot(
+            @ApiParam(value = "店铺ID", required = true) @RequestParam Integer storeId,
+            @ApiParam(value = "页码", defaultValue = "1") @RequestParam(defaultValue = "1") Integer pageNum,
+            @ApiParam(value = "每页数量", defaultValue = "10") @RequestParam(defaultValue = "10") Integer pageSize) {
+        log.info("StorePlatformDiscussionController.pageRoot?storeId={}, pageNum={}, pageSize={}", storeId, pageNum, pageSize);
+        return R.data(storePlatformDiscussionService.pageRootDiscussions(storeId, pageNum, pageSize));
+    }
+
+    @ApiOperation("分页获取某条一级问答讨论下的所有回复")
+    @GetMapping("/pageReplies")
+    public R<StorePlatformDiscussionReplyVo> pageReplies(
+            @ApiParam(value = "一级讨论ID (rootId)", required = true) @RequestParam Integer rootId,
+            @ApiParam(value = "页码", defaultValue = "1") @RequestParam(defaultValue = "1") Integer pageNum,
+            @ApiParam(value = "每页数量", defaultValue = "10") @RequestParam(defaultValue = "10") Integer pageSize) {
+        log.info("StorePlatformDiscussionController.pageReplies?rootId={}, pageNum={}, pageSize={}", rootId, pageNum, pageSize);
+        return R.data(storePlatformDiscussionService.pageRepliesByRootId(rootId, pageNum, pageSize));
+    }
+
     @ApiOperation("获取店铺问答讨论列表")
     @GetMapping("/list")
-    public R<List<StoreDiscussion>> list(@ApiParam(value = "店铺ID", required = true) @RequestParam Integer storeId) {
+    public R<List<StorePlatformDiscussionUserVo>> list(@ApiParam(value = "店铺ID", required = true) @RequestParam Integer storeId) {
         log.info("StorePlatformDiscussionController.list?storeId={}", storeId);
-        return R.data(storeDiscussionService.listByStoreId(storeId));
+        return R.data(storePlatformDiscussionService.listByStoreId(storeId));
     }
 
     @ApiOperation("删除问答讨论")
     @DeleteMapping("/delete/{id}")
     public R<Boolean> delete(@PathVariable Integer id) {
         log.info("StorePlatformDiscussionController.delete?id={}", id);
-        boolean success = storeDiscussionService.removeById(id);
+        boolean success = storePlatformDiscussionService.removeById(id);
         return success ? R.success("删除成功") : R.fail("删除失败");
     }
 }
-

+ 5 - 2
alien-store-platform/src/main/java/shop/alien/storeplatform/entity/StoreDiscussion.java → alien-store-platform/src/main/java/shop/alien/storeplatform/entity/StorePlatformDiscussion.java

@@ -17,7 +17,7 @@ import java.util.Date;
 @Data
 @TableName("store_discussion")
 @ApiModel(value = "StoreDiscussion对象", description = "店铺讨论表")
-public class StoreDiscussion {
+public class StorePlatformDiscussion {
 
     @ApiModelProperty(value = "主键")
     @TableId(value = "id", type = IdType.AUTO)
@@ -39,6 +39,10 @@ public class StoreDiscussion {
     @TableField("parent_id")
     private Integer parentId;
 
+    @ApiModelProperty(value = "根讨论ID (一级讨论的ID)")
+    @TableField("root_id")
+    private Integer rootId;
+
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")
     @TableLogic
@@ -62,4 +66,3 @@ public class StoreDiscussion {
     @TableField("updated_user_id")
     private Integer updatedUserId;
 }
-

+ 2 - 2
alien-store-platform/src/main/java/shop/alien/storeplatform/mapper/StoreDiscussionMapper.java → alien-store-platform/src/main/java/shop/alien/storeplatform/mapper/StorePlatformDiscussionMapper.java

@@ -2,7 +2,7 @@ package shop.alien.storeplatform.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
-import shop.alien.storeplatform.entity.StoreDiscussion;
+import shop.alien.storeplatform.entity.StorePlatformDiscussion;
 
 /**
  * 店铺讨论表 Mapper 接口
@@ -11,7 +11,7 @@ import shop.alien.storeplatform.entity.StoreDiscussion;
  * @since 2025-12-30
  */
 @Mapper
-public interface StoreDiscussionMapper extends BaseMapper<StoreDiscussion> {
+public interface StorePlatformDiscussionMapper extends BaseMapper<StorePlatformDiscussion> {
 
 }
 

+ 0 - 30
alien-store-platform/src/main/java/shop/alien/storeplatform/service/StoreDiscussionService.java

@@ -1,30 +0,0 @@
-package shop.alien.storeplatform.service;
-
-import com.baomidou.mybatisplus.extension.service.IService;
-import shop.alien.storeplatform.entity.StoreDiscussion;
-
-import java.util.List;
-
-/**
- * 店铺讨论表 服务类
- *
- * @author alien
- * @since 2025-12-30
- */
-public interface StoreDiscussionService extends IService<StoreDiscussion> {
-
-    /**
-     * 根据店铺ID查询讨论列表
-     * @param storeId 店铺ID
-     * @return 讨论列表
-     */
-    List<StoreDiscussion> listByStoreId(Integer storeId);
-
-    /**
-     * 发表讨论/回复
-     * @param discussion 讨论信息
-     * @return 是否成功
-     */
-    boolean postDiscussion(StoreDiscussion discussion);
-}
-

+ 57 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformDiscussionService.java

@@ -0,0 +1,57 @@
+package shop.alien.storeplatform.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.storeplatform.entity.StorePlatformDiscussion;
+import shop.alien.storeplatform.vo.StorePlatformDiscussionReplyVo;
+import shop.alien.storeplatform.vo.StorePlatformDiscussionUserVo;
+
+import java.util.List;
+
+/**
+ * 店铺讨论表 服务类
+ *
+ * @author alien
+ * @since 2025-12-30
+ */
+public interface StorePlatformDiscussionService extends IService<StorePlatformDiscussion> {
+
+    /**
+     * 分页查询店铺一级讨论
+     * @param storeId 店铺ID
+     * @param pageNum 页码
+     * @param pageSize 每页数量
+     * @return 分页结果
+     */
+    IPage<StorePlatformDiscussionUserVo> pageRootDiscussions(Integer storeId, Integer pageNum, Integer pageSize);
+
+    /**
+     * 分页查询某条一级讨论下的所有回复
+     * @param rootId 一级讨论ID
+     * @param pageNum 页码
+     * @param pageSize 每页数量
+     * @return 包含主贴信息的回复分页对象
+     */
+    StorePlatformDiscussionReplyVo pageRepliesByRootId(Integer rootId, Integer pageNum, Integer pageSize);
+
+    /**
+     * 根据店铺ID查询讨论列表
+     * @param storeId 店铺ID
+     * @return 讨论列表
+     */
+    List<StorePlatformDiscussionUserVo> listByStoreId(Integer storeId);
+
+    /**
+     * 发布一级讨论 (主贴)
+     * @param discussion 讨论信息 (带 storeId, content)
+     * @return 是否成功
+     */
+    boolean postTopic(StorePlatformDiscussion discussion);
+
+    /**
+     * 回复讨论
+     * @param discussion 讨论信息 (带 parentId, content)
+     * @return 是否成功
+     */
+    boolean postReply(StorePlatformDiscussion discussion);
+}

+ 0 - 36
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreDiscussionServiceImpl.java

@@ -1,36 +0,0 @@
-package shop.alien.storeplatform.service.impl;
-
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import org.springframework.stereotype.Service;
-import shop.alien.storeplatform.entity.StoreDiscussion;
-import shop.alien.storeplatform.mapper.StoreDiscussionMapper;
-import shop.alien.storeplatform.service.StoreDiscussionService;
-
-import java.util.List;
-
-/**
- * 店铺讨论表 服务实现类
- *
- * @author alien
- * @since 2025-12-30
- */
-@Service
-public class StoreDiscussionServiceImpl extends ServiceImpl<StoreDiscussionMapper, StoreDiscussion> implements StoreDiscussionService {
-
-    @Override
-    public List<StoreDiscussion> listByStoreId(Integer storeId) {
-        return this.list(new LambdaQueryWrapper<StoreDiscussion>()
-                .eq(StoreDiscussion::getStoreId, storeId)
-                .orderByDesc(StoreDiscussion::getCreatedTime));
-    }
-
-    @Override
-    public boolean postDiscussion(StoreDiscussion discussion) {
-        if (discussion.getParentId() == null) {
-            discussion.setParentId(0);
-        }
-        return this.save(discussion);
-    }
-}
-

+ 191 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformDiscussionServiceImpl.java

@@ -0,0 +1,191 @@
+package shop.alien.storeplatform.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import shop.alien.entity.store.LifeUser;
+import shop.alien.mapper.LifeUserMapper;
+import shop.alien.storeplatform.entity.StorePlatformDiscussion;
+import shop.alien.storeplatform.mapper.StorePlatformDiscussionMapper;
+import shop.alien.storeplatform.service.StorePlatformDiscussionService;
+import shop.alien.storeplatform.vo.StorePlatformDiscussionReplyVo;
+import shop.alien.storeplatform.vo.StorePlatformDiscussionUserVo;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 店铺问题讨论服务实现类
+ *
+ * @author alien
+ * @since 2025-12-30
+ */
+@Service
+@RequiredArgsConstructor
+public class StorePlatformDiscussionServiceImpl extends ServiceImpl<StorePlatformDiscussionMapper, StorePlatformDiscussion> implements StorePlatformDiscussionService {
+
+    private final LifeUserMapper lifeUserMapper;
+
+    @Override
+    public IPage<StorePlatformDiscussionUserVo> pageRootDiscussions(Integer storeId, Integer pageNum, Integer pageSize) {
+        Page<StorePlatformDiscussion> page = new Page<>(pageNum, pageSize);
+        IPage<StorePlatformDiscussion> discussionPage = this.page(page, new LambdaQueryWrapper<StorePlatformDiscussion>()
+                .eq(StorePlatformDiscussion::getStoreId, storeId)
+                .eq(StorePlatformDiscussion::getParentId, 0)
+                .orderByDesc(StorePlatformDiscussion::getCreatedTime));
+        
+        return discussionPage.convert(this::convertToVoWithUserInfo);
+    }
+
+    @Override
+    public StorePlatformDiscussionReplyVo pageRepliesByRootId(Integer rootId, Integer pageNum, Integer pageSize) {
+        // 1. 获取主贴信息
+        StorePlatformDiscussion root = this.getById(rootId);
+        if (root == null) {
+            throw new RuntimeException("主贴讨论不存在");
+        }
+        StorePlatformDiscussionUserVo topicVo = convertToVoWithUserInfo(root);
+
+        // 2. 分页获取回复
+        Page<StorePlatformDiscussion> page = new Page<>(pageNum, pageSize);
+        IPage<StorePlatformDiscussion> replyPage = this.page(page, new LambdaQueryWrapper<StorePlatformDiscussion>()
+                .eq(StorePlatformDiscussion::getRootId, rootId)
+                .ne(StorePlatformDiscussion::getId, rootId) // 排除主贴本身
+                .orderByAsc(StorePlatformDiscussion::getCreatedTime));
+        
+        // 3. 转换为VO并补充用户信息
+        IPage<StorePlatformDiscussionUserVo> voPage = replyPage.convert(this::convertToVo);
+        fillUserInfoBatch(voPage.getRecords());
+
+        return StorePlatformDiscussionReplyVo.fromPage(topicVo, voPage);
+    }
+
+    @Override
+    public List<StorePlatformDiscussionUserVo> listByStoreId(Integer storeId) {
+        List<StorePlatformDiscussion> list = this.list(new LambdaQueryWrapper<StorePlatformDiscussion>()
+                .eq(StorePlatformDiscussion::getStoreId, storeId)
+                .orderByDesc(StorePlatformDiscussion::getCreatedTime));
+        
+        List<StorePlatformDiscussionUserVo> voList = list.stream().map(this::convertToVo).collect(Collectors.toList());
+        fillUserInfoBatch(voList);
+        return voList;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean postTopic(StorePlatformDiscussion discussion) {
+        discussion.setParentId(0);
+        discussion.setRootId(0);
+        boolean saved = this.save(discussion);
+        if (saved) {
+            // 一级讨论的 rootId 设置为它自己的 ID
+            discussion.setRootId(discussion.getId());
+            this.updateById(discussion);
+        }
+        return saved;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean postReply(StorePlatformDiscussion discussion) {
+        if (discussion.getParentId() == null || discussion.getParentId() == 0) {
+            throw new RuntimeException("回复必须指定父级讨论 ID");
+        }
+        
+        StorePlatformDiscussion parent = this.getById(discussion.getParentId());
+        if (parent == null) {
+            throw new RuntimeException("父级讨论不存在");
+        }
+        
+        // 从父级继承 storeId 和 rootId
+        discussion.setStoreId(parent.getStoreId());
+        if (parent.getParentId() == 0) {
+            // 如果父级是根讨论,那么 rootId 就是父级的 id
+            discussion.setRootId(parent.getId());
+        } else {
+            // 如果父级本身也是回复,那么 rootId 沿用父级的 rootId
+            discussion.setRootId(parent.getRootId());
+        }
+        
+        return this.save(discussion);
+    }
+
+    private StorePlatformDiscussionUserVo convertToVo(StorePlatformDiscussion discussion) {
+        if (discussion == null) return null;
+        StorePlatformDiscussionUserVo vo = new StorePlatformDiscussionUserVo();
+        BeanUtils.copyProperties(discussion, vo);
+        return vo;
+    }
+
+    private StorePlatformDiscussionUserVo convertToVoWithUserInfo(StorePlatformDiscussion discussion) {
+        StorePlatformDiscussionUserVo vo = convertToVo(discussion);
+        if (vo != null) {
+            List<StorePlatformDiscussionUserVo> list = new ArrayList<>();
+            list.add(vo);
+            fillUserInfoBatch(list);
+        }
+        return vo;
+    }
+
+    /**
+     * 批量填充用户信息
+     */
+    private void fillUserInfoBatch(List<StorePlatformDiscussionUserVo> voList) {
+        if (CollectionUtils.isEmpty(voList)) return;
+
+        // 提取所有用户ID和父级ID对应的人
+        Set<Integer> userIds = voList.stream().map(StorePlatformDiscussionUserVo::getUserId).collect(Collectors.toSet());
+        
+        // 提取所有父级讨论,用于获取回复对象的名称
+        Set<Integer> parentIds = voList.stream()
+                .map(StorePlatformDiscussionUserVo::getParentId)
+                .filter(id -> id != null && id != 0)
+                .collect(Collectors.toSet());
+
+        // 获取评论人信息
+        Map<Integer, LifeUser> userMap = getUserMap(userIds);
+        
+        // 获取回复对象信息 (如果是回复,需要知道在回复谁)
+        Map<Integer, String> parentUserNameMap = getParentUserNameMap(parentIds);
+
+        voList.forEach(vo -> {
+            LifeUser user = userMap.get(vo.getUserId());
+            if (user != null) {
+                vo.setUserName(user.getUserName());
+                vo.setUserImage(user.getUserImage());
+            }
+            if (vo.getParentId() != null && vo.getParentId() != 0) {
+                vo.setParentUserName(parentUserNameMap.get(vo.getParentId()));
+            }
+        });
+    }
+
+    private Map<Integer, LifeUser> getUserMap(Set<Integer> userIds) {
+        if (CollectionUtils.isEmpty(userIds)) return Collections.emptyMap();
+        List<LifeUser> users = lifeUserMapper.selectBatchIds(userIds);
+        return users.stream().collect(Collectors.toMap(LifeUser::getId, u -> u));
+    }
+
+    private Map<Integer, String> getParentUserNameMap(Set<Integer> parentIds) {
+        if (CollectionUtils.isEmpty(parentIds)) return Collections.emptyMap();
+        List<StorePlatformDiscussion> parents = this.baseMapper.selectBatchIds(parentIds);
+        if (CollectionUtils.isEmpty(parents)) return Collections.emptyMap();
+        
+        Set<Integer> parentUserIds = parents.stream().map(StorePlatformDiscussion::getUserId).collect(Collectors.toSet());
+        Map<Integer, LifeUser> userMap = getUserMap(parentUserIds);
+        
+        return parents.stream().collect(Collectors.toMap(
+                StorePlatformDiscussion::getId,
+                p -> {
+                    LifeUser u = userMap.get(p.getUserId());
+                    return u != null ? u.getUserName() : "未知用户";
+                }
+        ));
+    }
+}

+ 42 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/vo/StorePlatformDiscussionReplyVo.java

@@ -0,0 +1,42 @@
+package shop.alien.storeplatform.vo;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 讨论回复分页返回对象 ()
+ */
+@Data
+@ApiModel(value = "StorePlatformDiscussionReplyVo对象", description = "讨论回复分页返回对象")
+public class StorePlatformDiscussionReplyVo {
+
+    @ApiModelProperty(value = "原问题/主贴信息")
+    private StorePlatformDiscussionUserVo topic;
+
+    @ApiModelProperty(value = "回复列表")
+    private List<StorePlatformDiscussionUserVo> replies;
+
+    @ApiModelProperty(value = "总页数")
+    private long pages;
+
+    @ApiModelProperty(value = "当前页")
+    private long current;
+
+    @ApiModelProperty(value = "总条数")
+    private long total;
+
+    public static StorePlatformDiscussionReplyVo fromPage(StorePlatformDiscussionUserVo topic, IPage<StorePlatformDiscussionUserVo> page) {
+        StorePlatformDiscussionReplyVo vo = new StorePlatformDiscussionReplyVo();
+        vo.setTopic(topic);
+        vo.setReplies(page.getRecords());
+        vo.setPages(page.getPages());
+        vo.setCurrent(page.getCurrent());
+        vo.setTotal(page.getTotal());
+        return vo;
+    }
+}
+

+ 26 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/vo/StorePlatformDiscussionUserVo.java

@@ -0,0 +1,26 @@
+package shop.alien.storeplatform.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import shop.alien.storeplatform.entity.StorePlatformDiscussion;
+
+/**
+ * 店铺讨论人员VO
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ApiModel(value = "StorePlatformDiscussionVo对象", description = "店铺讨论人员显示对象")
+public class StorePlatformDiscussionUserVo extends StorePlatformDiscussion {
+
+    @ApiModelProperty(value = "用户名称")
+    private String userName;
+
+    @ApiModelProperty(value = "用户头像")
+    private String userImage;
+    
+    @ApiModelProperty(value = "被回复人名称 (如果是回复)")
+    private String parentUserName;
+}
+

+ 197 - 0
alien-store/src/main/java/shop/alien/store/controller/PerformanceListController.java

@@ -0,0 +1,197 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.vo.PerformanceDetailVo;
+import shop.alien.entity.store.vo.PerformanceGroupListVo;
+import shop.alien.entity.store.vo.PerformanceListVo;
+import shop.alien.store.service.PerformanceListService;
+import shop.alien.store.util.CommonConstant;
+
+/**
+ * 演出列表Controller(用户端)
+ * 提供用户端演出列表查询接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"用户端-演出"})
+@CrossOrigin
+@RestController
+@RequestMapping("/performance/list")
+@RequiredArgsConstructor
+public class PerformanceListController {
+
+    private final PerformanceListService performanceListService;
+
+    /**
+     * 查询演出列表(用户端)
+     * <p>
+     * 根据店铺ID查询演出列表,支持分页
+     * 只返回审核通过、已上线、未删除的演出
+     * 返回结果包含演出名称、演出者信息、日期范围、演出时间安排等
+     * </p>
+     *
+     * @param page    分页页数,默认1,必须大于0
+     * @param size    分页条数,默认10,必须大于0且不超过100
+     * @param storeId 店铺ID,必填,必须大于0
+     * @return 演出列表分页结果
+     */
+    @ApiOperation("查询演出列表(用户端)")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "分页页数", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "size", value = "分页条数", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/query")
+    public R<IPage<PerformanceListVo>> queryPerformanceList(
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "size", defaultValue = "10") Integer size,
+            @RequestParam(value = "storeId") Integer storeId) {
+        log.info("查询演出列表,参数:page={}, size={}, storeId={}", page, size, storeId);
+
+        try {
+            // 参数校验
+            if (storeId == null || storeId <= 0) {
+                log.warn("查询演出列表参数校验失败,店铺ID无效:storeId={}", storeId);
+                return R.fail("店铺ID不能为空且必须大于0");
+            }
+            if (page != null && page < 1) {
+                log.warn("查询演出列表参数校验失败,分页页数无效:page={}", page);
+                return R.fail("分页页数必须大于0");
+            }
+            if (size != null && size < 1) {
+                log.warn("查询演出列表参数校验失败,分页条数无效:size={}", size);
+                return R.fail("分页条数必须大于0");
+            }
+            if (size != null && size > CommonConstant.MAX_PAGE_SIZE) {
+                log.warn("查询演出列表参数校验失败,分页条数超过最大值:size={}", size);
+                return R.fail("分页条数不能超过" + CommonConstant.MAX_PAGE_SIZE);
+            }
+
+            // 规范化分页参数
+            Integer normalizedPage = (page == null || page < 1) ? CommonConstant.DEFAULT_PAGE_NUM : page;
+            Integer normalizedSize;
+            if (size == null || size < 1) {
+                normalizedSize = CommonConstant.DEFAULT_PAGE_SIZE;
+            } else if (size > CommonConstant.MAX_PAGE_SIZE) {
+                normalizedSize = CommonConstant.MAX_PAGE_SIZE;
+            } else {
+                normalizedSize = size;
+            }
+
+            // 调用服务层查询
+            IPage<PerformanceListVo> result = performanceListService.queryPerformanceList(
+                    normalizedPage, normalizedSize, storeId);
+
+            log.info("查询演出列表成功,店铺ID={},共{}条记录,当前页{}条",
+                    storeId, result.getTotal(), result.getRecords() != null ? result.getRecords().size() : 0);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.warn("查询演出列表参数错误,storeId={},错误信息:{}", storeId, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("查询演出列表异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
+            return R.fail("查询演出列表失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 查询按日期分组的演出列表(用户端)
+     * <p>
+     * 根据店铺ID查询演出列表,按日期分组返回
+     * 只返回审核通过、已上线、未删除的演出
+     * 返回未来30天内有演出的日期及其对应的演出列表
+     * 日期标题格式:MM-dd 今天 或 MM-dd 星期几
+     * </p>
+     *
+     * @param storeId 店铺ID,必填,必须大于0
+     * @return 按日期分组的演出列表
+     */
+    @ApiOperation("查询按日期分组的演出列表(用户端)")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/queryGroupByDate")
+    public R<PerformanceGroupListVo> queryPerformanceListGroupByDate(
+            @RequestParam(value = "storeId") Integer storeId) {
+        log.info("查询按日期分组的演出列表,参数:storeId={}", storeId);
+
+        try {
+            // 参数校验
+            if (storeId == null || storeId <= 0) {
+                log.warn("查询按日期分组的演出列表参数校验失败,店铺ID无效:storeId={}", storeId);
+                return R.fail("店铺ID不能为空且必须大于0");
+            }
+
+            // 调用服务层查询
+            PerformanceGroupListVo result = performanceListService.queryPerformanceListGroupByDate(storeId);
+
+            log.info("查询按日期分组的演出列表成功,店铺ID={},共{}个日期组,{}条演出记录",
+                    storeId, result.getGroupList() != null ? result.getGroupList().size() : 0, result.getTotal());
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.warn("查询按日期分组的演出列表参数错误,storeId={},错误信息:{}", storeId, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("查询按日期分组的演出列表异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
+            return R.fail("查询按日期分组的演出列表失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 查询演出详情(用户端)
+     * <p>
+     * 根据演出ID查询演出详细信息
+     * 包含演出基本信息、演出时间、演出风格、演出详情描述和演出嘉宾列表
+     * 只返回审核通过、已上线、未删除的演出
+     * </p>
+     *
+     * @param id 演出ID,必填,必须大于0
+     * @return 演出详情
+     */
+    @ApiOperation("查询演出详情(用户端)")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "演出ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/detail")
+    public R<PerformanceDetailVo> queryPerformanceDetail(
+            @RequestParam(value = "id") Integer id) {
+        log.info("查询演出详情,参数:id={}", id);
+
+        try {
+            // 参数校验
+            if (id == null || id <= 0) {
+                log.warn("查询演出详情参数校验失败,演出ID无效:id={}", id);
+                return R.fail("演出ID不能为空且必须大于0");
+            }
+
+            // 调用服务层查询
+            PerformanceDetailVo result = performanceListService.queryPerformanceDetail(id);
+
+            if (result == null) {
+                log.warn("查询演出详情失败,演出不存在或状态不符合要求:id={}", id);
+                return R.fail("演出不存在或已下线");
+            }
+
+            log.info("查询演出详情成功,id={},嘉宾数量:{}", id,
+                    result.getGuestList() != null ? result.getGuestList().size() : 0);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.warn("查询演出详情参数错误,id={},错误信息:{}", id, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("查询演出详情异常,id={},异常信息:{}", id, e.getMessage(), e);
+            return R.fail("查询演出详情失败,请稍后重试");
+        }
+    }
+}
+

+ 190 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreCuisineController.java

@@ -0,0 +1,190 @@
+package shop.alien.store.controller;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreCuisine;
+import shop.alien.entity.store.dto.CuisineComboDto;
+import shop.alien.entity.store.dto.CuisineDetailDto;
+import shop.alien.store.service.StoreCuisineService;
+import shop.alien.store.util.ai.AiGetPriceUtil;
+
+import java.util.List;
+
+/**
+ * 美食价目表
+ *
+ * @author auto-generated
+ * @since 2025-12-30
+ */
+@Slf4j
+@Api(tags = {"美食价目表"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/store/cuisine")
+@RequiredArgsConstructor
+public class StoreCuisineController {
+
+    private final StoreCuisineService storeCuisineService;
+
+    private final AiGetPriceUtil aiGetPriceUtil;
+
+    @ApiOperation("新增美食套餐或单品")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/addCuisineCombo")
+    public R<StoreCuisine> addCuisineCombo(@RequestBody CuisineComboDto cuisineComboDto) {
+        log.info("StoreCuisineController.save?storeCuisine={}", cuisineComboDto);
+        if (storeCuisineService.addCuisineCombo(cuisineComboDto)) {
+            return R.success("新增成功");
+        }
+        return R.fail("新增失败");
+    }
+
+    @ApiOperation("修改美食套餐或单品")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/updateCuisineCombo")
+    public R<String> updateCuisineCombo(@RequestBody CuisineComboDto cuisineComboDto) {
+        log.info("StoreCuisineController.updateCuisineCombo?dto={}", cuisineComboDto);
+        if (storeCuisineService.updateCuisineCombo(cuisineComboDto)) {
+            return R.success("修改成功");
+        }
+        return R.fail("修改失败");
+    }
+
+    @ApiOperation("获取所有美食单品名称,用于添加套餐")
+    @ApiOperationSupport(order = 1)
+    @GetMapping("/getSingleName")
+    public R<List<StoreCuisine>> getSingleName() {
+        return R.data(storeCuisineService.getSingleName());
+    }
+
+    @ApiOperation("根据id与类型获取美食单品或套餐详情")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "美食/套餐 id", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "cuisineType", value = "美食类型:1-单品,2-套餐", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getByCuisineType")
+    public R<CuisineDetailDto> getByCuisineType(Integer id, Integer cuisineType) {
+        return R.data(storeCuisineService.getByCuisineType(id, cuisineType));
+    }
+
+
+    @ApiOperation("删除美食价目")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "美食价目id", dataType = "Integer", paramType = "query", required = true)})
+    @PostMapping("/delete")
+    public R<String> delete(Integer id) {
+        log.info("StoreCuisineController.delete?id={}", id);
+        if (storeCuisineService.removeById(id)) {
+            return R.success("删除成功");
+        }
+        return R.fail("删除失败");
+    }
+
+    @ApiOperation("批量删除美食价目")
+    @ApiOperationSupport(order = 6)
+    @PostMapping("/deleteBatch")
+    public R<String> deleteBatch(@RequestBody List<Integer> ids) {
+        log.info("StoreCuisineController.deleteBatch?ids={}", ids);
+        if (storeCuisineService.removeByIds(ids)) {
+            return R.success("批量删除成功");
+        }
+        return R.fail("批量删除失败");
+    }
+
+    @ApiOperation("美食上下架:1-上架,2-下架")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "美食价目id", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "shelfStatus", value = "上下架状态:1-上架,2-下架", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PostMapping("/changeShelfStatus")
+    public R<String> changeShelfStatus(Integer id, Integer shelfStatus) {
+        log.info("StoreCuisineController.changeShelfStatus?id={},shelfStatus={}", id, shelfStatus);
+        if (shelfStatus == null || (shelfStatus != 1 && shelfStatus != 2)) {
+            return R.fail("上下架状态不合法(只能为1或2)");
+        }
+        if (storeCuisineService.changeShelfStatus(id, shelfStatus)) {
+            return R.success("操作成功");
+        }
+        return R.fail("操作失败");
+    }
+
+    @ApiOperation("分页查询美食价目")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "stroeId", value = "商户id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "name", value = "菜名", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "status", value = "状态:0-待审核 1-审核通过 2-审核拒绝", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/getPage")
+    public R<IPage<StoreCuisine>> getPage(
+            @RequestParam(defaultValue = "1") int pageNum,
+            @RequestParam(defaultValue = "10") int pageSize,
+            @RequestParam(required = false) Integer stroeId,
+            @RequestParam(required = false) String name,
+            @RequestParam(required = false) Integer status) {
+        log.info("StoreCuisineController.getPage?pageNum={},pageSize={},stroeId={},name={},status={}", pageNum, pageSize, stroeId, name, status);
+        Page<StoreCuisine> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<StoreCuisine> queryWrapper = new LambdaQueryWrapper<>();
+        // 默认只查询已上架的菜品/套餐(shelf_status = 1)
+        queryWrapper.eq(StoreCuisine::getShelfStatus, 1);
+        if (stroeId != null) {
+            queryWrapper.eq(StoreCuisine::getStroeId, stroeId);
+        }
+        if (name != null && !name.isEmpty()) {
+            queryWrapper.like(StoreCuisine::getName, name);
+        }
+        if (status != null) {
+            queryWrapper.eq(StoreCuisine::getStatus, status);
+        }
+        queryWrapper.orderByDesc(StoreCuisine::getCreatedTime);
+        IPage<StoreCuisine> result = storeCuisineService.page(page, queryWrapper);
+        return R.data(result);
+    }
+
+    @ApiOperation("根据菜品和位置查询推荐菜价")
+    @ApiOperationSupport(order = 6)
+    @PostMapping("/getPrice")
+    public R<String> getPrice(String dish_name,String location) {
+        JSONObject dishPrice = aiGetPriceUtil.getDishPrice(dish_name, location);
+        
+        // 检查响应状态码
+        Integer code = dishPrice.getInteger("code");
+        if (code == null || code != 200) {
+            String message = dishPrice.getString("message");
+            log.warn("查询菜品价格失败,code: {}, message: {}", code, message);
+            return R.fail(message != null ? message : "查询菜品价格失败");
+        }
+        
+        // 获取 data 对象
+        JSONObject data = dishPrice.getJSONObject("data");
+        if (data == null) {
+            log.warn("查询菜品价格响应中 data 为空");
+            return R.fail("查询菜品价格失败:响应数据为空");
+        }
+        
+        // 从 data 中获取 price
+        Object priceObj = data.get("price");
+        if (priceObj == null) {
+            log.warn("查询菜品价格响应中 price 为空");
+            return R.fail("查询菜品价格失败:价格信息为空");
+        }
+        
+        // 将价格转换为字符串返回
+        String price = priceObj.toString();
+        return R.data(price);
+    }
+}
+
+

+ 20 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreImgController.java

@@ -268,4 +268,24 @@ public class StoreImgController {
         }
     }
 
+    /**
+     * 获取封面图片/环境
+     *storeId 商家id
+     * @param imgType 图片类型
+     *                0:其他, 1:入口图, 2:相册, 3:菜品, 4:环境, 5:价目表, 6:推荐菜, 7:菜单, 8:用户评论, 9:商家申诉, 10:商家头像, 11:店铺轮播图,20单图,21多图
+     * @return 图片路径
+     */
+    @ApiOperation("获取封面图片")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({@ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "imgType", value = "图片类型, 0:其他, 1:入口图, 2:相册, 3:菜品, 4:环境, 5:价目表, 6:推荐菜, 7:菜单, 8:用户评论, 9:商家申诉, 10:商家头像, 11:店铺轮播图,20单图,21多图", dataType = "Integer", paramType = "query", required = true),
+         })
+    @GetMapping("/getByCover")
+    public R<List<StoreImg>> getByCover(Integer storeId, Integer imgType) {
+        log.info("StoreImgController.getByBusinessId?storeId={}&imgType={}", storeId, imgType);
+        return R.data(storeImgService.getByCover(storeId, imgType));
+    }
+
+
+
 }

+ 23 - 2
alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java

@@ -408,10 +408,10 @@ public class StoreInfoController {
     public R getStoreCouponList(@RequestParam("storeId") String storeId) {
         log.info("StoreInfoController.getStoreCouponList?storeId={}", storeId);
         CouponAndEventVo couponAndEventVo = new CouponAndEventVo();
-        List<LifeCouponVo> lifeCouponVos = storeInfoService.getStoreCouponList(storeId);
+//        List<LifeCouponVo> lifeCouponVos = storeInfoService.getStoreCouponList(storeId);
 
         List<StoreImg> storeImgs = storeInfoService.getBannerUrl(storeId);
-        couponAndEventVo.setCouponList(lifeCouponVos);
+//        couponAndEventVo.setCouponList(lifeCouponVos);
         if (CollectionUtils.isNotEmpty(storeImgs)) {
             couponAndEventVo.setMarketingList(storeImgs);
         }
@@ -917,6 +917,27 @@ public class StoreInfoController {
 
     }
 
+    @ApiOperation(value = "根据店铺ID获取推荐店铺列表(用户端)")
+    @GetMapping("/getRecommendedStoresByStoreId")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    public R<List<StoreInfoVo>> getRecommendedStoresByStoreId(@RequestParam Integer storeId) {
+        log.info("StoreInfoController.getRecommendedStoresByStoreId?storeId={}", storeId);
+        try {
+            if (storeId == null || storeId <= 0) {
+                log.warn("获取推荐店铺列表失败,店铺ID无效:{}", storeId);
+                return R.fail("店铺ID不能为空且必须大于0");
+            }
+            List<StoreInfoVo> result = storeInfoService.getRecommendedStoresByStoreId(storeId);
+            log.info("获取推荐店铺列表成功,storeId={},返回店铺数量:{}", storeId, result != null ? result.size() : 0);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("获取推荐店铺列表异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
+            return R.fail("获取推荐店铺列表失败:" + e.getMessage());
+        }
+    }
+
     @ApiOperation(value = "AI服务-门头识别")
     @ApiOperationSupport(order = 16)
     @ApiImplicitParams({

+ 14 - 6
alien-store/src/main/java/shop/alien/store/controller/StoreOfficialAlbumController.java

@@ -67,21 +67,29 @@ public class StoreOfficialAlbumController {
      *
      * @param storeId   门店ID,必填,必须大于0
      * @param albumName 相册名称,可选。例如:酒水、餐食、环境、全部等。当为null或空字符串时,查询全部
+     * @param type      类型,必填。1:视频,2:相册,3:环境
+     * @param pageNum   页码,可选,默认1
+     * @param pageSize  每页数量,可选,默认10
      * @return 统一返回结果,包含图片列表和总数
-     * @throws IllegalArgumentException 当门店ID为空或小于等于0时抛出
      */
     @ApiOperation("获取官方相册图片列表(客户端)")
     @ApiOperationSupport(order = 4)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "albumName", value = "相册名称,例如:酒水、餐食、环境、全部等。不传或传空字符串时查询全部", dataType = "String", paramType = "query")
+            @ApiImplicitParam(name = "albumName", value = "相册名称,例如:酒水、餐食、环境、全部等。不传或传空字符串时查询全部", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "type", value = "1:视频,2:相册,3:封面, 4:环境", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageNum", value = "页码,默认1", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "每页数量,默认10", dataType = "Integer", paramType = "query")
     })
     @GetMapping("/getOfficialAlbumImgList")
     public R<StoreOfficialAlbumImgVo> getOfficialAlbumImgList(
             @RequestParam(value = "storeId", required = true) Integer storeId,
-            @RequestParam(value = "albumName", required = false) String albumName) {
-        // 记录请求入参
-        log.info("获取官方相册图片列表,入参:storeId={}, albumName={}", storeId, albumName);
+            @RequestParam(value = "albumName", required = false) String albumName,
+            @RequestParam(value = "type", required = true) Integer type,
+            @RequestParam(value = "pageNum", required = false, defaultValue = "1") int pageNum,
+            @RequestParam(value = "pageSize", required = false, defaultValue = "10") int pageSize) {
+        log.info("获取官方相册图片列表,入参:storeId={}, albumName={}, type={}, pageNum={}, pageSize={}", 
+                storeId, albumName, type, pageNum, pageSize);
 
         // 参数校验
         if (storeId == null || storeId <= 0) {
@@ -91,7 +99,7 @@ public class StoreOfficialAlbumController {
 
         try {
             // 调用服务层获取图片列表
-            StoreOfficialAlbumImgVo result = storeOfficialAlbumService.getOfficialAlbumImgList(storeId, albumName);
+            StoreOfficialAlbumImgVo result = storeOfficialAlbumService.getOfficialAlbumImgList(storeId, albumName, type, pageNum, pageSize);
 
             // 记录返回结果
             log.info("获取官方相册图片列表成功,门店ID:{},相册名称:{},返回图片数量:{}",

+ 228 - 0
alien-store/src/main/java/shop/alien/store/controller/StorePriceController.java

@@ -0,0 +1,228 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StorePrice;
+import shop.alien.store.service.StorePriceService;
+
+import java.util.List;
+
+/**
+ * 通用价目表
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+@Slf4j
+@Api(tags = {"通用价目表"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/store/price")
+@RequiredArgsConstructor
+public class StorePriceController {
+
+    private final StorePriceService storePriceService;
+
+    @ApiOperation("新增通用价目")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/save")
+    public R<StorePrice> save(@RequestBody StorePrice storePrice) {
+        log.info("StorePriceController.save?storePrice={}", storePrice);
+        if (storePriceService.save(storePrice)) {
+            return R.data(storePrice, "新增成功");
+        }
+        return R.fail("新增失败");
+    }
+
+    @ApiOperation("修改通用价目")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/update")
+    public R<String> update(@RequestBody StorePrice storePrice) {
+        log.info("StorePriceController.update?storePrice={}", storePrice);
+        
+        // 校验ID不能为空
+        if (storePrice.getId() == null) {
+            log.error("修改通用价目失败:ID不能为空");
+            return R.fail("ID不能为空");
+        }
+        
+        // 检查记录是否存在
+        StorePrice existingPrice = storePriceService.getById(storePrice.getId());
+        if (existingPrice == null) {
+            log.error("修改通用价目失败:ID={} 的记录不存在", storePrice.getId());
+            return R.fail("记录不存在,无法修改");
+        }
+        
+        // 执行更新
+        boolean result = storePriceService.updateById(storePrice);
+        if (result) {
+            return R.success("修改成功");
+        }
+        
+        log.error("修改通用价目失败:ID={},更新操作返回false", storePrice.getId());
+        return R.fail("修改失败,请检查数据是否正确");
+    }
+
+    @ApiOperation("根据ID查询通用价目")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "通用价目id", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getById")
+    public R<StorePrice> getById(@RequestParam Integer id) {
+        log.info("StorePriceController.getById?id={}", id);
+        StorePrice storePrice = storePriceService.getById(id);
+        if (storePrice != null) {
+            return R.data(storePrice);
+        }
+        return R.fail("查询失败");
+    }
+
+    @ApiOperation("删除通用价目")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "通用价目id", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PostMapping("/delete")
+    public R<String> delete(@RequestParam Integer id) {
+        log.info("StorePriceController.delete?id={}", id);
+        if (storePriceService.removeById(id)) {
+            return R.success("删除成功");
+        }
+        return R.fail("删除失败");
+    }
+
+    @ApiOperation("批量删除通用价目")
+    @ApiOperationSupport(order = 5)
+    @PostMapping("/deleteBatch")
+    public R<String> deleteBatch(@RequestBody List<Integer> ids) {
+        log.info("StorePriceController.deleteBatch?ids={}", ids);
+        if (storePriceService.removeByIds(ids)) {
+            return R.success("批量删除成功");
+        }
+        return R.fail("批量删除失败");
+    }
+
+    @ApiOperation("分页查询通用价目")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "stroeId", value = "商户id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "name", value = "名字", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "status", value = "状态:0-待审核 1-审核通过 2-审核拒绝", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "shelfStatus", value = "上下架状态:1-上架,2-下架", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/getPage")
+    public R<IPage<StorePrice>> getPage(
+            @RequestParam(defaultValue = "1") int pageNum,
+            @RequestParam(defaultValue = "10") int pageSize,
+            @RequestParam(required = false) Integer stroeId,
+            @RequestParam(required = false) String name,
+            @RequestParam(required = false) Integer status,
+            @RequestParam(required = false) Integer shelfStatus) {
+        log.info("StorePriceController.getPage?pageNum={},pageSize={},stroeId={},name={},status={},shelfStatus={}",
+                pageNum, pageSize, stroeId, name, status, shelfStatus);
+        Page<StorePrice> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<StorePrice> queryWrapper = new LambdaQueryWrapper<>();
+        if (stroeId != null) {
+            queryWrapper.eq(StorePrice::getStroeId, stroeId);
+        }
+        if (name != null && !name.isEmpty()) {
+            queryWrapper.like(StorePrice::getName, name);
+        }
+        if (status != null) {
+            queryWrapper.eq(StorePrice::getStatus, status);
+        }
+        if (shelfStatus != null) {
+            queryWrapper.eq(StorePrice::getShelfStatus, shelfStatus);
+        }
+        queryWrapper.orderByDesc(StorePrice::getCreatedTime);
+        IPage<StorePrice> result = storePriceService.page(page, queryWrapper);
+        return R.data(result);
+    }
+
+    @ApiOperation("查询所有通用价目")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "stroeId", value = "商户id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "status", value = "状态:0-待审核 1-审核通过 2-审核拒绝", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "shelfStatus", value = "上下架状态:1-上架,2-下架", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/list")
+    public R<List<StorePrice>> list(
+            @RequestParam(required = false) Integer stroeId,
+            @RequestParam(required = false) Integer status,
+            @RequestParam(required = false) Integer shelfStatus) {
+        log.info("StorePriceController.list?stroeId={},status={},shelfStatus={}", stroeId, status, shelfStatus);
+        LambdaQueryWrapper<StorePrice> queryWrapper = new LambdaQueryWrapper<>();
+        if (stroeId != null) {
+            queryWrapper.eq(StorePrice::getStroeId, stroeId);
+        }
+        if (status != null) {
+            queryWrapper.eq(StorePrice::getStatus, status);
+        }
+        if (shelfStatus != null) {
+            queryWrapper.eq(StorePrice::getShelfStatus, shelfStatus);
+        }
+        queryWrapper.orderByDesc(StorePrice::getCreatedTime);
+        List<StorePrice> list = storePriceService.list(queryWrapper);
+        return R.data(list);
+    }
+
+    @ApiOperation("上下架操作:1-上架,2-下架")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "通用价目id", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "shelfStatus", value = "上下架状态:1-上架,2-下架", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PostMapping("/changeShelfStatus")
+    public R<String> changeShelfStatus(@RequestParam Integer id, @RequestParam Integer shelfStatus) {
+        log.info("StorePriceController.changeShelfStatus?id={},shelfStatus={}", id, shelfStatus);
+        if (shelfStatus == null || (shelfStatus != 1 && shelfStatus != 2)) {
+            return R.fail("上下架状态不合法(只能为1或2)");
+        }
+        StorePrice storePrice = new StorePrice();
+        storePrice.setId(id);
+        storePrice.setShelfStatus(shelfStatus);
+        if (storePriceService.updateById(storePrice)) {
+            return R.success("操作成功");
+        }
+        return R.fail("操作失败");
+    }
+
+    @ApiOperation("审核操作:0-待审核 1-审核通过 2-审核拒绝")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "通用价目id", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "status", value = "状态:0-待审核 1-审核通过 2-审核拒绝", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "rejectionReason", value = "拒绝原因", dataType = "String", paramType = "query")
+    })
+    @PostMapping("/changeStatus")
+    public R<String> changeStatus(@RequestParam Integer id,
+                                   @RequestParam Integer status,
+                                   @RequestParam(required = false) String rejectionReason) {
+        log.info("StorePriceController.changeStatus?id={},status={},rejectionReason={}", id, status, rejectionReason);
+        if (status == null || (status != 0 && status != 1 && status != 2)) {
+            return R.fail("状态不合法(只能为0、1或2)");
+        }
+        StorePrice storePrice = new StorePrice();
+        storePrice.setId(id);
+        storePrice.setStatus(status);
+        if (status == 2 && rejectionReason != null) {
+            storePrice.setRejectionReason(rejectionReason);
+        }
+        if (storePriceService.updateById(storePrice)) {
+            return R.success("操作成功");
+        }
+        return R.fail("操作失败");
+    }
+}
+

+ 207 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreStaffCommentController.java

@@ -0,0 +1,207 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreStaffComment;
+import shop.alien.entity.store.dto.StoreStaffCommentRequestDto;
+import shop.alien.entity.store.dto.StoreStaffReplyDto;
+import shop.alien.entity.store.vo.StoreStaffCommentVo;
+import shop.alien.store.service.StoreStaffCommentService;
+
+import java.util.List;
+
+/**
+ * 员工评论 前端控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"员工评论"})
+@ApiSort(21)
+@CrossOrigin
+@RestController
+@RequestMapping("/store/staffComment")
+@RequiredArgsConstructor
+public class StoreStaffCommentController {
+
+    private final StoreStaffCommentService storeStaffCommentService;
+
+    @ApiOperation("创建评论(用户对评价的评论,支持普通用户和员工)")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataTypeClass = Integer.class, paramType = "body", required = true),
+            @ApiImplicitParam(name = "sendUserId", value = "发送用户ID(评论用户ID,如果传入userId则自动映射到sendUserId)", dataTypeClass = Integer.class, paramType = "body", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID(登录用户ID,会自动映射到sendUserId,如果sendUserId已存在则优先使用sendUserId)", dataTypeClass = Integer.class, paramType = "body", required = false),
+            @ApiImplicitParam(name = "receiveUserId", value = "接收用户ID(可选,不填时默认为评价的创建者)", dataTypeClass = Integer.class, paramType = "body", required = false),
+            @ApiImplicitParam(name = "sendUserType", value = "发送用户类型:1-普通用户,2-员工(必填)", dataTypeClass = Integer.class, paramType = "body", required = true),
+            @ApiImplicitParam(name = "receiveUserType", value = "接收用户类型:1-普通用户,2-员工(必填)", dataTypeClass = Integer.class, paramType = "body", required = true),
+            @ApiImplicitParam(name = "commentContent", value = "评论内容", dataTypeClass = String.class, paramType = "body", required = true)
+    })
+    @PostMapping("/create")
+    public R<StoreStaffComment> createComment(@RequestBody StoreStaffComment comment) {
+        log.info("创建评论, comment={}", comment);
+        
+        // 参数校验:优先使用sendUserId,如果不存在则使用userId
+        if (comment.getSendUserId() == null) {
+            if (comment.getUserId() == null) {
+                return R.fail("用户ID不能为空");
+            }
+            comment.setSendUserId(comment.getUserId());
+        }
+        
+        // 校验用户类型字段
+        if (comment.getSendUserType() == null) {
+            return R.fail("发送用户类型不能为空");
+        }
+        if (comment.getReceiveUserType() == null) {
+            return R.fail("接收用户类型不能为空");
+        }
+        
+        return storeStaffCommentService.createComment(comment);
+    }
+
+    @ApiOperation("根据评价ID查询评论列表")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataTypeClass = Integer.class, paramType = "query", required = false)
+    })
+    @GetMapping("/list/reviewId")
+    public R<List<StoreStaffCommentVo>> getCommentListByReviewId(
+            @RequestParam Integer reviewId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("根据评价ID查询评论列表, reviewId={}, currentUserId={}", reviewId, currentUserId);
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+        return storeStaffCommentService.getCommentListByReviewId(reviewId, currentUserId);
+    }
+
+    @ApiOperation("点赞评论")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "commentId", value = "评论ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    })
+    @PostMapping("/like")
+    public R<Boolean> likeComment(@RequestParam Integer commentId,
+                                   @RequestParam Integer userId) {
+        log.info("点赞评论, commentId={}, userId={}", commentId, userId);
+        if (commentId == null) {
+            return R.fail("评论ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户未登录");
+        }
+        StoreStaffCommentRequestDto requestDto = new StoreStaffCommentRequestDto();
+        requestDto.setCommentId(commentId);
+        requestDto.setUserId(userId);
+        return storeStaffCommentService.likeComment(requestDto);
+    }
+
+    @ApiOperation("取消点赞评论")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "commentId", value = "评论ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    })
+    @PostMapping("/cancelLike")
+    public R<Boolean> cancelLikeComment(@RequestParam Integer commentId,
+                                         @RequestParam Integer userId) {
+        log.info("取消点赞评论, commentId={}, userId={}", commentId, userId);
+        if (commentId == null) {
+            return R.fail("评论ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户未登录");
+        }
+        StoreStaffCommentRequestDto requestDto = new StoreStaffCommentRequestDto();
+        requestDto.setCommentId(commentId);
+        requestDto.setUserId(userId);
+        return storeStaffCommentService.cancelLikeComment(requestDto);
+    }
+
+    @ApiOperation("创建回复(用户对评论的回复)")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "commentId", value = "首评ID", dataTypeClass = Integer.class, paramType = "body", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID(发送用户ID)", dataTypeClass = Integer.class, paramType = "body", required = true),
+            @ApiImplicitParam(name = "replyToUserId", value = "被回复用户ID(可选,不填时默认为首评的发送用户)", dataTypeClass = Integer.class, paramType = "body", required = false),
+            @ApiImplicitParam(name = "sendUserType", value = "发送用户类型:1-普通用户,2-员工(必填)", dataTypeClass = Integer.class, paramType = "body", required = true),
+            @ApiImplicitParam(name = "receiveUserType", value = "接收用户类型:1-普通用户,2-员工(必填)", dataTypeClass = Integer.class, paramType = "body", required = true),
+            @ApiImplicitParam(name = "replyContent", value = "回复内容", dataTypeClass = String.class, paramType = "body", required = true)
+    })
+    @PostMapping("/reply/create")
+    public R<StoreStaffComment> createReply(@RequestBody StoreStaffReplyDto replyDto) {
+        log.info("创建回复, replyDto={}", replyDto);
+        
+        if (replyDto == null || replyDto.getUserId() == null) {
+            return R.fail("用户ID不能为空");
+        }
+        
+        // 校验用户类型字段
+        if (replyDto.getSendUserType() == null) {
+            return R.fail("发送用户类型不能为空");
+        }
+        if (replyDto.getReceiveUserType() == null) {
+            return R.fail("接收用户类型不能为空");
+        }
+        
+        return storeStaffCommentService.createReply(replyDto);
+    }
+
+    @ApiOperation("根据首评ID查询回复列表")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "headId", value = "首评ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataTypeClass = Integer.class, paramType = "query", required = false)
+    })
+    @GetMapping("/reply/list/headId")
+    public R<List<StoreStaffCommentVo>> getReplyListByHeadId(
+            @RequestParam Integer headId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("根据首评ID查询回复列表, headId={}, currentUserId={}", headId, currentUserId);
+        if (headId == null) {
+            return R.fail("首评ID不能为空");
+        }
+        return storeStaffCommentService.getReplyListByHeadId(headId, currentUserId);
+    }
+
+    @ApiOperation("删除回复")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "replyId", value = "回复ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    })
+    @PostMapping("/reply/delete")
+    public R<Boolean> deleteReply(@RequestParam Integer replyId,
+                                   @RequestParam Integer userId) {
+        log.info("删除回复, replyId={}, userId={}", replyId, userId);
+        if (replyId == null) {
+            return R.fail("回复ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户未登录");
+        }
+        return storeStaffCommentService.deleteReply(replyId, userId);
+    }
+
+    @ApiOperation(value = "删除评论(根据ID)", notes = "根据评论ID删除评论,userId有值时只能删除自己发布的评论,userId为空时允许删除任何评论(管理员删除)")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "评论ID", dataTypeClass = Integer.class, paramType = "body", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID(可选,有值时只能删除自己的评论,为空时允许删除任何评论)", dataTypeClass = Integer.class, paramType = "body", required = false)
+    })
+    @PostMapping("/deleteReviewComment")
+    public R<Boolean> deleteReviewComment(@RequestBody StoreStaffComment storeStaffComment) {
+        log.info("删除评论, storeStaffComment={}", storeStaffComment);
+        if (storeStaffComment == null || storeStaffComment.getId() == null) {
+            return R.fail("评论ID不能为空");
+        }
+        return storeStaffCommentService.deleteReviewComment(storeStaffComment);
+    }
+}

+ 213 - 103
alien-store/src/main/java/shop/alien/store/controller/StoreStaffConfigController.java

@@ -1,26 +1,26 @@
 package shop.alien.store.controller;
 
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
-import shop.alien.entity.store.StoreDictionary;
 import shop.alien.entity.store.StoreStaffConfig;
+import shop.alien.entity.store.StoreStaffTitle;
 import shop.alien.entity.store.dto.StoreStaffConfigListQueryDto;
+import shop.alien.entity.store.vo.StaffTitleGroupVo;
 import shop.alien.entity.store.vo.StoreStaffDetailVo;
+import shop.alien.entity.store.vo.StoreStaffDetailWithPerformanceVo;
 import shop.alien.entity.store.vo.StoreStaffFitnessDetailVo;
 import shop.alien.entity.store.vo.StoreStaffPositionCountVo;
 import shop.alien.mapper.StoreDictionaryMapper;
 import shop.alien.store.service.StoreStaffConfigService;
+import shop.alien.store.service.StoreStaffTitleService;
+import shop.alien.store.util.CommonConstant;
 
 import java.io.IOException;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
 
 /**
  * @Author: fcw
@@ -46,7 +46,6 @@ public class StoreStaffConfigController {
             @ApiImplicitParam(name = "page", value = "分页页数", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "size", value = "分页条数", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "status", value = "员工状态(0-待审核 1-审核通过 2-审核拒绝)", dataType = "String", paramType = "query", required = false),
-            @ApiImplicitParam(name = "businessSection", value = "经营板块id(词典表 键为 business_section)", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "onlineStatus", value = "上线状态(0-上线 1-下线)", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "staffPosition", value = "职位", dataType = "String", paramType = "query", required = false)
     })
@@ -137,13 +136,53 @@ public class StoreStaffConfigController {
 
 
     /**
-     * 员工列表查询接口(用户端)
+     * 查询员工职位列表(商家端)
      *
-     * @param page    分页页数,默认1
-     * @param size    分页条数,默认10
      * @param storeId 店铺ID,必填
-     * @param status  员工状态,可选(0-待审核 1-审核通过 2-审核拒绝)
-     * @return 员工列表
+     * @return 员工职位名称列表
+     */
+    @ApiOperation("查询员工职位列表(商家端)")
+    @ApiOperationSupport(order = 11)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getStaffPositionList")
+    public R<List<String>> getStaffPositionList(
+            @RequestParam(value = "storeId") Integer storeId) {
+        log.info("查询员工职位列表,参数:storeId={}", storeId);
+
+        try {
+            // 参数校验
+            if (storeId == null || storeId <= 0) {
+                log.warn("查询员工职位列表失败,店铺ID无效:storeId={}", storeId);
+                return R.fail("店铺ID不能为空且必须大于0");
+            }
+
+            List<String> result = storeStaffConfigService.getStaffPositionList(storeId);
+            log.info("查询员工职位列表成功,storeId={},职位数量:{}", storeId, result.size());
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.warn("查询员工职位列表失败,参数错误:{}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("查询员工职位列表异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+
+    /**
+     * 员工列表查询接口(用户端)
+     * <p>
+     * 根据店铺ID查询员工列表,支持按状态和职位筛选,返回结果包含员工基本信息和今日是否有演出标识
+     * </p>
+     *
+     * @param page          分页页数,默认1,必须大于0
+     * @param size          分页条数,默认10,必须大于0且不超过100
+     * @param storeId       店铺ID,必填,必须大于0
+     * @param status        员工状态,可选(0-待审核 1-审核通过 2-审核拒绝)
+     * @param staffPosition 员工职位,可选,支持精确匹配
+     * @return 员工列表分页结果,包含员工基本信息和今日是否有演出标识
      */
     @ApiOperation("员工列表查询(用户端)")
     @ApiOperationSupport(order = 5)
@@ -161,125 +200,196 @@ public class StoreStaffConfigController {
             @RequestParam(value = "storeId") Integer storeId,
             @RequestParam(value = "status", required = false) String status,
             @RequestParam(value = "staffPosition", required = false) String staffPosition) {
-        log.info("查询员工列表,参数:page={}, size={}, storeId={}, status={}, staffPosition={}", page, size, storeId, status, staffPosition);
+        log.info("查询员工列表,参数:page={}, size={}, storeId={}, status={}, staffPosition={}",
+                page, size, storeId, status, staffPosition);
 
-        // 参数校验
-        if (storeId == null || storeId <= 0) {
-            log.warn("查询员工列表失败,店铺ID无效:storeId={}", storeId);
-            return R.fail("店铺ID不能为空且必须大于0");
-        }
-        if (page == null || page < 1) {
-            page = 1;
-        }
-        if (size == null || size < 1) {
-            size = 10;
-        }
-        // 限制分页大小,防止查询过多数据
-        if (size > 100) {
-            size = 100;
-        }
+        try {
+            // 参数校验
+            if (storeId == null || storeId <= 0) {
+                log.warn("查询员工列表参数校验失败,店铺ID无效:storeId={}", storeId);
+                return R.fail("店铺ID不能为空且必须大于0");
+            }
+            if (page != null && page < 1) {
+                log.warn("查询员工列表参数校验失败,分页页数无效:page={}", page);
+                return R.fail("分页页数必须大于0");
+            }
+            if (size != null && size < 1) {
+                log.warn("查询员工列表参数校验失败,分页条数无效:size={}", size);
+                return R.fail("分页条数必须大于0");
+            }
+            if (size != null && size > CommonConstant.MAX_PAGE_SIZE) {
+                log.warn("查询员工列表参数校验失败,分页条数超过最大值:size={}", size);
+                return R.fail("分页条数不能超过" + CommonConstant.MAX_PAGE_SIZE);
+            }
 
-        IPage<StoreStaffConfig> result = storeStaffConfigService.queryStaffList(page, size, storeId, status, staffPosition);
-        log.info("查询员工列表成功,共{}条记录", result.getTotal());
-        return R.data(result);
+            // 规范化分页参数
+            Integer normalizedPage = (page == null || page < 1) ? CommonConstant.DEFAULT_PAGE_NUM : page;
+            Integer normalizedSize;
+            if (size == null || size < 1) {
+                normalizedSize = CommonConstant.DEFAULT_PAGE_SIZE;
+            } else if (size > CommonConstant.MAX_PAGE_SIZE) {
+                normalizedSize = CommonConstant.MAX_PAGE_SIZE;
+            } else {
+                normalizedSize = size;
+            }
+
+            // 调用服务层查询
+            IPage<StoreStaffConfig> result = storeStaffConfigService.queryStaffList(
+                    normalizedPage, normalizedSize, storeId, status, staffPosition);
+
+            log.info("查询员工列表成功,店铺ID={},共{}条记录,当前页{}条",
+                    storeId, result.getTotal(), result.getRecords() != null ? result.getRecords().size() : 0);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.warn("查询员工列表参数错误,storeId={},错误信息:{}", storeId, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("查询员工列表异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
+            return R.fail("查询员工列表失败,请稍后重试");
+        }
     }
 
     /**
      * 员工详情查询接口(用户端)
+     * <p>
+     * 查询员工基本信息以及关联的演出安排列表
+     * 演出安排列表包含未来30天内的所有演出时间,格式:12月12日 周五 20:30-次日00:00
+     * </p>
      *
-     * @param id 员工主键id
-     * @return 员工详情
+     * @param id 员工主键ID,必填,必须大于0
+     * @return 员工详情(包含演出列表)
      */
-    @ApiOperation("员工详情查询(用户端)")
+    @ApiOperation("员工详情查询(用户端,包含演出列表)")
     @ApiOperationSupport(order = 6)
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "id", value = "员工主键id", dataType = "Integer", paramType = "query", required = true)
+            @ApiImplicitParam(name = "id", value = "员工主键ID", dataType = "Integer", paramType = "query", required = true)
     })
     @GetMapping("/queryStaffDetail")
-    public R<StoreStaffConfig> queryStaffDetail(@RequestParam(value = "id", required = true) Integer id) {
-        log.info("StoreStaffConfigController.queryStaffDetail?id={}", id);
-        StoreStaffConfig result = storeStaffConfigService.queryStaffDetail(id);
-        if (result == null) {
-            return R.fail("员工不存在");
+    public R<StoreStaffDetailWithPerformanceVo> queryStaffDetail(
+            @RequestParam(value = "id", required = true) Integer id) {
+        log.info("查询员工详情(包含演出列表),id={}", id);
+
+        try {
+            // 参数校验
+            if (id == null || id <= 0) {
+                log.warn("查询员工详情参数校验失败,员工ID无效:id={}", id);
+                return R.fail("员工ID不能为空且必须大于0");
+            }
+
+            // 调用服务层查询
+            StoreStaffDetailWithPerformanceVo result =
+                    storeStaffConfigService.queryStaffDetailWithPerformance(id);
+
+            if (result == null) {
+                log.warn("查询员工详情失败,员工不存在:id={}", id);
+                return R.fail("员工不存在");
+            }
+
+            log.info("查询员工详情成功,id={},演出安排数量:{}",
+                    id, result.getPerformanceScheduleList() != null ? result.getPerformanceScheduleList().size() : 0);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.warn("查询员工详情参数错误,id={},错误信息:{}", id, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("查询员工详情异常,id={},异常信息:{}", id, e.getMessage(), e);
+            return R.fail("查询员工详情失败,请稍后重试");
         }
-        return R.data(result);
     }
 
     /**
-     * 查询擅长类型和标签(用于人员配置)
-     * 返回格式:{types: [{id, dictId, dictDetail, typeDetail, tags: [...]}]}
+     * 员工列表查询(按标题分组)(用户端)
+     * <p>
+     * 根据店铺ID查询store_staff_title表中的记录,并通过staff_ids关联出store_staff_config
+     * 返回按标题分组的员工列表,每个标题下包含对应的员工信息
+     * 保留原有逻辑:今日是否有演出、点赞数、好评数等
+     * </p>
      *
-     * @param businessSection 经营板块id(词典表 键为 business_section),可选
-     * @return 擅长类型和标签的树形结构
+     * @param storeId 店铺ID,必填,必须大于0
+     * @return 按标题分组的员工列表
      */
-    @ApiOperation("查询擅长类型和标签(商家端)")
+    @ApiOperation("员工列表查询(按标题分组)(用户端)")
     @ApiOperationSupport(order = 7)
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "businessSection", value = "经营板块id(词典表 键为 business_section)", dataType = "Integer", paramType = "query", required = false)
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true)
     })
-    @GetMapping("/getProficientTypesAndTags")
-    public R<Map<String, Object>> getProficientTypesAndTags(
-            @RequestParam(value = "businessSection", required = false) Integer businessSection) {
-        log.info("StoreStaffConfigController.getProficientTypesAndTags?businessSection={}", businessSection);
-
-        // 查询经营板块(business_section)
-        LambdaQueryWrapper<StoreDictionary> typeWrapper = new LambdaQueryWrapper<>();
-        typeWrapper.eq(StoreDictionary::getTypeName, "business_section");
-        typeWrapper.eq(StoreDictionary::getDeleteFlag, 0);
-        typeWrapper.isNull(StoreDictionary::getParentId);
-
-        // 如果指定了经营板块,根据 businessSection(dict_id) 过滤
-        if (businessSection != null) {
-            typeWrapper.eq(StoreDictionary::getDictId, String.valueOf(businessSection));
-        }
+    @GetMapping("/queryStaffListByTitle")
+    public R<List<StaffTitleGroupVo>> queryStaffListByTitle(
+            @RequestParam(value = "storeId") Integer storeId) {
+        log.info("查询员工列表(按标题分组),参数:storeId={}", storeId);
 
-        List<StoreDictionary> types = storeDictionaryMapper.selectList(typeWrapper);
+        try {
+            // 参数校验
+            if (storeId == null || storeId <= 0) {
+                log.warn("查询员工列表(按标题分组)参数校验失败,店铺ID无效:storeId={}", storeId);
+                return R.fail("店铺ID不能为空且必须大于0");
+            }
 
-        // 如果指定了经营板块,只查询对应板块的标签
-        List<Integer> typeIds = types.stream().map(StoreDictionary::getId).collect(Collectors.toList());
+            // 调用服务层查询
+            List<StaffTitleGroupVo> result = storeStaffConfigService.queryStaffListByTitle(storeId);
 
-        // 查询擅长标签(proficient_tag),其 parent_id 指向 business_section 的主键id
-        LambdaQueryWrapper<StoreDictionary> tagWrapper = new LambdaQueryWrapper<>();
-        tagWrapper.eq(StoreDictionary::getTypeName, "proficient_tag");
-        tagWrapper.eq(StoreDictionary::getDeleteFlag, 0);
-        if (!typeIds.isEmpty()) {
-            tagWrapper.in(StoreDictionary::getParentId, typeIds);
+            log.info("查询员工列表(按标题分组)成功,店铺ID={},标题数量:{}", storeId, result != null ? result.size() : 0);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.warn("查询员工列表(按标题分组)参数错误,storeId={},错误信息:{}", storeId, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("查询员工列表(按标题分组)异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
+            return R.fail("查询员工列表(按标题分组)失败,请稍后重试");
         }
-        List<StoreDictionary> allTags = storeDictionaryMapper.selectList(tagWrapper);
-
-        // 构建结果:按 parent_id 分组标签
-        Map<Integer, List<StoreDictionary>> tagsByParent = allTags.stream()
-                .filter(tag -> tag.getParentId() != null)
-                .collect(Collectors.groupingBy(StoreDictionary::getParentId));
-
-        // 构建返回结果
-        List<Map<String, Object>> resultList = types.stream().map(type -> {
-            Map<String, Object> typeMap = new HashMap<>();
-            typeMap.put("id", type.getId());
-            typeMap.put("dictId", type.getDictId());
-            typeMap.put("dictDetail", type.getDictDetail());
-            typeMap.put("typeDetail", type.getTypeDetail());
-
-            // 获取该类型下的标签
-            List<StoreDictionary> tags = tagsByParent.getOrDefault(type.getId(), new java.util.ArrayList<>());
-            List<Map<String, Object>> tagList = tags.stream().map(tag -> {
-                Map<String, Object> tagMap = new HashMap<>();
-                tagMap.put("id", tag.getId());
-                tagMap.put("dictId", tag.getDictId());
-                tagMap.put("dictDetail", tag.getDictDetail());
-                return tagMap;
-            }).collect(Collectors.toList());
-            typeMap.put("tags", tagList);
-
-            return typeMap;
-        }).collect(Collectors.toList());
-
-        Map<String, Object> result = new HashMap<>();
-        result.put("types", resultList);
-
-        return R.data(result);
     }
 
+    /**
+     * 根据标题ID查询员工列表(用户端)
+     * <p>
+     * 根据店铺ID和标题ID查询store_staff_title表中的记录,并通过staff_ids关联出store_staff_config
+     * 返回该标题下的员工列表
+     * 保留原有逻辑:今日是否有演出、点赞数、好评数等
+     * </p>
+     *
+     * @param storeId 店铺ID,必填,必须大于0
+     * @param titleId 标题ID,必填,必须大于0
+     * @return 员工列表
+     */
+    @ApiOperation("根据标题ID查询员工列表(用户端)")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "titleId", value = "标题ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/queryStaffListByTitleId")
+    public R<List<StoreStaffConfig>> queryStaffListByTitleId(
+            @RequestParam(value = "storeId") Integer storeId,
+            @RequestParam(value = "titleId") Integer titleId) {
+        log.info("根据标题ID查询员工列表,参数:storeId={}, titleId={}", storeId, titleId);
+
+        try {
+            // 参数校验
+            if (storeId == null || storeId <= 0) {
+                log.warn("根据标题ID查询员工列表参数校验失败,店铺ID无效:storeId={}", storeId);
+                return R.fail("店铺ID不能为空且必须大于0");
+            }
+            if (titleId == null || titleId <= 0) {
+                log.warn("根据标题ID查询员工列表参数校验失败,标题ID无效:titleId={}", titleId);
+                return R.fail("标题ID不能为空且必须大于0");
+            }
+
+            // 调用服务层查询
+            List<StoreStaffConfig> result = storeStaffConfigService.queryStaffListByTitleId(storeId, titleId);
+
+            log.info("根据标题ID查询员工列表成功,店铺ID={},标题ID={},员工数量:{}",
+                    storeId, titleId, result != null ? result.size() : 0);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.warn("根据标题ID查询员工列表参数错误,storeId={}, titleId={},错误信息:{}",
+                    storeId, titleId, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("根据标题ID查询员工列表异常,storeId={}, titleId={},异常信息:{}",
+                    storeId, titleId, e.getMessage(), e);
+            return R.fail("根据标题ID查询员工列表失败,请稍后重试");
+        }
+    }
 
     @ApiOperation("获取美食员工列表")
     @ApiOperationSupport(order = 7)

+ 185 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreStaffReviewController.java

@@ -0,0 +1,185 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreStaffReview;
+import shop.alien.entity.store.dto.StoreStaffReviewDto;
+import shop.alien.entity.store.vo.StoreStaffReviewDetailVo;
+import shop.alien.entity.store.vo.StoreStaffReviewListWithStaffVo;
+import shop.alien.entity.store.vo.StoreStaffReviewVo;
+import shop.alien.store.service.StoreStaffReviewService;
+
+/**
+ * 员工评价 前端控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"员工评价"})
+@ApiSort(20)
+@CrossOrigin
+@RestController
+@RequestMapping("/store/staffReview")
+@RequiredArgsConstructor
+public class StoreStaffReviewController {
+
+    private final StoreStaffReviewService storeStaffReviewService;
+
+    @ApiOperation("创建员工评价")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "staffUserId", value = "员工用户ID", dataTypeClass = Integer.class, paramType = "body", required = true),
+            @ApiImplicitParam(name = "overallRating", value = "总体评分(1-5星,支持0.5星)", dataTypeClass = java.math.BigDecimal.class, paramType = "body"),
+            @ApiImplicitParam(name = "serviceAttitudeRating", value = "服务态度评分(1-5星,支持0.5星)", dataTypeClass = java.math.BigDecimal.class, paramType = "body"),
+            @ApiImplicitParam(name = "responseTimeRating", value = "响应时间评分(1-5星,支持0.5星)", dataTypeClass = java.math.BigDecimal.class, paramType = "body"),
+            @ApiImplicitParam(name = "professionalAbilityRating", value = "专业能力评分(1-5星,支持0.5星)", dataTypeClass = java.math.BigDecimal.class, paramType = "body"),
+            @ApiImplicitParam(name = "reviewContent", value = "评价内容", dataTypeClass = String.class, paramType = "body"),
+            @ApiImplicitParam(name = "reviewImages", value = "评价图片列表", dataTypeClass = java.util.List.class, paramType = "body"),
+            @ApiImplicitParam(name = "isAnonymous", value = "是否匿名评价,0:否,1:是", dataTypeClass = Integer.class, paramType = "body"),
+            @ApiImplicitParam(name = "userId", value = "用户ID(必填)", dataTypeClass = Integer.class, paramType = "body", required = true)
+    })
+    @PostMapping("/create")
+    public R<StoreStaffReview> createReview(@RequestBody StoreStaffReviewDto reviewDto) {
+        log.info("创建员工评价, reviewDto={}", reviewDto);
+        if (reviewDto == null || reviewDto.getUserId() == null) {
+            return R.fail("用户未登录");
+        }
+        return storeStaffReviewService.createReview(reviewDto);
+    }
+
+    @ApiOperation("获取评价详情(包含评论和回复)")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataTypeClass = Integer.class, paramType = "query", required = false)
+    })
+    @GetMapping("/detail/reviewId")
+    public R<StoreStaffReviewDetailVo> getReviewDetail(
+            @RequestParam Integer reviewId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("获取评价详情, reviewId={}, currentUserId={}", reviewId, currentUserId);
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+        return storeStaffReviewService.getReviewDetail(reviewId, currentUserId);
+    }
+
+    @ApiOperation("点赞评价")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    })
+    @PostMapping("/like")
+    public R<Boolean> likeReview(@RequestParam Integer reviewId,
+                                  @RequestParam Integer userId) {
+        log.info("点赞评价, reviewId={}, userId={}", reviewId, userId);
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户未登录");
+        }
+        return storeStaffReviewService.likeReview(reviewId, userId);
+    }
+
+    @ApiOperation("取消点赞评价")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    })
+    @PostMapping("/cancelLike")
+    public R<Boolean> cancelLikeReview(@RequestParam Integer reviewId,
+                                       @RequestParam Integer userId) {
+        log.info("取消点赞评价, reviewId={}, userId={}", reviewId, userId);
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户未登录");
+        }
+        return storeStaffReviewService.cancelLikeReview(reviewId, userId);
+    }
+
+    @ApiOperation("分页查询评价列表")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页数(默认1)", dataTypeClass = Integer.class, paramType = "query", required = false),
+            @ApiImplicitParam(name = "size", value = "页容(默认10)", dataTypeClass = Integer.class, paramType = "query", required = false),
+            @ApiImplicitParam(name = "staffUserId", value = "员工用户ID", dataTypeClass = Integer.class, paramType = "query", required = false),
+            @ApiImplicitParam(name = "userId", value = "评价用户ID", dataTypeClass = Integer.class, paramType = "query", required = false),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataTypeClass = Integer.class, paramType = "query", required = false)
+    })
+    @GetMapping("/list")
+    public R<IPage<StoreStaffReviewVo>> getReviewList(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size,
+            @RequestParam(required = false) Integer staffUserId,
+            @RequestParam(required = false) Integer userId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("分页查询评价列表, page={}, size={}, staffUserId={}, userId={}, currentUserId={}",
+                page, size, staffUserId, userId, currentUserId);
+        return storeStaffReviewService.getReviewList(page, size, staffUserId, userId, currentUserId);
+    }
+
+    @ApiOperation(value = "用户删除评价", notes = "只能删除自己的评价,删除评价时,会级联删除该评价下的所有评论和回复")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID(必填,只能删除自己的评价)", dataTypeClass = Integer.class, paramType = "query", required = true)
+    })
+    @PostMapping("/delete/reviewId")
+    public R<Boolean> deleteReview(@RequestParam Integer reviewId,
+                                   @RequestParam Integer userId) {
+        log.info("用户删除评价, reviewId={}, userId={}", reviewId, userId);
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+        return storeStaffReviewService.deleteReview(reviewId, userId);
+    }
+
+    @ApiOperation(value = "管理员删除评价", notes = "可以删除任何评价,删除评价时,会级联删除该评价下的所有评论和回复")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    })
+    @PostMapping("/admin/delete/reviewId")
+    public R<Boolean> deleteReviewByAdmin(@RequestParam Integer reviewId) {
+        log.info("管理员删除评价, reviewId={}", reviewId);
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+        return storeStaffReviewService.deleteReviewByAdmin(reviewId);
+    }
+
+    @ApiOperation("根据人员ID查询评价列表(包含人员信息)")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页数(默认1)", dataTypeClass = Integer.class, paramType = "query", required = false),
+            @ApiImplicitParam(name = "size", value = "页容(默认10)", dataTypeClass = Integer.class, paramType = "query", required = false),
+            @ApiImplicitParam(name = "staffUserId", value = "人员ID(必填)", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataTypeClass = Integer.class, paramType = "query", required = false)
+    })
+    @GetMapping("/list/byStaffId")
+    public R<StoreStaffReviewListWithStaffVo> getReviewListByStaffId(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size,
+            @RequestParam Integer staffUserId,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("根据人员ID查询评价列表, page={}, size={}, staffUserId={}, currentUserId={}",
+                page, size, staffUserId, currentUserId);
+        if (staffUserId == null) {
+            return R.fail("人员ID不能为空");
+        }
+        return storeStaffReviewService.getReviewListByStaffId(page, size, staffUserId, currentUserId);
+    }
+}

+ 252 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreStaffTitleController.java

@@ -0,0 +1,252 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreStaffTitle;
+import shop.alien.store.service.StoreStaffTitleService;
+import shop.alien.store.util.CommonConstant;
+
+/**
+ * 员工标题Controller
+ * 提供员工标题的增删改查接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"员工标题管理"})
+@CrossOrigin
+@RestController
+@RequestMapping("/storeStaffTitle")
+@RequiredArgsConstructor
+public class StoreStaffTitleController {
+
+    private final StoreStaffTitleService storeStaffTitleService;
+
+    /**
+     * 分页查询员工标题列表
+     * <p>
+     * 根据店铺ID查询员工标题列表,支持分页
+     * 只返回未删除的记录
+     * </p>
+     *
+     * @param page    分页页数,默认1,必须大于0
+     * @param size    分页条数,默认10,必须大于0且不超过100
+     * @param storeId 店铺ID,可选
+     * @return 员工标题列表分页结果
+     */
+    @ApiOperation("分页查询员工标题列表")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "分页页数", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "size", value = "分页条数", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = false)
+    })
+    @GetMapping("/query")
+    public R<IPage<StoreStaffTitle>> queryStaffTitleList(
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "size", defaultValue = "10") Integer size,
+            @RequestParam(value = "storeId", required = false) Integer storeId) {
+        log.info("查询员工标题列表,参数:page={}, size={}, storeId={}", page, size, storeId);
+
+        try {
+            // 参数校验
+            if (page != null && page < 1) {
+                log.warn("查询员工标题列表参数校验失败,分页页数无效:page={}", page);
+                return R.fail("分页页数必须大于0");
+            }
+            if (size != null && size < 1) {
+                log.warn("查询员工标题列表参数校验失败,分页条数无效:size={}", size);
+                return R.fail("分页条数必须大于0");
+            }
+            if (size != null && size > CommonConstant.MAX_PAGE_SIZE) {
+                log.warn("查询员工标题列表参数校验失败,分页条数超过最大值:size={}", size);
+                return R.fail("分页条数不能超过" + CommonConstant.MAX_PAGE_SIZE);
+            }
+
+            // 规范化分页参数
+            Integer normalizedPage = (page == null || page < 1) ? CommonConstant.DEFAULT_PAGE_NUM : page;
+            Integer normalizedSize;
+            if (size == null || size < 1) {
+                normalizedSize = CommonConstant.DEFAULT_PAGE_SIZE;
+            } else if (size > CommonConstant.MAX_PAGE_SIZE) {
+                normalizedSize = CommonConstant.MAX_PAGE_SIZE;
+            } else {
+                normalizedSize = size;
+            }
+
+            // 调用服务层查询
+            IPage<StoreStaffTitle> result = storeStaffTitleService.queryStaffTitleList(
+                    normalizedPage, normalizedSize, storeId);
+
+            log.info("查询员工标题列表成功,共{}条记录,当前页{}条",
+                    result.getTotal(), result.getRecords() != null ? result.getRecords().size() : 0);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.warn("查询员工标题列表参数错误,错误信息:{}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("查询员工标题列表异常,异常信息:{}", e.getMessage(), e);
+            return R.fail("查询员工标题列表失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 新增员工标题
+     * <p>
+     * 新增员工标题,自动计算员工数量
+     * </p>
+     *
+     * @param storeStaffTitle 员工标题信息
+     * @return 新增结果
+     */
+    @ApiOperation("新增员工标题")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/createTitle")
+    public R<String> addStaffTitle(@RequestBody StoreStaffTitle storeStaffTitle) {
+        log.info("新增员工标题,参数:{}", storeStaffTitle);
+
+        try {
+            Integer result = storeStaffTitleService.addStaffTitle(storeStaffTitle);
+
+            if (result > 0) {
+                log.info("新增员工标题成功,id={}", storeStaffTitle.getId());
+                return R.data("新增员工标题成功");
+            } else {
+                log.warn("新增员工标题失败,插入记录数为0");
+                return R.fail("新增员工标题失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.warn("新增员工标题参数错误,错误信息:{}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("新增员工标题异常,异常信息:{}", e.getMessage(), e);
+            return R.fail("新增员工标题失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 更新员工标题
+     * <p>
+     * 更新员工标题信息,自动重新计算员工数量
+     * </p>
+     *
+     * @param storeStaffTitle 员工标题信息
+     * @return 更新结果
+     */
+    @ApiOperation("更新员工标题")
+    @ApiOperationSupport(order = 3)
+    @PostMapping("/updateTitle")
+    public R<String> updateStaffTitle(@RequestBody StoreStaffTitle storeStaffTitle) {
+        log.info("更新员工标题,参数:{}", storeStaffTitle);
+
+        try {
+            Integer result = storeStaffTitleService.updateStaffTitle(storeStaffTitle);
+
+            if (result > 0) {
+                log.info("更新员工标题成功,id={}", storeStaffTitle.getId());
+                return R.data("更新员工标题成功");
+            } else {
+                log.warn("更新员工标题失败,更新记录数为0,id={}", storeStaffTitle.getId());
+                return R.fail("更新员工标题失败,记录不存在或已被删除");
+            }
+        } catch (IllegalArgumentException e) {
+            log.warn("更新员工标题参数错误,错误信息:{}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("更新员工标题异常,id={},异常信息:{}", storeStaffTitle.getId(), e.getMessage(), e);
+            return R.fail("更新员工标题失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 删除员工标题(逻辑删除)
+     * <p>
+     * 根据ID逻辑删除员工标题
+     * </p>
+     *
+     * @param id 员工标题ID,必填,必须大于0
+     * @return 删除结果
+     */
+    @ApiOperation("删除员工标题")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "员工标题ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/deleteTitle")
+    public R<String> deleteStaffTitle(@RequestParam(value = "id") Integer id) {
+        log.info("删除员工标题,参数:id={}", id);
+
+        try {
+            // 参数校验
+            if (id == null || id <= 0) {
+                log.warn("删除员工标题参数校验失败,ID无效:id={}", id);
+                return R.fail("员工标题ID不能为空且必须大于0");
+            }
+
+            Integer result = storeStaffTitleService.deleteStaffTitle(id);
+
+            if (result > 0) {
+                log.info("删除员工标题成功,id={}", id);
+                return R.data("删除员工标题成功");
+            } else {
+                log.warn("删除员工标题失败,删除记录数为0,id={}", id);
+                return R.fail("删除员工标题失败,记录不存在或已被删除");
+            }
+        } catch (IllegalArgumentException e) {
+            log.warn("删除员工标题参数错误,id={},错误信息:{}", id, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("删除员工标题异常,id={},异常信息:{}", id, e.getMessage(), e);
+            return R.fail("删除员工标题失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 查询员工标题详情
+     * <p>
+     * 根据门店ID查询员工标题详细信息(一个门店只能有一个标题)
+     * </p>
+     *
+     * @param storeId 门店ID,必填,必须大于0
+     * @return 员工标题详情
+     */
+    @ApiOperation("查询员工标题详情")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/detail")
+    public R<StoreStaffTitle> getStaffTitleDetail(@RequestParam(value = "storeId") Integer storeId) {
+        log.info("查询员工标题详情,参数:storeId={}", storeId);
+
+        try {
+            // 参数校验
+            if (storeId == null || storeId <= 0) {
+                log.warn("查询员工标题详情参数校验失败,门店ID无效:storeId={}", storeId);
+                return R.fail("门店ID不能为空且必须大于0");
+            }
+
+            StoreStaffTitle result = storeStaffTitleService.getStaffTitleDetail(storeId);
+
+            if (result == null) {
+                log.warn("查询员工标题详情失败,记录不存在:storeId={}", storeId);
+                return R.fail("该门店还没有创建标题");
+            }
+
+            log.info("查询员工标题详情成功,storeId={},id={},标题名称={}", storeId, result.getId(), result.getTitleName());
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.warn("查询员工标题详情参数错误,storeId={},错误信息:{}", storeId, e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("查询员工标题详情异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
+            return R.fail("查询员工标题详情失败,请稍后重试");
+        }
+    }
+}
+

+ 176 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreVideoController.java

@@ -0,0 +1,176 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreVideo;
+import shop.alien.store.service.StoreVideoService;
+
+import java.util.List;
+
+/**
+ * 门店视频Controller
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"门店视频"})
+@ApiSort(6)
+@CrossOrigin
+@RestController
+@RequestMapping("/video")
+@RequiredArgsConstructor
+public class StoreVideoController {
+    private final StoreVideoService storeVideoService;
+
+    @ApiOperation("新增视频")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/save")
+    public R<String> save(@RequestBody StoreVideo storeVideo) {
+        log.info("StoreVideoController.save?storeVideo={}", storeVideo);
+        if (storeVideoService.save(storeVideo)) {
+            return R.success("新增成功");
+        }
+        return R.fail("新增失败");
+    }
+
+    @ApiOperation("批量新增视频")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/saveBatch")
+    public R<String> saveBatch(@RequestBody List<StoreVideo> storeVideoList) {
+        log.info("StoreVideoController.saveBatch?storeVideoList={}", storeVideoList);
+        if (storeVideoService.saveBatch(storeVideoList)) {
+            return R.success("批量新增成功");
+        }
+        return R.fail("批量新增失败");
+    }
+
+    @ApiOperation("修改视频")
+    @ApiOperationSupport(order = 3)
+    @PostMapping("/update")
+    public R<String> update(@RequestBody StoreVideo storeVideo) {
+        log.info("StoreVideoController.update?storeVideo={}", storeVideo);
+        if (storeVideoService.updateById(storeVideo)) {
+            return R.success("修改成功");
+        }
+        return R.fail("修改失败");
+    }
+
+    @ApiOperation("新增或修改视频")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/saveOrUpdate")
+    public R<String> saveOrUpdate(@RequestBody StoreVideo storeVideo) {
+        log.info("StoreVideoController.saveOrUpdate?storeVideo={}", storeVideo);
+        if (storeVideoService.saveOrUpdate(storeVideo)) {
+            if (storeVideo.getId() != null) {
+                return R.success("修改成功");
+            }
+            return R.success("新增成功");
+        }
+        return R.fail("操作失败");
+    }
+
+    @ApiOperation("批量新增或修改视频")
+    @ApiOperationSupport(order = 5)
+    @PostMapping("/saveOrUpdateBatch")
+    public R<String> saveOrUpdateBatch(@RequestBody List<StoreVideo> storeVideoList) {
+        log.info("StoreVideoController.saveOrUpdateBatch?storeVideoList={}", storeVideoList);
+        if (storeVideoService.saveOrUpdateBatch(storeVideoList)) {
+            return R.success("批量操作成功");
+        }
+        return R.fail("批量操作失败");
+    }
+
+    @ApiOperation("根据ID删除视频")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "视频ID", dataType = "Integer", paramType = "query", required = true)})
+    @PostMapping("/deleteById")
+    public R<String> deleteById(Integer id) {
+        log.info("StoreVideoController.deleteById?id={}", id);
+        if (storeVideoService.removeById(id)) {
+            return R.success("删除成功");
+        }
+        return R.fail("删除失败");
+    }
+
+    @ApiOperation("批量删除视频")
+    @ApiOperationSupport(order = 7)
+    @PostMapping("/deleteBatch")
+    public R<String> deleteBatch(@RequestBody List<Integer> ids) {
+        log.info("StoreVideoController.deleteBatch?ids={}", ids);
+        if (storeVideoService.removeByIds(ids)) {
+            return R.success("批量删除成功");
+        }
+        return R.fail("批量删除失败");
+    }
+
+    @ApiOperation("根据ID查询视频")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "视频ID", dataType = "Integer", paramType = "query", required = true)})
+    @GetMapping("/getById")
+    public R<StoreVideo> getById(Integer id) {
+        log.info("StoreVideoController.getById?id={}", id);
+        StoreVideo storeVideo = storeVideoService.getById(id);
+        if (storeVideo != null) {
+            return R.data(storeVideo);
+        }
+        return R.fail("未找到数据");
+    }
+
+    @ApiOperation("根据门店ID查询视频列表")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParams({@ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true)})
+    @GetMapping("/getByStoreId")
+    public R<List<StoreVideo>> getByStoreId(Integer storeId) {
+        log.info("StoreVideoController.getByStoreId?storeId={}", storeId);
+        return R.data(storeVideoService.getByStoreId(storeId));
+    }
+
+    @ApiOperation("根据门店ID和业务ID查询视频列表")
+    @ApiOperationSupport(order = 10)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "businessId", value = "业务ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getByStoreIdAndBusinessId")
+    public R<List<StoreVideo>> getByStoreIdAndBusinessId(Integer storeId, Integer businessId) {
+        log.info("StoreVideoController.getByStoreIdAndBusinessId?storeId={}&businessId={}", storeId, businessId);
+        return R.data(storeVideoService.getByStoreIdAndBusinessId(storeId, businessId));
+    }
+
+    @ApiOperation("分页查询视频列表")
+    @ApiOperationSupport(order = 11)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "current", value = "当前页", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "size", value = "每页数量", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "businessId", value = "业务ID", dataType = "Integer", paramType = "query", required = false)
+    })
+    @GetMapping("/page")
+    public R<IPage<StoreVideo>> page(Integer current, Integer size, Integer storeId, Integer businessId) {
+        log.info("StoreVideoController.page?current={}&size={}&storeId={}&businessId={}", current, size, storeId, businessId);
+        Page<StoreVideo> page = new Page<>(current, size);
+        LambdaQueryWrapper<StoreVideo> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(storeId != null, StoreVideo::getStoreId, storeId)
+                .eq(businessId != null, StoreVideo::getBusinessId, businessId)
+                .orderByAsc(StoreVideo::getImgSort)
+                .orderByDesc(StoreVideo::getCreatedTime);
+        IPage<StoreVideo> result = storeVideoService.page(page, queryWrapper);
+        return R.data(result);
+    }
+
+    @ApiOperation("查询所有视频列表")
+    @ApiOperationSupport(order = 12)
+    @GetMapping("/list")
+    public R<List<StoreVideo>> list() {
+        log.info("StoreVideoController.list");
+        return R.data(storeVideoService.list());
+    }
+}
+

+ 56 - 0
alien-store/src/main/java/shop/alien/store/service/PerformanceListService.java

@@ -0,0 +1,56 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.store.vo.PerformanceDetailVo;
+import shop.alien.entity.store.vo.PerformanceGroupListVo;
+import shop.alien.entity.store.vo.PerformanceListVo;
+
+/**
+ * 演出列表服务接口
+ * 提供用户端演出列表查询功能
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface PerformanceListService {
+
+    /**
+     * 查询演出列表(用户端)
+     * <p>
+     * 根据店铺ID查询演出列表,支持分页
+     * 只返回审核通过、已上线、未删除的演出
+     * </p>
+     *
+     * @param page    分页页数,必须大于0
+     * @param size    分页条数,必须大于0
+     * @param storeId 店铺ID,必须大于0
+     * @return 演出列表分页结果
+     */
+    IPage<PerformanceListVo> queryPerformanceList(Integer page, Integer size, Integer storeId);
+
+    /**
+     * 查询按日期分组的演出列表(用户端)
+     * <p>
+     * 根据店铺ID查询演出列表,按日期分组返回
+     * 只返回审核通过、已上线、未删除的演出
+     * 返回未来30天内有演出的日期及其对应的演出列表
+     * </p>
+     *
+     * @param storeId 店铺ID,必须大于0
+     * @return 按日期分组的演出列表
+     */
+    PerformanceGroupListVo queryPerformanceListGroupByDate(Integer storeId);
+
+    /**
+     * 查询演出详情(用户端)
+     * <p>
+     * 根据演出ID查询演出详细信息
+     * 包含演出基本信息、演出时间、演出风格、演出详情描述和演出嘉宾列表
+     * </p>
+     *
+     * @param id 演出ID,必须大于0
+     * @return 演出详情,如果演出不存在则返回null
+     */
+    PerformanceDetailVo queryPerformanceDetail(Integer id);
+}
+

+ 36 - 0
alien-store/src/main/java/shop/alien/store/service/StoreCuisineComboService.java

@@ -0,0 +1,36 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.StoreCuisineCombo;
+import shop.alien.entity.store.dto.CuisineComboDto;
+
+import java.util.List;
+
+/**
+ * 美食套餐表(辅助详情查询) 服务类
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+public interface StoreCuisineComboService extends IService<StoreCuisineCombo> {
+
+    /**
+     * 批量保存套餐单品关联关系
+     *
+     * @param comboList 套餐单品关联列表
+     * @return boolean
+     */
+    boolean saveBatch(List<StoreCuisineCombo> comboList);
+
+    /**
+     * 根据套餐id删除套餐单品关联关系
+     *
+     * @param cid 套餐id
+     * @return boolean
+     */
+    boolean deleteByComboId(Integer cid);
+
+}
+
+
+

+ 46 - 0
alien-store/src/main/java/shop/alien/store/service/StoreCuisineService.java

@@ -0,0 +1,46 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.StoreCuisine;
+import shop.alien.entity.store.dto.CuisineComboDto;
+import shop.alien.entity.store.dto.CuisineDetailDto;
+
+import java.util.List;
+
+/**
+ * 美食价目表 服务类
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+public interface StoreCuisineService extends IService<StoreCuisine> {
+
+    /**
+     * 新增美食(单品或套餐)
+     */
+    boolean addCuisineCombo(CuisineComboDto cuisineComboDto);
+
+    /**
+     * 修改美食(单品或套餐)
+     */
+    boolean updateCuisineCombo(CuisineComboDto cuisineComboDto);
+
+    /**
+     * 获取所有单品名称(cuisine_type = 1),用于添加套餐时选择
+     */
+    List<StoreCuisine> getSingleName();
+
+    /**
+     * 根据 id 与类型获取详情:
+     * - type = 1:返回单品详情
+     * - type = 2:返回套餐详情(主信息 + 套餐包含的单品列表)
+     */
+    CuisineDetailDto getByCuisineType(Integer id, Integer cuisineType);
+
+    /**
+     * 上下架操作:1-上架,2-下架
+     */
+    boolean changeShelfStatus(Integer id, Integer shelfStatus);
+}
+
+

+ 11 - 0
alien-store/src/main/java/shop/alien/store/service/StoreImgService.java

@@ -78,4 +78,15 @@ public interface StoreImgService extends IService<StoreImg> {
      * @param imgUrl 图片URL
      */
     void asyncExtractColorForImg(Integer storeImgId, String imgUrl);
+
+
+    /**
+     * 通过businessId获取图片
+     *
+     * @param storeId 门店id
+     * @param imgType 图片类型, 0:其他, 1:入口图, 2:相册, 3:菜品, 4:环境, 5:价目表, 6:推荐菜, 7:菜单, 8:用户评论, 9:商家申诉, 10:商家头像, 11:店铺轮播图
+     * @return list
+     */
+    List<StoreImg> getByCover(Integer storeId, Integer imgType);
+
 }

+ 11 - 0
alien-store/src/main/java/shop/alien/store/service/StoreInfoService.java

@@ -355,6 +355,17 @@ public interface StoreInfoService extends IService<StoreInfo> {
      */
     List<StoreInfoVo> getMoreRecommendedStores(Double lon , Double lat, String businessSection, String businessTypes, String businessClassify);
 
+    /**
+     * 根据店铺ID获取推荐店铺列表(用户端)
+     * <p>
+     * 只使用store_id查询,不包含其他筛选条件(经营板块、经营分类、分类、经纬度等)
+     * </p>
+     *
+     * @param storeId 店铺ID,必填,必须大于0
+     * @return 推荐店铺列表
+     */
+    List<StoreInfoVo> getRecommendedStoresByStoreId(Integer storeId);
+
 
     /**
      * 根据门店及图片地址查询最新OCR识别数据

+ 5 - 2
alien-store/src/main/java/shop/alien/store/service/StoreOfficialAlbumService.java

@@ -23,11 +23,14 @@ public interface StoreOfficialAlbumService extends IService<StoreOfficialAlbum>
      * 查询条件:imgType = 2(官方相册),通过 business_id 关联到 store_official_album 表,按 albumName 筛选
      * </p>
      *
-     * @param storeId   门店ID,必填
+     * @param storeId   门店ID,必填,必须大于0
      * @param albumName 相册名称,可选。例如:酒水、餐食、环境、全部等。当为null或空字符串时,查询全部
+     * @param type      类型,必填。1:视频,2:相册,3:环境
+     * @param pageNum   页码,可选,默认1
+     * @param pageSize  每页数量,可选,默认10
      * @return 图片列表和总数
      */
-    StoreOfficialAlbumImgVo getOfficialAlbumImgList(Integer storeId, String albumName);
+    StoreOfficialAlbumImgVo getOfficialAlbumImgList(Integer storeId, String albumName, Integer type, Integer pageNum, Integer pageSize);
 
     /**
      * 获取官方相册名称列表(客户端)

+ 15 - 0
alien-store/src/main/java/shop/alien/store/service/StorePriceService.java

@@ -0,0 +1,15 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.StorePrice;
+
+/**
+ * 通用价目表 服务类
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+public interface StorePriceService extends IService<StorePrice> {
+
+}
+

+ 88 - 0
alien-store/src/main/java/shop/alien/store/service/StoreStaffCommentService.java

@@ -0,0 +1,88 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreStaffComment;
+import shop.alien.entity.store.dto.StoreStaffCommentRequestDto;
+import shop.alien.entity.store.dto.StoreStaffReplyDto;
+import shop.alien.entity.store.vo.StoreStaffCommentVo;
+
+import java.util.List;
+
+/**
+ * 员工评论 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreStaffCommentService extends IService<StoreStaffComment> {
+
+    /**
+     * 创建评论(其他用户对评价的评论)
+     *
+     * @param comment 评论实体
+     * @return R<StoreStaffComment>
+     */
+    R<StoreStaffComment> createComment(StoreStaffComment comment);
+
+    /**
+     * 根据评价ID查询评论列表
+     *
+     * @param reviewId 评价ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<List<StoreStaffCommentVo>>
+     */
+    R<List<StoreStaffCommentVo>> getCommentListByReviewId(Integer reviewId, Integer currentUserId);
+
+    /**
+     * 点赞评论
+     *
+     * @param requestDto 请求DTO
+     * @return R<Boolean>
+     */
+    R<Boolean> likeComment(StoreStaffCommentRequestDto requestDto);
+
+    /**
+     * 取消点赞评论
+     *
+     * @param requestDto 请求DTO
+     * @return R<Boolean>
+     */
+    R<Boolean> cancelLikeComment(StoreStaffCommentRequestDto requestDto);
+
+    /**
+     * 创建回复(用户对评论的回复)
+     *
+     * @param replyDto 回复DTO
+     * @return R<StoreStaffComment>
+     */
+    R<StoreStaffComment> createReply(StoreStaffReplyDto replyDto);
+
+    /**
+     * 根据首评ID查询回复列表
+     *
+     * @param headId 首评ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<List<StoreStaffCommentVo>>
+     */
+    R<List<StoreStaffCommentVo>> getReplyListByHeadId(Integer headId, Integer currentUserId);
+
+    /**
+     * 删除回复
+     *
+     * @param replyId 回复ID
+     * @param userId 用户ID
+     * @return R<Boolean>
+     */
+    R<Boolean> deleteReply(Integer replyId, Integer userId);
+
+    /**
+     * 删除评论(根据ID,物理删除)
+     * userId有值时只能删除自己发布的评论,userId为空时允许删除任何评论(管理员删除)
+     *
+     * @param comment 评论对象(包含id和userId,userId可选)
+     * @return R<Boolean>
+     */
+    R<Boolean> deleteReviewComment(StoreStaffComment comment);
+}
+

+ 50 - 0
alien-store/src/main/java/shop/alien/store/service/StoreStaffConfigService.java

@@ -90,6 +90,33 @@ public interface StoreStaffConfigService {
     IPage<StoreStaffConfig> queryStaffList(Integer page, Integer size, Integer storeId, String status, String staffPosition);
 
     /**
+     * 员工列表查询(按标题分组)(用户端)
+     * <p>
+     * 根据店铺ID查询store_staff_title表中的记录,并通过staff_ids关联出store_staff_config
+     * 返回按标题分组的员工列表,每个标题下包含对应的员工信息
+     * 保留原有逻辑:今日是否有演出、点赞数、好评数等
+     * </p>
+     *
+     * @param storeId 店铺ID,必须大于0
+     * @return 按标题分组的员工列表
+     */
+    List<shop.alien.entity.store.vo.StaffTitleGroupVo> queryStaffListByTitle(Integer storeId);
+
+    /**
+     * 根据标题ID查询员工列表(用户端)
+     * <p>
+     * 根据店铺ID和标题ID查询store_staff_title表中的记录,并通过staff_ids关联出store_staff_config
+     * 返回该标题下的员工列表
+     * 保留原有逻辑:今日是否有演出、点赞数、好评数等
+     * </p>
+     *
+     * @param storeId 店铺ID,必须大于0
+     * @param titleId 标题ID,必须大于0
+     * @return 员工列表
+     */
+    List<StoreStaffConfig> queryStaffListByTitleId(Integer storeId, Integer titleId);
+
+    /**
      * 员工详情查询(用户端)
      *
      * @param id 员工主键id
@@ -98,6 +125,18 @@ public interface StoreStaffConfigService {
     StoreStaffConfig queryStaffDetail(Integer id);
 
     /**
+     * 员工详情查询(包含演出列表)(用户端)
+     * <p>
+     * 查询员工基本信息以及关联的演出安排列表
+     * 演出安排列表包含未来30天内的所有演出时间
+     * </p>
+     *
+     * @param id 员工主键ID,必须大于0
+     * @return 员工详情(包含演出列表),如果员工不存在则返回null
+     */
+    shop.alien.entity.store.vo.StoreStaffDetailWithPerformanceVo queryStaffDetailWithPerformance(Integer id);
+
+    /**
      * 获取美食员工列表
      *
      * @param page          分页页数
@@ -142,4 +181,15 @@ public interface StoreStaffConfigService {
      * @return 员工职位统计列表,每个元素包含职位名称和对应员工数量
      */
     List<StoreStaffPositionCountVo> getStaffPositionCount(Integer storeId);
+
+    /**
+     * 查询指定店铺的员工职位列表
+     * <p>
+     * 查询指定店铺下所有不重复的职位名称列表,只统计未删除的员工
+     * </p>
+     *
+     * @param storeId 店铺ID,必须大于0
+     * @return 员工职位名称列表
+     */
+    List<String> getStaffPositionList(Integer storeId);
 }

+ 95 - 0
alien-store/src/main/java/shop/alien/store/service/StoreStaffReviewService.java

@@ -0,0 +1,95 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreStaffReview;
+import shop.alien.entity.store.dto.StoreStaffReviewDto;
+import shop.alien.entity.store.vo.StoreStaffReviewDetailVo;
+import shop.alien.entity.store.vo.StoreStaffReviewListWithStaffVo;
+import shop.alien.entity.store.vo.StoreStaffReviewVo;
+
+/**
+ * 员工评价 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreStaffReviewService extends IService<StoreStaffReview> {
+
+    /**
+     * 创建员工评价
+     *
+     * @param reviewDto 评价DTO
+     * @return R<StoreStaffReview>
+     */
+    R<StoreStaffReview> createReview(StoreStaffReviewDto reviewDto);
+
+    /**
+     * 获取评价详情(包含评论和回复)
+     *
+     * @param reviewId 评价ID
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<StoreStaffReviewDetailVo>
+     */
+    R<StoreStaffReviewDetailVo> getReviewDetail(Integer reviewId, Integer currentUserId);
+
+    /**
+     * 点赞评价
+     *
+     * @param reviewId 评价ID
+     * @param userId 用户ID
+     * @return R<Boolean>
+     */
+    R<Boolean> likeReview(Integer reviewId, Integer userId);
+
+    /**
+     * 取消点赞评价
+     *
+     * @param reviewId 评价ID
+     * @param userId 用户ID
+     * @return R<Boolean>
+     */
+    R<Boolean> cancelLikeReview(Integer reviewId, Integer userId);
+
+    /**
+     * 分页查询评价列表
+     *
+     * @param pageNum 页码
+     * @param pageSize 页大小
+     * @param staffUserId 员工用户ID(可选)
+     * @param userId 评价用户ID(可选)
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<IPage<StoreStaffReviewVo>>
+     */
+    R<IPage<StoreStaffReviewVo>> getReviewList(int pageNum, int pageSize, Integer staffUserId, Integer userId, Integer currentUserId);
+
+    /**
+     * 用户删除评价(只能删除自己的评价,删除评价时,会级联删除该评价下的所有评论和回复)
+     *
+     * @param reviewId 评价ID
+     * @param userId 用户ID(必填,只能删除自己的评价)
+     * @return R<Boolean>
+     */
+    R<Boolean> deleteReview(Integer reviewId, Integer userId);
+
+    /**
+     * 管理员删除评价(可以删除任何评价,删除评价时,会级联删除该评价下的所有评论和回复)
+     *
+     * @param reviewId 评价ID
+     * @return R<Boolean>
+     */
+    R<Boolean> deleteReviewByAdmin(Integer reviewId);
+
+    /**
+     * 根据人员ID查询评价列表(包含人员信息)
+     *
+     * @param pageNum 页码
+     * @param pageSize 页大小
+     * @param staffUserId 员工用户ID(必填)
+     * @param currentUserId 当前用户ID(用于判断是否已点赞,可为null)
+     * @return R<StoreStaffReviewListWithStaffVo>
+     */
+    R<StoreStaffReviewListWithStaffVo> getReviewListByStaffId(int pageNum, int pageSize, Integer staffUserId, Integer currentUserId);
+}
+

+ 73 - 0
alien-store/src/main/java/shop/alien/store/service/StoreStaffTitleService.java

@@ -0,0 +1,73 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.StoreStaffTitle;
+
+/**
+ * 员工标题服务接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreStaffTitleService extends IService<StoreStaffTitle> {
+
+    /**
+     * 分页查询员工标题列表
+     * <p>
+     * 根据店铺ID查询员工标题列表,支持分页
+     * 只返回未删除的记录
+     * </p>
+     *
+     * @param page    分页页数,必须大于0
+     * @param size    分页条数,必须大于0且不超过100
+     * @param storeId 店铺ID,可选
+     * @return 员工标题列表分页结果
+     */
+    IPage<StoreStaffTitle> queryStaffTitleList(Integer page, Integer size, Integer storeId);
+
+    /**
+     * 新增员工标题
+     * <p>
+     * 新增员工标题,自动计算员工数量
+     * </p>
+     *
+     * @param storeStaffTitle 员工标题信息
+     * @return 新增结果,成功返回1,失败返回0
+     */
+    Integer addStaffTitle(StoreStaffTitle storeStaffTitle);
+
+    /**
+     * 更新员工标题
+     * <p>
+     * 更新员工标题信息,自动重新计算员工数量
+     * </p>
+     *
+     * @param storeStaffTitle 员工标题信息
+     * @return 更新结果,成功返回1,失败返回0
+     */
+    Integer updateStaffTitle(StoreStaffTitle storeStaffTitle);
+
+    /**
+     * 删除员工标题(逻辑删除)
+     * <p>
+     * 根据ID逻辑删除员工标题
+     * </p>
+     *
+     * @param id 员工标题ID,必须大于0
+     * @return 删除结果,成功返回1,失败返回0
+     */
+    Integer deleteStaffTitle(Integer id);
+
+    /**
+     * 查询员工标题详情
+     * <p>
+     * 根据门店ID查询员工标题详细信息(一个门店只能有一个标题)
+     * </p>
+     *
+     * @param storeId 门店ID,必须大于0
+     * @return 员工标题详情,如果不存在则返回null
+     */
+    StoreStaffTitle getStaffTitleDetail(Integer storeId);
+}
+

+ 33 - 0
alien-store/src/main/java/shop/alien/store/service/StoreVideoService.java

@@ -0,0 +1,33 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.StoreVideo;
+
+import java.util.List;
+
+/**
+ * 门店视频 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreVideoService extends IService<StoreVideo> {
+
+    /**
+     * 根据门店ID获取视频列表
+     *
+     * @param storeId 门店id
+     * @return 视频列表
+     */
+    List<StoreVideo> getByStoreId(Integer storeId);
+
+    /**
+     * 根据门店ID和业务ID获取视频列表
+     *
+     * @param storeId 门店id
+     * @param businessId 业务ID
+     * @return 视频列表
+     */
+    List<StoreVideo> getByStoreIdAndBusinessId(Integer storeId, Integer businessId);
+}
+

+ 906 - 0
alien-store/src/main/java/shop/alien/store/service/impl/PerformanceListServiceImpl.java

@@ -0,0 +1,906 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.BarPerformance;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.store.StoreStaffConfig;
+import shop.alien.entity.store.vo.PerformanceDetailVo;
+import shop.alien.entity.store.vo.PerformanceGroupByDateVo;
+import shop.alien.entity.store.vo.PerformanceGroupListVo;
+import shop.alien.entity.store.vo.PerformanceGuestVo;
+import shop.alien.entity.store.vo.PerformanceListVo;
+import shop.alien.mapper.BarPerformanceMapper;
+import shop.alien.mapper.StoreDictionaryMapper;
+import shop.alien.mapper.StoreStaffConfigMapper;
+import shop.alien.store.service.PerformanceListService;
+import shop.alien.store.util.CommonConstant;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 演出列表服务实现类
+ * 实现用户端演出列表查询功能
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class PerformanceListServiceImpl implements PerformanceListService {
+
+    private final BarPerformanceMapper barPerformanceMapper;
+    private final StoreStaffConfigMapper storeStaffConfigMapper;
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    /**
+     * 日期格式化:yyyy.MM.dd
+     */
+    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy.MM.dd");
+
+    /**
+     * 日期格式化:MM-dd(用于日期标题)
+     */
+    private static final SimpleDateFormat DATE_TITLE_FORMAT = new SimpleDateFormat("MM-dd");
+
+    /**
+     * 日期格式化:yyyy-MM-dd(用于日期字符串)
+     */
+    private static final SimpleDateFormat DATE_STRING_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
+
+    /**
+     * 时间格式化:HH:mm
+     */
+    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm");
+
+    /**
+     * LocalTime格式化:HH:mm
+     */
+    private static final DateTimeFormatter LOCAL_TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm");
+
+    /**
+     * 星期几中文数组
+     * Calendar中:1=周日,2=周一,...,7=周六
+     */
+    private static final String[] WEEK_DAYS = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
+
+    @Override
+    public IPage<PerformanceListVo> queryPerformanceList(Integer page, Integer size, Integer storeId) {
+        log.info("查询演出列表,参数:page={}, size={}, storeId={}", page, size, storeId);
+
+        // 参数校验
+        validateQueryParams(page, size, storeId);
+
+        // 构建分页对象
+        IPage<BarPerformance> performancePage = new Page<>(page, size);
+
+        // 构建查询条件
+        LambdaQueryWrapper<BarPerformance> queryWrapper = buildQueryWrapper(storeId);
+
+        // 执行查询
+        IPage<BarPerformance> result = barPerformanceMapper.selectPage(performancePage, queryWrapper);
+
+        // 转换为VO列表
+        List<PerformanceListVo> voList = convertToVoList(result.getRecords());
+
+        // 构建返回结果
+        IPage<PerformanceListVo> voPage = new Page<>(page, size, result.getTotal());
+        voPage.setRecords(voList);
+
+        log.info("查询演出列表成功,店铺ID={},共{}条记录,当前页{}条", 
+                storeId, result.getTotal(), voList.size());
+        return voPage;
+    }
+
+    /**
+     * 校验查询参数
+     *
+     * @param page    分页页数
+     * @param size    分页条数
+     * @param storeId 店铺ID
+     * @throws IllegalArgumentException 当参数无效时抛出
+     */
+    private void validateQueryParams(Integer page, Integer size, Integer storeId) {
+        if (page == null || page < 1) {
+            throw new IllegalArgumentException("分页页数不能为空且必须大于0");
+        }
+        if (size == null || size < 1) {
+            throw new IllegalArgumentException("分页条数不能为空且必须大于0");
+        }
+        if (size > CommonConstant.MAX_PAGE_SIZE) {
+            throw new IllegalArgumentException("分页条数不能超过" + CommonConstant.MAX_PAGE_SIZE);
+        }
+        if (storeId == null || storeId <= 0) {
+            throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
+        }
+    }
+
+    /**
+     * 构建查询条件
+     *
+     * @param storeId 店铺ID
+     * @return 查询条件包装器
+     */
+    private LambdaQueryWrapper<BarPerformance> buildQueryWrapper(Integer storeId) {
+        LambdaQueryWrapper<BarPerformance> queryWrapper = new LambdaQueryWrapper<>();
+
+        // 必须条件:店铺ID、未删除、审核通过、已上线
+        queryWrapper.eq(BarPerformance::getStoreId, storeId)
+                .eq(BarPerformance::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE)
+                .eq(BarPerformance::getReviewStatus, CommonConstant.PERFORMANCE_REVIEW_STATUS_APPROVED)
+                .eq(BarPerformance::getOnlineStatus, CommonConstant.PERFORMANCE_ONLINE_STATUS_ONLINE);
+
+        // 排序规则:按创建时间降序
+        queryWrapper.orderByDesc(BarPerformance::getCreatedTime);
+
+        return queryWrapper;
+    }
+
+    /**
+     * 转换为VO列表
+     *
+     * @param performances 演出列表
+     * @return VO列表
+     */
+    private List<PerformanceListVo> convertToVoList(List<BarPerformance> performances) {
+        if (performances == null || performances.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        return performances.stream()
+                .map(this::convertToVo)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 转换为VO
+     *
+     * @param performance 演出信息
+     * @return VO对象
+     */
+    private PerformanceListVo convertToVo(BarPerformance performance) {
+        PerformanceListVo vo = new PerformanceListVo();
+        vo.setId(performance.getId());
+        vo.setPerformanceName(performance.getPerformanceName());
+        vo.setPerformancePoster(performance.getPerformancePoster());
+        vo.setPerformanceType(performance.getPerformanceType());
+
+        // 设置演出者信息
+        vo.setPerformersInfo(buildPerformersInfo(performance.getStaffConfigIds()));
+
+        // 设置日期范围
+        vo.setDateRange(buildDateRange(performance));
+
+        // 设置演出时间安排
+        vo.setScheduleInfo(buildScheduleInfo(performance));
+
+        return vo;
+    }
+
+    /**
+     * 构建演出者信息
+     * <p>
+     * 从员工ID列表中查询第一个有名字的员工,显示格式:
+     * - 1人:显示员工名字
+     * - 多人:显示"xx等X人"
+     * </p>
+     *
+     * @param staffConfigIds 员工配置ID字符串(逗号分隔)
+     * @return 演出者信息(如:刘能等7人)
+     */
+    private String buildPerformersInfo(String staffConfigIds) {
+        if (StringUtils.isEmpty(staffConfigIds)) {
+            return "";
+        }
+
+        try {
+            String[] ids = staffConfigIds.split(",");
+            List<Integer> staffIdList = new ArrayList<>();
+            
+            // 解析所有有效的员工ID
+            for (String idStr : ids) {
+                if (StringUtils.isNotEmpty(idStr.trim())) {
+                    try {
+                        Integer staffId = Integer.parseInt(idStr.trim());
+                        if (staffId > 0) {
+                            staffIdList.add(staffId);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("解析员工配置ID失败,无效的ID:{}", idStr);
+                    }
+                }
+            }
+
+            if (staffIdList.isEmpty()) {
+                return "";
+            }
+
+            int count = staffIdList.size();
+            String firstName = null;
+
+            // 遍历员工ID列表,找到第一个有名字的员工
+            for (Integer staffId : staffIdList) {
+                try {
+                    StoreStaffConfig staff = storeStaffConfigMapper.selectById(staffId);
+                    if (staff != null && StringUtils.isNotEmpty(staff.getName())) {
+                        firstName = staff.getName();
+                        break; // 找到第一个有名字的员工就退出
+                    }
+                } catch (Exception e) {
+                    log.warn("查询员工信息失败,staffId={},异常信息:{}", staffId, e.getMessage());
+                    // 继续查询下一个员工
+                }
+            }
+
+            // 根据人数和是否找到名字来构建返回信息
+            if (count == 1) {
+                return firstName != null ? firstName : "1人";
+            } else {
+                return firstName != null ? firstName + "等" + count + "人" : count + "人";
+            }
+        } catch (Exception e) {
+            log.error("构建演出者信息异常,staffConfigIds={},异常信息:{}", staffConfigIds, e.getMessage(), e);
+            return "";
+        }
+    }
+
+    /**
+     * 构建日期范围
+     *
+     * @param performance 演出信息
+     * @return 日期范围(格式:2025.11.11-2027.01.31)
+     */
+    private String buildDateRange(BarPerformance performance) {
+        String frequency = performance.getPerformanceFrequency();
+        if (StringUtils.isEmpty(frequency)) {
+            return "";
+        }
+
+        try {
+            switch (frequency) {
+                case CommonConstant.PERFORMANCE_FREQUENCY_SINGLE:
+                    // 单次演出:使用singleStartDatetime和singleEndDatetime
+                    Date singleStart = performance.getSingleStartDatetime();
+                    Date singleEnd = performance.getSingleEndDatetime();
+                    if (singleStart != null && singleEnd != null) {
+                        return DATE_FORMAT.format(singleStart) + "-" + DATE_FORMAT.format(singleEnd);
+                    }
+                    break;
+                case CommonConstant.PERFORMANCE_FREQUENCY_DAILY:
+                case CommonConstant.PERFORMANCE_FREQUENCY_WEEKLY:
+                    // 每天定时或每周定时:使用dailyStartDate和dailyEndDate
+                    Date dailyStart = performance.getDailyStartDate();
+                    Date dailyEnd = performance.getDailyEndDate();
+                    if (dailyStart != null && dailyEnd != null) {
+                        return DATE_FORMAT.format(dailyStart) + "-" + DATE_FORMAT.format(dailyEnd);
+                    }
+                    break;
+                default:
+                    break;
+            }
+        } catch (Exception e) {
+            log.error("构建日期范围异常,performanceId={},异常信息:{}", performance.getId(), e.getMessage(), e);
+        }
+
+        return "";
+    }
+
+    /**
+     * 构建演出时间安排
+     *
+     * @param performance 演出信息
+     * @return 演出时间安排(格式:每周一、二、三、四19:30-次日00:00)
+     */
+    private String buildScheduleInfo(BarPerformance performance) {
+        String frequency = performance.getPerformanceFrequency();
+        if (StringUtils.isEmpty(frequency)) {
+            return "";
+        }
+
+        try {
+            switch (frequency) {
+                case CommonConstant.PERFORMANCE_FREQUENCY_SINGLE:
+                    // 单次演出:显示具体日期和时间
+                    Date singleStart = performance.getSingleStartDatetime();
+                    Date singleEnd = performance.getSingleEndDatetime();
+                    if (singleStart != null && singleEnd != null) {
+                        return buildSingleScheduleInfo(singleStart, singleEnd);
+                    }
+                    break;
+                case CommonConstant.PERFORMANCE_FREQUENCY_DAILY:
+                    // 每天定时:使用dailyStartTime和dailyEndTime
+                    LocalTime dailyStartTime = performance.getDailyStartTime();
+                    LocalTime dailyEndTime = performance.getDailyEndTime();
+                    if (dailyStartTime != null && dailyEndTime != null) {
+                        return "每天" + buildLocalTimeRange(dailyStartTime, dailyEndTime);
+                    }
+                    break;
+                case CommonConstant.PERFORMANCE_FREQUENCY_WEEKLY:
+                    // 每周定时:显示星期几和时间,使用dailyStartTime和dailyEndTime
+                    String performanceWeek = performance.getPerformanceWeek();
+                    LocalTime weeklyStartTime = performance.getDailyStartTime();
+                    LocalTime weeklyEndTime = performance.getDailyEndTime();
+                    if (StringUtils.isNotEmpty(performanceWeek) && weeklyStartTime != null && weeklyEndTime != null) {
+                        return buildWeeklyScheduleInfo(performanceWeek, weeklyStartTime, weeklyEndTime);
+                    }
+                    break;
+                default:
+                    break;
+            }
+        } catch (Exception e) {
+            log.error("构建演出时间安排异常,performanceId={},异常信息:{}", performance.getId(), e.getMessage(), e);
+        }
+
+        return "";
+    }
+
+    /**
+     * 构建单次演出时间安排
+     *
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     * @return 时间安排字符串
+     */
+    private String buildSingleScheduleInfo(Date startTime, Date endTime) {
+        java.util.Calendar cal = java.util.Calendar.getInstance();
+        cal.setTime(startTime);
+        int dayOfWeek = cal.get(java.util.Calendar.DAY_OF_WEEK);
+        String weekDay = WEEK_DAYS[dayOfWeek - 1];
+
+        return weekDay + " " + buildTimeRange(startTime, endTime);
+    }
+
+    /**
+     * 构建每周定时演出时间安排(使用LocalTime)
+     *
+     * @param performanceWeek 星期几字符串(0-周一,1-周二,...,6-周日,逗号分隔)
+     * @param startTime        开始时间(LocalTime)
+     * @param endTime          结束时间(LocalTime)
+     * @return 时间安排字符串
+     */
+    private String buildWeeklyScheduleInfo(String performanceWeek, LocalTime startTime, LocalTime endTime) {
+        String[] weekDays = performanceWeek.split(",");
+        List<String> weekDayList = new ArrayList<>();
+
+        for (String weekDay : weekDays) {
+            if (StringUtils.isNotEmpty(weekDay.trim())) {
+                try {
+                    int dayIndex = Integer.parseInt(weekDay.trim());
+                    if (dayIndex >= 0 && dayIndex < WEEK_DAYS.length) {
+                        weekDayList.add(WEEK_DAYS[dayIndex]);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("解析星期几失败,无效的值:{}", weekDay);
+                }
+            }
+        }
+
+        if (weekDayList.isEmpty()) {
+            return "";
+        }
+
+        String weekDayStr = String.join("、", weekDayList);
+        String timeRange = buildLocalTimeRange(startTime, endTime);
+
+        return "每周" + weekDayStr + timeRange;
+    }
+
+    /**
+     * 构建时间范围(使用Date)
+     *
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     * @return 时间范围字符串(格式:19:30-次日00:00)
+     */
+    private String buildTimeRange(Date startTime, Date endTime) {
+        String startTimeStr = TIME_FORMAT.format(startTime);
+
+        // 判断是否跨天
+        java.util.Calendar startCal = java.util.Calendar.getInstance();
+        startCal.setTime(startTime);
+        java.util.Calendar endCal = java.util.Calendar.getInstance();
+        endCal.setTime(endTime);
+
+        boolean isNextDay = endCal.get(java.util.Calendar.DAY_OF_YEAR) > startCal.get(java.util.Calendar.DAY_OF_YEAR) ||
+                endCal.get(java.util.Calendar.YEAR) > startCal.get(java.util.Calendar.YEAR);
+
+        String endTimeStr;
+        if (isNextDay) {
+            endTimeStr = "次日" + TIME_FORMAT.format(endTime);
+        } else {
+            endTimeStr = TIME_FORMAT.format(endTime);
+        }
+
+        return startTimeStr + "-" + endTimeStr;
+    }
+
+    /**
+     * 构建时间范围(使用LocalTime)
+     * <p>
+     * 用于每天定时和每周定时演出,判断是否跨天(结束时间小于或等于开始时间表示跨天)
+     * </p>
+     *
+     * @param startTime 开始时间(LocalTime)
+     * @param endTime   结束时间(LocalTime)
+     * @return 时间范围字符串(格式:19:30-次日00:00)
+     */
+    private String buildLocalTimeRange(LocalTime startTime, LocalTime endTime) {
+        String startTimeStr = startTime.format(LOCAL_TIME_FORMAT);
+
+        // 判断是否跨天:结束时间小于或等于开始时间表示跨天
+        boolean isNextDay = !endTime.isAfter(startTime);
+
+        String endTimeStr;
+        if (isNextDay) {
+            endTimeStr = "次日" + endTime.format(LOCAL_TIME_FORMAT);
+        } else {
+            endTimeStr = endTime.format(LOCAL_TIME_FORMAT);
+        }
+
+        return startTimeStr + "-" + endTimeStr;
+    }
+
+    /**
+     * 查询按日期分组的演出列表(用户端)
+     * <p>
+     * 根据店铺ID查询演出列表,按日期分组返回
+     * 只返回审核通过、已上线、未删除的演出
+     * 返回未来30天内有演出的日期及其对应的演出列表
+     * </p>
+     *
+     * @param storeId 店铺ID,必须大于0
+     * @return 按日期分组的演出列表
+     */
+    @Override
+    public PerformanceGroupListVo queryPerformanceListGroupByDate(Integer storeId) {
+        log.info("查询按日期分组的演出列表,参数:storeId={}", storeId);
+
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
+        }
+
+        try {
+            // 查询所有演出
+            List<BarPerformance> performances = queryAllPerformances(storeId);
+
+            if (performances == null || performances.isEmpty()) {
+                PerformanceGroupListVo result = new PerformanceGroupListVo();
+                result.setGroupList(new ArrayList<>());
+                result.setTotal(0L);
+                return result;
+            }
+
+            // 转换为VO列表
+            List<PerformanceListVo> voList = convertToVoList(performances);
+
+            // 生成演出日期映射(日期 -> 演出列表)
+            Map<String, List<PerformanceListVo>> datePerformanceMap = generateDatePerformanceMap(performances, voList);
+
+            // 构建按日期分组的列表
+            List<PerformanceGroupByDateVo> groupList = buildGroupList(datePerformanceMap);
+
+            // 构建返回结果
+            PerformanceGroupListVo result = new PerformanceGroupListVo();
+            result.setGroupList(groupList);
+            result.setTotal((long) voList.size());
+
+            log.info("查询按日期分组的演出列表成功,店铺ID={},共{}个日期组,{}条演出记录",
+                    storeId, groupList.size(), voList.size());
+            return result;
+        } catch (Exception e) {
+            log.error("查询按日期分组的演出列表异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
+            throw new RuntimeException("查询按日期分组的演出列表失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 查询所有演出
+     *
+     * @param storeId 店铺ID
+     * @return 演出列表
+     */
+    private List<BarPerformance> queryAllPerformances(Integer storeId) {
+        LambdaQueryWrapper<BarPerformance> queryWrapper = buildQueryWrapper(storeId);
+        // 不限制数量,查询所有符合条件的演出
+        return barPerformanceMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * 生成演出日期映射
+     * <p>
+     * 根据演出类型生成未来30天内的演出日期,并建立日期与演出的映射关系
+     * </p>
+     *
+     * @param performances 演出列表
+     * @param voList       VO列表
+     * @return 日期与演出列表的映射
+     */
+    private Map<String, List<PerformanceListVo>> generateDatePerformanceMap(
+            List<BarPerformance> performances, List<PerformanceListVo> voList) {
+        Map<String, List<PerformanceListVo>> datePerformanceMap = new LinkedHashMap<>();
+
+        // 获取今天和未来30天的日期范围
+        Date today = new Date();
+        java.util.Calendar cal = java.util.Calendar.getInstance();
+        cal.setTime(today);
+        cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
+        cal.set(java.util.Calendar.MINUTE, 0);
+        cal.set(java.util.Calendar.SECOND, 0);
+        cal.set(java.util.Calendar.MILLISECOND, 0);
+        Date todayStart = cal.getTime();
+
+        cal.add(java.util.Calendar.DAY_OF_MONTH, 30);
+        Date futureEnd = cal.getTime();
+
+        // 遍历演出,生成日期映射
+        for (int i = 0; i < performances.size(); i++) {
+            BarPerformance performance = performances.get(i);
+            PerformanceListVo vo = voList.get(i);
+            String frequency = performance.getPerformanceFrequency();
+
+            if (StringUtils.isEmpty(frequency)) {
+                continue;
+            }
+
+            Set<String> performanceDates = new HashSet<>();
+
+            switch (frequency) {
+                case CommonConstant.PERFORMANCE_FREQUENCY_SINGLE:
+                    // 单次演出:使用singleStartDatetime
+                    Date singleStart = performance.getSingleStartDatetime();
+                    if (singleStart != null && !singleStart.before(todayStart) && !singleStart.after(futureEnd)) {
+                        String dateStr = DATE_STRING_FORMAT.format(singleStart);
+                        performanceDates.add(dateStr);
+                    }
+                    break;
+                case CommonConstant.PERFORMANCE_FREQUENCY_DAILY:
+                    // 每天定时:从dailyStartDate到dailyEndDate,每天生成一条记录
+                    Date dailyStart = performance.getDailyStartDate();
+                    Date dailyEnd = performance.getDailyEndDate();
+                    if (dailyStart != null && dailyEnd != null) {
+                        Date queryStart = dailyStart.before(todayStart) ? todayStart : dailyStart;
+                        Date queryEnd = dailyEnd.after(futureEnd) ? futureEnd : dailyEnd;
+                        if (!queryStart.after(queryEnd)) {
+                            java.util.Calendar dateCal = java.util.Calendar.getInstance();
+                            dateCal.setTime(queryStart);
+                            while (!dateCal.getTime().after(queryEnd)) {
+                                String dateStr = DATE_STRING_FORMAT.format(dateCal.getTime());
+                                performanceDates.add(dateStr);
+                                dateCal.add(java.util.Calendar.DAY_OF_MONTH, 1);
+                            }
+                        }
+                    }
+                    break;
+                case CommonConstant.PERFORMANCE_FREQUENCY_WEEKLY:
+                    // 每周定时:从dailyStartDate到dailyEndDate,只生成符合performanceWeek的日期
+                    Date weeklyStart = performance.getDailyStartDate();
+                    Date weeklyEnd = performance.getDailyEndDate();
+                    String performanceWeek = performance.getPerformanceWeek();
+                    if (weeklyStart != null && weeklyEnd != null && StringUtils.isNotEmpty(performanceWeek)) {
+                        Date queryStart = weeklyStart.before(todayStart) ? todayStart : weeklyStart;
+                        Date queryEnd = weeklyEnd.after(futureEnd) ? futureEnd : weeklyEnd;
+                        if (!queryStart.after(queryEnd)) {
+                            // 解析星期几列表
+                            Set<String> weekDaySet = new HashSet<>();
+                            String[] weekDays = performanceWeek.split(",");
+                            for (String weekDay : weekDays) {
+                                if (StringUtils.isNotEmpty(weekDay.trim())) {
+                                    weekDaySet.add(weekDay.trim());
+                                }
+                            }
+
+                            java.util.Calendar dateCal = java.util.Calendar.getInstance();
+                            dateCal.setTime(queryStart);
+                            while (!dateCal.getTime().after(queryEnd)) {
+                                // 获取当前日期是星期几(0-周一,1-周二,...,6-周日)
+                                int dayOfWeek = dateCal.get(java.util.Calendar.DAY_OF_WEEK);
+                                int todayWeekDay = (dayOfWeek == 1) ? 6 : (dayOfWeek - 2);
+                                String todayWeekDayStr = String.valueOf(todayWeekDay);
+
+                                if (weekDaySet.contains(todayWeekDayStr)) {
+                                    String dateStr = DATE_STRING_FORMAT.format(dateCal.getTime());
+                                    performanceDates.add(dateStr);
+                                }
+                                dateCal.add(java.util.Calendar.DAY_OF_MONTH, 1);
+                            }
+                        }
+                    }
+                    break;
+                default:
+                    break;
+            }
+
+            // 将演出添加到对应日期的列表中
+            for (String dateStr : performanceDates) {
+                datePerformanceMap.computeIfAbsent(dateStr, k -> new ArrayList<>()).add(vo);
+            }
+        }
+
+        return datePerformanceMap;
+    }
+
+    /**
+     * 构建按日期分组的列表
+     *
+     * @param datePerformanceMap 日期与演出列表的映射
+     * @return 按日期分组的列表
+     */
+    private List<PerformanceGroupByDateVo> buildGroupList(Map<String, List<PerformanceListVo>> datePerformanceMap) {
+        List<PerformanceGroupByDateVo> groupList = new ArrayList<>();
+
+        // 获取今天的日期字符串
+        String todayStr = DATE_STRING_FORMAT.format(new Date());
+
+        // 按日期排序
+        List<String> sortedDates = datePerformanceMap.keySet().stream()
+                .sorted()
+                .collect(Collectors.toList());
+
+        // 构建分组列表
+        for (String dateStr : sortedDates) {
+            PerformanceGroupByDateVo group = new PerformanceGroupByDateVo();
+            group.setDate(dateStr);
+
+            // 判断是否为今天
+            boolean isToday = todayStr.equals(dateStr);
+            group.setIsToday(isToday);
+
+            // 构建日期标题
+            try {
+                Date date = DATE_STRING_FORMAT.parse(dateStr);
+                String dateTitle = DATE_TITLE_FORMAT.format(date);
+
+                if (isToday) {
+                    group.setDateTitle(dateTitle + " 今天");
+                } else {
+                    // 获取星期几
+                    java.util.Calendar cal = java.util.Calendar.getInstance();
+                    cal.setTime(date);
+                    int dayOfWeek = cal.get(java.util.Calendar.DAY_OF_WEEK);
+                    String weekDay = WEEK_DAYS[dayOfWeek - 1];
+                    group.setDateTitle(dateTitle + " " + weekDay);
+                }
+            } catch (Exception e) {
+                log.warn("解析日期失败,dateStr={},异常信息:{}", dateStr, e.getMessage());
+                group.setDateTitle(dateStr);
+            }
+
+            group.setPerformanceList(datePerformanceMap.get(dateStr));
+            groupList.add(group);
+        }
+
+        return groupList;
+    }
+
+    /**
+     * 查询演出详情(用户端)
+     * <p>
+     * 根据演出ID查询演出详细信息
+     * 包含演出基本信息、演出时间、演出风格、演出详情描述和演出嘉宾列表
+     * </p>
+     *
+     * @param id 演出ID,必须大于0
+     * @return 演出详情,如果演出不存在则返回null
+     */
+    @Override
+    public PerformanceDetailVo queryPerformanceDetail(Integer id) {
+        log.info("查询演出详情,参数:id={}", id);
+
+        // 参数校验
+        if (id == null || id <= 0) {
+            log.warn("查询演出详情失败,演出ID无效:id={}", id);
+            throw new IllegalArgumentException("演出ID不能为空且必须大于0");
+        }
+
+        try {
+            // 查询演出基本信息
+            BarPerformance performance = barPerformanceMapper.selectById(id);
+            if (performance == null) {
+                log.warn("查询演出详情失败,演出不存在:id={}", id);
+                return null;
+            }
+
+            // 校验演出状态:只返回审核通过、已上线、未删除的演出
+            if (!CommonConstant.DELETE_FLAG_UNDELETE.equals(performance.getDeleteFlag()) ||
+                    !CommonConstant.PERFORMANCE_REVIEW_STATUS_APPROVED.equals(performance.getReviewStatus()) ||
+                    !CommonConstant.PERFORMANCE_ONLINE_STATUS_ONLINE.equals(performance.getOnlineStatus())) {
+                log.warn("查询演出详情失败,演出状态不符合要求:id={},deleteFlag={},reviewStatus={},onlineStatus={}",
+                        id, performance.getDeleteFlag(), performance.getReviewStatus(), performance.getOnlineStatus());
+                return null;
+            }
+
+            // 构建演出详情VO
+            PerformanceDetailVo vo = new PerformanceDetailVo();
+            vo.setId(performance.getId());
+            vo.setPerformanceName(performance.getPerformanceName());
+            vo.setPerformancePoster(performance.getPerformancePoster());
+            vo.setPerformanceType(performance.getPerformanceType());
+            vo.setPerformanceContent(performance.getPerformanceContent());
+
+            // 构建演出时间
+            vo.setPerformanceTime(buildPerformanceTime(performance));
+
+            // 构建演出风格
+            vo.setPerformanceStyle(buildPerformanceStyle(performance.getPerformanceStyle()));
+
+            // 查询演出嘉宾列表
+            List<PerformanceGuestVo> guestList = queryGuestList(performance.getStaffConfigIds());
+            vo.setGuestList(guestList);
+
+            // 设置演出人员数量
+            vo.setGuestCount(guestList != null ? guestList.size() : 0);
+
+            log.info("查询演出详情成功,id={},嘉宾数量:{}", id,
+                    vo.getGuestCount());
+            return vo;
+        } catch (Exception e) {
+            log.error("查询演出详情异常,id={},异常信息:{}", id, e.getMessage(), e);
+            throw new RuntimeException("查询演出详情失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 构建演出时间字符串
+     * <p>
+     * 格式:2025.11.11-2027.01.31每周一、四19:30-次日00:00
+     * </p>
+     *
+     * @param performance 演出信息
+     * @return 演出时间字符串
+     */
+    private String buildPerformanceTime(BarPerformance performance) {
+        String frequency = performance.getPerformanceFrequency();
+        if (StringUtils.isEmpty(frequency)) {
+            return "";
+        }
+
+        try {
+            StringBuilder timeStr = new StringBuilder();
+
+            // 构建日期范围
+            String dateRange = buildDateRange(performance);
+            if (StringUtils.isNotEmpty(dateRange)) {
+                timeStr.append(dateRange);
+            }
+
+            // 构建时间安排
+            String scheduleInfo = buildScheduleInfo(performance);
+            if (StringUtils.isNotEmpty(scheduleInfo)) {
+                if (timeStr.length() > 0) {
+                    timeStr.append(scheduleInfo);
+                } else {
+                    timeStr.append(scheduleInfo);
+                }
+            }
+
+            return timeStr.toString();
+        } catch (Exception e) {
+            log.error("构建演出时间字符串异常,performanceId={},异常信息:{}", performance.getId(), e.getMessage(), e);
+            return "";
+        }
+    }
+
+    /**
+     * 构建演出风格字符串
+     * <p>
+     * 根据performance_style字段(dict_id逗号分隔)查询字典表,转换为名称
+     * 格式:流行、民谣
+     * </p>
+     *
+     * @param performanceStyle 演出风格dict_id字符串(逗号分隔)
+     * @return 演出风格名称字符串(逗号分隔)
+     */
+    private String buildPerformanceStyle(String performanceStyle) {
+        if (StringUtils.isEmpty(performanceStyle)) {
+            return "";
+        }
+
+        try {
+            String[] styleIds = performanceStyle.split(",");
+            List<String> styleNames = new ArrayList<>();
+
+            for (String styleId : styleIds) {
+                if (StringUtils.isNotEmpty(styleId.trim())) {
+                    try {
+                        // 查询字典表,typeName为proficient_tag
+                        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+                        queryWrapper.eq(StoreDictionary::getTypeName, "proficient_tag")
+                                .eq(StoreDictionary::getDictId, styleId.trim())
+                                .eq(StoreDictionary::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE)
+                                .last("limit 1");
+                        StoreDictionary dictionary = storeDictionaryMapper.selectOne(queryWrapper);
+                        if (dictionary != null && StringUtils.isNotEmpty(dictionary.getDictDetail())) {
+                            styleNames.add(dictionary.getDictDetail());
+                        }
+                    } catch (Exception e) {
+                        log.warn("查询演出风格字典失败,styleId={},异常信息:{}", styleId, e.getMessage());
+                    }
+                }
+            }
+
+            return String.join("、", styleNames);
+        } catch (Exception e) {
+            log.error("构建演出风格字符串异常,performanceStyle={},异常信息:{}", performanceStyle, e.getMessage(), e);
+            return "";
+        }
+    }
+
+    /**
+     * 查询演出嘉宾列表
+     *
+     * @param staffConfigIds 员工配置ID字符串(逗号分隔)
+     * @return 演出嘉宾列表
+     */
+    private List<PerformanceGuestVo> queryGuestList(String staffConfigIds) {
+        List<PerformanceGuestVo> guestList = new ArrayList<>();
+
+        if (StringUtils.isEmpty(staffConfigIds)) {
+            return guestList;
+        }
+
+        try {
+            String[] ids = staffConfigIds.split(",");
+            List<Integer> staffIdList = new ArrayList<>();
+
+            // 解析所有有效的员工ID
+            for (String idStr : ids) {
+                if (StringUtils.isNotEmpty(idStr.trim())) {
+                    try {
+                        Integer staffId = Integer.parseInt(idStr.trim());
+                        if (staffId > 0) {
+                            staffIdList.add(staffId);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("解析员工配置ID失败,无效的ID:{}", idStr);
+                    }
+                }
+            }
+
+            if (staffIdList.isEmpty()) {
+                return guestList;
+            }
+
+            // 批量查询员工信息
+            List<StoreStaffConfig> staffList = storeStaffConfigMapper.selectBatchIds(staffIdList);
+
+            // 转换为VO列表
+            for (StoreStaffConfig staff : staffList) {
+                if (staff == null || !CommonConstant.DELETE_FLAG_UNDELETE.equals(staff.getDeleteFlag())) {
+                    continue;
+                }
+
+                PerformanceGuestVo guest = new PerformanceGuestVo();
+                guest.setStaffId(staff.getId());
+                guest.setName(staff.getName());
+                guest.setStaffImage(staff.getStaffImage());
+                guest.setStaffPosition(staff.getStaffPosition());
+                guest.setProficientProjects(staff.getProficientProjects());
+                guest.setLikeCount(staff.getLikeCount() != null ? staff.getLikeCount() : 0);
+                guestList.add(guest);
+            }
+
+            return guestList;
+        } catch (Exception e) {
+            log.error("查询演出嘉宾列表异常,staffConfigIds={},异常信息:{}", staffConfigIds, e.getMessage(), e);
+            return guestList;
+        }
+    }
+}
+

+ 55 - 74
alien-store/src/main/java/shop/alien/store/service/impl/SportsEquipmentFacilityServiceImpl.java

@@ -13,6 +13,7 @@ import org.springframework.util.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import shop.alien.entity.store.FitnessEquipmentInfo;
 import shop.alien.entity.store.SportsEquipmentFacility;
+import shop.alien.entity.store.SportsFacilityArea;
 import shop.alien.entity.store.StoreImg;
 import shop.alien.entity.store.vo.FitnessEquipmentCategoryDetailVo;
 import shop.alien.entity.store.vo.FitnessEquipmentTypeSummaryVo;
@@ -20,6 +21,7 @@ import shop.alien.entity.store.vo.SportsEquipmentFacilityCategorySummaryVo;
 import shop.alien.entity.store.vo.SportsEquipmentFacilityCategoryVo;
 import shop.alien.entity.store.vo.SportsEquipmentFacilityVo;
 import shop.alien.mapper.SportsEquipmentFacilityMapper;
+import shop.alien.mapper.SportsFacilityAreaMapper;
 import shop.alien.mapper.StoreImgMapper;
 import shop.alien.store.service.FitnessEquipmentInfoService;
 import shop.alien.store.service.SportsEquipmentFacilityService;
@@ -42,6 +44,7 @@ public class SportsEquipmentFacilityServiceImpl extends ServiceImpl<SportsEquipm
         implements SportsEquipmentFacilityService {
 
     private final SportsEquipmentFacilityMapper facilityMapper;
+    private final SportsFacilityAreaMapper sportsFacilityAreaMapper;
     private final StoreImgMapper storeImgMapper;
     private final FitnessEquipmentInfoService fitnessEquipmentInfoService;
 
@@ -199,9 +202,10 @@ public class SportsEquipmentFacilityServiceImpl extends ServiceImpl<SportsEquipm
 
     /**
      * 查询指定店铺按分类汇总的设备信息(用户端)
-     * 包含每个分类的设备数量、图片列表和设备列表
-     * 通过fitnessEquipmentIds关联FitnessEquipmentInfo信息
-     * 按facility_category_name分组查询,支持商户自定义分类名称
+     * <p>
+     * 从sports_facility_area表获取区域列表,通过area_id关联sports_equipment_facility,
+     * 再通过fitness_equipment_ids关联fitness_equipment_info获取设备信息
+     * </p>
      *
      * @param storeId 门店ID,不能为空且必须大于0
      * @return 分类汇总列表,不会返回null
@@ -219,24 +223,23 @@ public class SportsEquipmentFacilityServiceImpl extends ServiceImpl<SportsEquipm
         try {
             List<SportsEquipmentFacilityCategorySummaryVo> result = new ArrayList<>();
             
-            // 查询该店铺下所有不同的设施分类名称(专门用于categorySummary)
-            List<String> categoryNameList = queryDistinctCategoryNamesForCategorySummary(storeId);
+            // 1. 从sports_facility_area表查询该店铺下的所有区域
+            List<SportsFacilityArea> areaList = queryAreaListByStoreId(storeId);
             
-            if (CollectionUtils.isEmpty(categoryNameList)) {
-                log.info("查询指定店铺按分类汇总的设备信息完成,storeId={},未找到分类数据", storeId);
+            if (CollectionUtils.isEmpty(areaList)) {
+                log.info("查询指定店铺按分类汇总的设备信息完成,storeId={},未找到区域数据", storeId);
                 return result;
             }
             
-            // 按分类名称分组查询
-            for (String categoryName : categoryNameList) {
-                SportsEquipmentFacilityCategorySummaryVo categoryVo = buildCategorySummaryVoByCategoryNameForCategorySummary(
-                        storeId, categoryName);
-                // 只添加有设备数据的分类
+            // 2. 按区域分组查询设备信息
+            for (SportsFacilityArea area : areaList) {
+                SportsEquipmentFacilityCategorySummaryVo categoryVo = buildCategorySummaryVoByArea(
+                        storeId, area);
+                // 添加所有区域,包括没有设备的区域
                 result.add(categoryVo);
-
             }
             
-            log.info("查询指定店铺按分类汇总的设备信息完成,storeId={},分类数量:{}", storeId, result.size());
+            log.info("查询指定店铺按分类汇总的设备信息完成,storeId={},区域数量:{}", storeId, result.size());
             return result;
         } catch (Exception e) {
             log.error("查询指定店铺按分类汇总的设备信息异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
@@ -245,83 +248,75 @@ public class SportsEquipmentFacilityServiceImpl extends ServiceImpl<SportsEquipm
     }
     
     /**
-     * 查询指定店铺下所有不同的设施分类名称(专门用于categorySummary接口,不与其他接口共用
+     * 根据门店ID查询区域列表(专门用于categorySummary接口
      *
      * @param storeId 门店ID
-     * @return 分类名称列表,不会返回null
+     * @return 区域列表,按sort_order升序,相同排序号按创建时间降序,不会返回null
      */
-    private List<String> queryDistinctCategoryNamesForCategorySummary(Integer storeId) {
+    private List<SportsFacilityArea> queryAreaListByStoreId(Integer storeId) {
         try {
-            LambdaQueryWrapper<SportsEquipmentFacility> queryWrapper = new LambdaQueryWrapper<>();
-            queryWrapper.eq(SportsEquipmentFacility::getStoreId, storeId)
-                    .eq(SportsEquipmentFacility::getDeleteFlag, DELETE_FLAG_NOT_DELETED)
-                    .isNotNull(SportsEquipmentFacility::getFacilityCategoryName)
-                    .ne(SportsEquipmentFacility::getFacilityCategoryName, "");
+            LambdaQueryWrapper<SportsFacilityArea> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(SportsFacilityArea::getStoreId, storeId)
+                    .eq(SportsFacilityArea::getDeleteFlag, DELETE_FLAG_NOT_DELETED)
+                    .orderByAsc(SportsFacilityArea::getSortOrder)
+                    .orderByDesc(SportsFacilityArea::getCreatedTime);
             
-            List<SportsEquipmentFacility> facilityList = facilityMapper.selectList(queryWrapper);
+            List<SportsFacilityArea> areaList = sportsFacilityAreaMapper.selectList(queryWrapper);
             
-            if (CollectionUtils.isEmpty(facilityList)) {
+            if (CollectionUtils.isEmpty(areaList)) {
                 return new ArrayList<>();
             }
             
-            // 提取所有不同的分类名称并排序
-            return facilityList.stream()
-                    .map(SportsEquipmentFacility::getFacilityCategoryName)
-                    .filter(StringUtils::isNotBlank)
-                    .distinct()
-                    .sorted()
-                    .collect(Collectors.toList());
+            return areaList;
         } catch (Exception e) {
-            log.error("查询设施分类名称列表异常(categorySummary专用),storeId={},异常信息:{}", storeId, e.getMessage(), e);
+            log.error("查询区域列表异常(categorySummary专用),storeId={},异常信息:{}", storeId, e.getMessage(), e);
             return new ArrayList<>();
         }
     }
     
     /**
-     * 根据分类名称构建分类汇总VO对象(专门用于categorySummary接口,不与其他接口共用
+     * 根据区域构建分类汇总VO对象(专门用于categorySummary接口)
      *
-     * @param storeId      门店ID
-     * @param categoryName 分类名称
+     * @param storeId 门店ID
+     * @param area    区域对象
      * @return 分类汇总VO对象
      */
-    private SportsEquipmentFacilityCategorySummaryVo buildCategorySummaryVoByCategoryNameForCategorySummary(Integer storeId, String categoryName) {
+    private SportsEquipmentFacilityCategorySummaryVo buildCategorySummaryVoByArea(Integer storeId, SportsFacilityArea area) {
         SportsEquipmentFacilityCategorySummaryVo categoryVo = new SportsEquipmentFacilityCategorySummaryVo();
         categoryVo.setStoreId(storeId);
-        categoryVo.setFacilityCategoryName(categoryName);
+        categoryVo.setFacilityCategoryName(area.getAreaName());
         
-        // 查询该分类名称下的设备列表(专门用于categorySummary)
-        List<SportsEquipmentFacility> facilityList = queryFacilityListByCategoryNameForCategorySummary(storeId, categoryName);
+        // 1. 通过area_id查询sports_equipment_facility表中的设备列表
+        List<SportsEquipmentFacility> facilityList = queryFacilityListByAreaId(storeId, area.getId());
         
-        // 收集并解析fitnessEquipmentIds(专门用于categorySummary)
+        // 2. 收集并解析fitnessEquipmentIds
         List<Integer> fitnessEquipmentIdList = collectFitnessEquipmentIdsForCategorySummary(facilityList, storeId);
         
-        // 查询FitnessEquipmentInfo列表(专门用于categorySummary)
+        // 3. 通过fitness_equipment_ids关联查询fitness_equipment_info设备记录
         List<FitnessEquipmentInfo> fitnessEquipmentInfoList = queryFitnessEquipmentInfoListForCategorySummary(fitnessEquipmentIdList, storeId);
 
-        // 设置设备列表
+        // 4. 设置设备列表
         categoryVo.setFacilityList(fitnessEquipmentInfoList);
         categoryVo.setFacilityCount(fitnessEquipmentInfoList.size());
 
-        // 查询并设置图片列表(专门用于categorySummary)
-        // 兼容处理:图片存储时business_id存储的是facility_category编号
-        // 需要从该分类名称下的设备中获取facility_category值来查询图片
-        List<String> imageList = queryCategoryImageListByCategoryNameForCategorySummary(storeId, categoryName, facilityList);
+        // 5. 查询并设置图片列表(根据区域ID查询)
+        List<String> imageList = queryCategoryImageListByAreaId(storeId, area.getId());
         categoryVo.setImageList(imageList);
         
         return categoryVo;
     }
     
     /**
-     * 根据分类名称查询设备列表(专门用于categorySummary接口,不与其他接口共用
+     * 根据区域ID查询设备列表(专门用于categorySummary接口)
      *
-     * @param storeId      门店ID
-     * @param categoryName 分类名称
+     * @param storeId 门店ID
+     * @param areaId  区域ID
      * @return 设备列表
      */
-    private List<SportsEquipmentFacility> queryFacilityListByCategoryNameForCategorySummary(Integer storeId, String categoryName) {
+    private List<SportsEquipmentFacility> queryFacilityListByAreaId(Integer storeId, Integer areaId) {
         LambdaQueryWrapper<SportsEquipmentFacility> facilityWrapper = new LambdaQueryWrapper<>();
         facilityWrapper.eq(SportsEquipmentFacility::getStoreId, storeId)
-                .eq(SportsEquipmentFacility::getFacilityCategoryName, categoryName)
+                .eq(SportsEquipmentFacility::getAreaId, areaId)
                 .eq(SportsEquipmentFacility::getDeleteFlag, DELETE_FLAG_NOT_DELETED)
                 .orderByDesc(SportsEquipmentFacility::getCreatedTime);
         return facilityMapper.selectList(facilityWrapper);
@@ -426,33 +421,19 @@ public class SportsEquipmentFacilityServiceImpl extends ServiceImpl<SportsEquipm
     }
     
     /**
-     * 根据分类名称查询图片列表(专门用于categorySummary接口,不与其他接口共用)
-     * 兼容处理:图片存储时business_id存储的是facility_category编号
-     * 需要从该分类名称下的设备中获取facility_category值来查询图片
+     * 根据区域ID查询图片列表(专门用于categorySummary接口)
+     * 图片存储时business_id存储的是区域ID(area_id)
      *
-     * @param storeId      门店ID
-     * @param categoryName 分类名称
-     * @param facilityList 该分类下的设备列表
+     * @param storeId 门店ID
+     * @param areaId  区域ID
      * @return 图片URL列表
      */
-    private List<String> queryCategoryImageListByCategoryNameForCategorySummary(Integer storeId, String categoryName, 
-                                                                               List<SportsEquipmentFacility> facilityList) {
+    private List<String> queryCategoryImageListByAreaId(Integer storeId, Integer areaId) {
         try {
-            // 从设备列表中获取所有不同的facility_category值
-            List<Integer> categoryIdList = facilityList.stream()
-                    .map(SportsEquipmentFacility::getFacilityCategory)
-                    .filter(category -> category != null && category > 0)
-                    .distinct()
-                    .collect(Collectors.toList());
-            
-            if (CollectionUtils.isEmpty(categoryIdList)) {
-                return new ArrayList<>();
-            }
-            
-            // 查询所有相关分类的图片
+            // 查询该区域下的图片
             LambdaQueryWrapper<StoreImg> imageWrapper = new LambdaQueryWrapper<>();
             imageWrapper.eq(StoreImg::getStoreId, storeId)
-                    .in(StoreImg::getBusinessId, categoryIdList)
+                    .eq(StoreImg::getBusinessId, areaId)
                     .eq(StoreImg::getImgType, IMG_TYPE_SPORTS_EQUIPMENT)
                     .orderByAsc(StoreImg::getImgSort);
             
@@ -467,8 +448,8 @@ public class SportsEquipmentFacilityServiceImpl extends ServiceImpl<SportsEquipm
                     .distinct()
                     .collect(Collectors.toList());
         } catch (Exception e) {
-            log.error("查询分类图片列表异常(categorySummary专用),storeId={},categoryName={},异常信息:{}", 
-                    storeId, categoryName, e.getMessage(), e);
+            log.error("查询区域图片列表异常(categorySummary专用),storeId={},areaId={},异常信息:{}", 
+                    storeId, areaId, e.getMessage(), e);
             return new ArrayList<>();
         }
     }

+ 46 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreCuisineComboServiceImpl.java

@@ -0,0 +1,46 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.StoreCuisineCombo;
+import shop.alien.entity.store.dto.CuisineComboDto;
+import shop.alien.mapper.StoreCuisineComboMapper;
+import shop.alien.store.service.StoreCuisineComboService;
+
+import java.util.List;
+
+/**
+ * 美食套餐表(辅助详情查询) 服务实现类
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class StoreCuisineComboServiceImpl extends ServiceImpl<StoreCuisineComboMapper, StoreCuisineCombo> implements StoreCuisineComboService {
+
+    @Override
+    public boolean saveBatch(List<StoreCuisineCombo> comboList) {
+        log.info("StoreCuisineComboServiceImpl.saveBatch?comboList.size={}", comboList != null ? comboList.size() : 0);
+        if (comboList == null || comboList.isEmpty()) {
+            return false;
+        }
+        return super.saveBatch(comboList, comboList.size());
+    }
+
+    @Override
+    public boolean deleteByComboId(Integer cid) {
+        log.info("StoreCuisineComboServiceImpl.deleteByComboId?cid={}", cid);
+        LambdaQueryWrapper<StoreCuisineCombo> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreCuisineCombo::getCid, cid);
+        return this.remove(queryWrapper);
+    }
+
+}
+

+ 206 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreCuisineServiceImpl.java

@@ -0,0 +1,206 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.StoreCuisine;
+import shop.alien.entity.store.StoreCuisineCombo;
+import shop.alien.entity.store.dto.CategoryGroupDto;
+import shop.alien.entity.store.dto.CuisineComboDto;
+import shop.alien.entity.store.dto.CuisineDetailDto;
+import shop.alien.entity.store.dto.CuisineItemDto;
+import shop.alien.mapper.StoreCuisineMapper;
+import shop.alien.store.service.StoreCuisineComboService;
+import shop.alien.store.service.StoreCuisineService;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 美食价目表 服务实现类
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+@Service
+@RequiredArgsConstructor
+@Transactional(rollbackFor = Exception.class)
+public class StoreCuisineServiceImpl extends ServiceImpl<StoreCuisineMapper, StoreCuisine> implements StoreCuisineService {
+
+    private final StoreCuisineComboService storeCuisineComboService;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    /**
+     * 新增美食(单品或套餐)
+     */
+    @Override
+    public boolean addCuisineCombo(CuisineComboDto cuisineComboDto) {
+        // 1. 保存主表信息到 store_cuisine
+        StoreCuisine cuisine = new StoreCuisine();
+        BeanUtils.copyProperties(cuisineComboDto, cuisine);
+        save(cuisine);
+
+        // 单品:只写主表,不写中间表
+        if (cuisineComboDto.getCuisineType() == 1) {
+            return true;
+        }
+
+        // 2. 套餐:解析 JSON,生成中间表记录并批量插入
+        Integer comboId = cuisine.getId();
+        List<StoreCuisineCombo> comboList = buildComboListFromJson(comboId, cuisineComboDto.getGroupJson());
+        return storeCuisineComboService.saveBatch(comboList);
+    }
+
+    /**
+     * 修改美食(单品或套餐)
+     */
+    @Override
+    public boolean updateCuisineCombo(CuisineComboDto cuisineComboDto) {
+        Integer comboId = cuisineComboDto.getId();
+
+        // 1. 更新主表信息
+        StoreCuisine cuisine = new StoreCuisine();
+        BeanUtils.copyProperties(cuisineComboDto, cuisine);
+        cuisine.setId(comboId);
+        updateById(cuisine);
+
+        // 单品:只更新主表即可
+        if (cuisineComboDto.getCuisineType() == 1) {
+            return true;
+        }
+
+        // 2. 套餐:删除旧的中间表记录(逻辑删除)
+        LambdaQueryWrapper<StoreCuisineCombo> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreCuisineCombo::getCid, comboId);
+        storeCuisineComboService.remove(wrapper);
+
+        // 3. 重新解析 JSON,生成新的中间表记录并批量插入
+        List<StoreCuisineCombo> comboList = buildComboListFromJson(comboId, cuisineComboDto.getGroupJson());
+        return storeCuisineComboService.saveBatch(comboList);
+    }
+
+    /**
+     * 获取所有单品名称(cuisine_type = 1),用于添加套餐
+     */
+    @Override
+    public List<StoreCuisine> getSingleName() {
+        return lambdaQuery()
+                .eq(StoreCuisine::getCuisineType, 1)
+                .list();
+    }
+
+    /**
+     * 上下架操作:1-上架,2-下架
+     */
+    @Override
+    public boolean changeShelfStatus(Integer id, Integer shelfStatus) {
+        StoreCuisine cuisine = new StoreCuisine();
+        cuisine.setId(id);
+        cuisine.setShelfStatus(shelfStatus);
+        return updateById(cuisine);
+    }
+
+    /**
+     * 根据 id 与类型获取详情:
+     * - type = 1:返回单品详情
+     * - type = 2:返回套餐详情(主信息 + 套餐包含的单品列表)
+     */
+    @Override
+    public CuisineDetailDto getByCuisineType(Integer id, Integer cuisineType) {
+        // 1. 先查主表记录
+        StoreCuisine base = getById(id);
+        if (base == null) {
+            return null;
+        }
+
+        CuisineDetailDto dto = new CuisineDetailDto();
+        dto.setBaseInfo(base);
+
+        // 单品:直接返回主信息,items 为空
+        if (cuisineType != null && cuisineType == 1) {
+            dto.setItems(Collections.emptyList());
+            return dto;
+        }
+
+        // 套餐:通过中间表查出包含哪些单品
+        LambdaQueryWrapper<StoreCuisineCombo> comboWrapper = new LambdaQueryWrapper<>();
+        comboWrapper.eq(StoreCuisineCombo::getCid, id);
+        List<StoreCuisineCombo> combos = storeCuisineComboService.list(comboWrapper);
+        if (combos.isEmpty()) {
+            dto.setItems(Collections.emptyList());
+            return dto;
+        }
+
+        // 收集所有单品 sid
+        List<Integer> sidList = combos.stream()
+                .map(StoreCuisineCombo::getSid)
+                .distinct()
+                .collect(Collectors.toList());
+
+        // 一次性查出所有单品
+        List<StoreCuisine> singles = lambdaQuery()
+                .in(StoreCuisine::getId, sidList)
+                .list();
+        Map<Integer, StoreCuisine> singleMap = singles.stream()
+                .collect(Collectors.toMap(StoreCuisine::getId, s -> s));
+
+        // 组装套餐明细
+        List<CuisineDetailDto.CuisineItemDetail> itemDetails = new ArrayList<>();
+        for (StoreCuisineCombo combo : combos) {
+            StoreCuisine single = singleMap.get(combo.getSid());
+            if (single == null) {
+                continue;
+            }
+            CuisineDetailDto.CuisineItemDetail detail = new CuisineDetailDto.CuisineItemDetail();
+            detail.setSingle(single);
+            detail.setQuantity(combo.getSnum());
+            detail.setCategory(combo.getCategory());
+            itemDetails.add(detail);
+        }
+
+        dto.setItems(itemDetails);
+        return dto;
+    }
+
+    /**
+     * 根据前端传入的 JSON,构建中间表记录列表
+     *
+     * @param comboId  套餐ID
+     * @param groupJson 前端传入的 JSON 字符串
+     * @return 中间表记录列表
+     */
+    private List<StoreCuisineCombo> buildComboListFromJson(Integer comboId, String groupJson) {
+        List<CategoryGroupDto> groups;
+        try {
+            groups = objectMapper.readValue(groupJson, new TypeReference<List<CategoryGroupDto>>() {});
+        } catch (IOException e) {
+            throw new RuntimeException("解析套餐组合 JSON 失败", e);
+        }
+
+        List<StoreCuisineCombo> comboList = new ArrayList<>();
+        for (CategoryGroupDto group : groups) {
+            String categoryName = group.getCategoryName();
+            for (CuisineItemDto item : group.getItems()) {
+                StoreCuisineCombo combo = new StoreCuisineCombo();
+                combo.setCid(comboId);
+                combo.setSid(item.getCuisineId());
+                combo.setSnum(item.getQuantity());
+                combo.setCategory(categoryName);
+                comboList.add(combo);
+            }
+        }
+        return comboList;
+    }
+}
+
+

+ 20 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreImgServiceImpl.java

@@ -20,6 +20,7 @@ import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.StoreOfficialAlbumMapper;
 import shop.alien.store.service.StoreImgService;
 import shop.alien.store.util.ai.AiImageColorExtractUtil;
+import shop.alien.util.common.Constants;
 
 import java.util.Comparator;
 import java.util.List;
@@ -207,4 +208,23 @@ public class StoreImgServiceImpl extends ServiceImpl<StoreImgMapper, StoreImg> i
             log.error("异步提取图片颜色异常,storeImgId: {}, imgUrl: {}", storeImgId, imgUrl, e);
         }
     }
+
+    @Override
+    public List<StoreImg> getByCover(Integer storeId, Integer imgType) {
+
+        log.info("StoreImgController.getByBusinessId?storeId={}&imgType={}", storeId, imgType);
+
+
+
+        QueryWrapper<StoreImg> queryWrapper = new QueryWrapper<>();
+
+        queryWrapper.lambda()
+                .eq(StoreImg::getImgType, imgType) // 商品 图片
+                .eq(StoreImg::getDeleteFlag, Constants.DeleteFlag.NOT_DELETED)
+                .eq(StoreImg::getStoreId,storeId );
+        List<StoreImg> imagesList= storeImgMapper.getByCover(queryWrapper);
+
+
+        return imagesList;
+    }
 }

+ 175 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java

@@ -4834,6 +4834,181 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         // SQL已经实现了距离过滤和排序,直接返回结果
         return storeInfoVoList;
     }
+
+    /**
+     * 根据店铺ID获取推荐店铺列表(用户端)
+     * <p>
+     * 只使用store_id查询,不包含其他筛选条件(经营板块、经营分类、分类、经纬度等)
+     * </p>
+     *
+     * @param storeId 店铺ID,必填,必须大于0
+     * @return 推荐店铺列表
+     */
+    @Override
+    public List<StoreInfoVo> getRecommendedStoresByStoreId(Integer storeId) {
+        log.info("根据店铺ID获取推荐店铺列表,storeId={}", storeId);
+        
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            log.warn("获取推荐店铺列表失败,店铺ID无效:{}", storeId);
+            return Collections.emptyList();
+        }
+        
+        try {
+            QueryWrapper<StoreInfoVo> queryWrapper = new QueryWrapper<>();
+            // 基础条件:未删除、已启用
+            queryWrapper.eq("a.delete_flag", 0)
+                    .eq("b.delete_flag", 0)
+                    .ne("a.business_status", 99) // 过滤永久关门的店铺
+                    .ne("a.store_status", 0); // 过滤禁用的店铺
+            
+            // 排除当前店铺
+            queryWrapper.ne("a.id", storeId);
+            
+            // 过期时间判断
+            Date currentDate = new Date();
+            // 如果 expiration_time 为空则不做过期判断;如果不为空则要求大于当前时间
+            queryWrapper.and(w -> w.isNull("a.expiration_time")
+                    .or()
+                    .gt("a.expiration_time", currentDate));
+            
+            // 如果 food_licence_expiration_time 为空则不做过期判断;如果不为空则要求大于当前时间
+            queryWrapper.and(w -> w.isNull("a.food_licence_expiration_time")
+                    .or()
+                    .gt("a.food_licence_expiration_time", currentDate));
+            
+            // 查询店铺列表(限制20条)
+            queryWrapper.last("limit 20");
+            
+            List<StoreInfoVo> storeInfoVoList = storeInfoMapper.getStoreInfoVoList(queryWrapper);
+            
+            if (CollectionUtils.isEmpty(storeInfoVoList)) {
+                return Collections.emptyList();
+            }
+            
+            // 提前查询所有需要的字典数据
+            List<StoreInfoVo> collect = storeInfoVoList.stream()
+                    .filter(record -> StringUtils.isNotEmpty(record.getStoreType()))
+                    .collect(Collectors.toList());
+            Set<String> allTypes = collect.stream()
+                    .map(StoreInfoVo::getStoreType)
+                    .flatMap(type -> Arrays.stream(type.split(",")))
+                    .collect(Collectors.toSet());
+            
+            List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(
+                    new LambdaQueryWrapper<StoreDictionary>()
+                            .eq(StoreDictionary::getTypeName, "storeType")
+                            .isNull(StoreDictionary::getParentId)
+                            .in(!allTypes.isEmpty(), StoreDictionary::getDictId, allTypes));
+            Map<String, String> typeMap = storeDictionaries.stream()
+                    .collect(Collectors.toMap(StoreDictionary::getDictId, StoreDictionary::getDictDetail));
+            
+            // 计算平均分和评价
+            Map<String, List<Map<String, Object>>> avgScoreMap = new HashMap<>();
+            Map<Integer, List<StoreComment>> commentMap = new HashMap<>();
+            
+            // 注意:需要将store_id转换为String类型,与后续containsKey判断保持一致
+            avgScoreMap = storeEvaluationMapper.allStoreAvgScore().stream()
+                    .collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
+            List<StoreComment> storeComments = storeCommentMapper.selectList(
+                    new QueryWrapper<StoreComment>()
+                            .eq("business_type", "5")
+                            .eq("delete_flag", 0));
+            commentMap = storeComments.stream()
+                    .filter(comment -> comment.getStoreId() != null)
+                    .collect(Collectors.groupingBy(StoreComment::getStoreId));
+            
+            // 查询入口头图
+            List<Integer> storeIds = storeInfoVoList.stream()
+                    .map(StoreInfoVo::getId)
+                    .collect(Collectors.toList());
+            
+            List<StoreImg> storeImgList = storeImgMapper.selectList(
+                    new QueryWrapper<StoreImg>()
+                            .in("store_id", storeIds)
+                            .eq("img_type", 1));
+            Map<Integer, List<StoreImg>> storeCollect = storeImgList.stream()
+                    .collect(Collectors.groupingBy(StoreImg::getStoreId));
+            
+            // 处理每个店铺的数据
+            for (StoreInfoVo record : storeInfoVoList) {
+                // 处理类型
+                if (StringUtils.isNotEmpty(record.getStoreType())) {
+                    String[] types = record.getStoreType().split(",");
+                    List<String> typeDetails = Arrays.stream(types)
+                            .map(typeMap::get)
+                            .filter(Objects::nonNull)
+                            .collect(Collectors.toList());
+                    record.setStoreTypeStr(String.join(",", typeDetails));
+                    record.setStoreTypeList(Arrays.asList(types));
+                }
+                
+                // 加入头图
+                if (!CollectionUtils.isEmpty(storeCollect) && storeCollect.containsKey(record.getId())) {
+                    List<StoreImg> storeImgs = storeCollect.get(record.getId());
+                    if (!CollectionUtils.isEmpty(storeImgs)) {
+                        record.setEntranceImage(storeImgs.get(0).getImgUrl());
+                    }
+                }
+                
+                // 处理经纬度
+                if (StringUtils.isNotEmpty(record.getStorePosition())) {
+                    String[] split = record.getStorePosition().split(",");
+                    if (split.length >= 2) {
+                        record.setStorePositionLongitude(split[0]);
+                        record.setStorePositionLatitude(split[1]);
+                    }
+                }
+                
+                // 处理到期状态
+                Date expirationTime = record.getExpirationTime();
+                if (expirationTime != null) {
+                    Calendar now = Calendar.getInstance();
+                    Date nowCurrentDate = now.getTime();
+                    now.add(Calendar.DAY_OF_YEAR, 30);
+                    Date thirtyDaysLater = now.getTime();
+                    
+                    if (expirationTime.after(currentDate)) {
+                        record.setExpiredState("0");
+                        if ((expirationTime.after(nowCurrentDate) || expirationTime.equals(nowCurrentDate))
+                                && expirationTime.before(thirtyDaysLater)) {
+                            record.setExpiredState("1");
+                        }
+                    } else {
+                        record.setExpiredState("2");
+                    }
+                    
+                    LocalDate nowLocal = LocalDate.now();
+                    LocalDate expDate = expirationTime.toInstant()
+                            .atZone(ZoneId.systemDefault())
+                            .toLocalDate();
+                    long daysToExpire = ChronoUnit.DAYS.between(nowLocal, expDate);
+                    record.setDaysToExpire(daysToExpire);
+                }
+                
+                // 设置店铺得分
+                if (!CollectionUtils.isEmpty(avgScoreMap) && avgScoreMap.containsKey(String.valueOf(record.getId()))) {
+                    record.setAvgScore(String.valueOf(avgScoreMap.get(String.valueOf(record.getId())).get(0).get("avg_score")));
+                } else {
+                    record.setAvgScore("0");
+                }
+                
+                // 设置总评论数
+                if (!CollectionUtils.isEmpty(commentMap) && commentMap.containsKey(record.getId())) {
+                    record.setTotalNum(String.valueOf(commentMap.get(record.getId()).size()));
+                } else {
+                    record.setTotalNum("0");
+                }
+            }
+            
+            log.info("根据店铺ID获取推荐店铺列表成功,storeId={},返回店铺数量:{}", storeId, storeInfoVoList.size());
+            return storeInfoVoList;
+        } catch (Exception e) {
+            log.error("根据店铺ID获取推荐店铺列表异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
+            throw new RuntimeException("获取推荐店铺列表失败:" + e.getMessage(), e);
+        }
+    }
+
     @Override
     public int uploadEntertainmentLicence(StoreImg storeImg) {
         storeImgMapper.delete(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getImgType, 24).eq(StoreImg::getStoreId, storeImg.getStoreId()));

+ 107 - 38
alien-store/src/main/java/shop/alien/store/service/impl/StoreOfficialAlbumServiceImpl.java

@@ -10,13 +10,18 @@ import org.apache.commons.lang.StringUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.StoreInfo;
 import shop.alien.entity.store.StoreOfficialAlbum;
+import shop.alien.entity.store.StoreVideo;
 import shop.alien.entity.store.vo.StoreAlbumDetailVo;
 import shop.alien.entity.store.vo.StoreAlbumNameVo;
 import shop.alien.entity.store.vo.StoreOfficialAlbumImgVo;
 import shop.alien.entity.store.vo.StoreOfficialAlbumVo;
 import shop.alien.mapper.StoreImgMapper;
+import shop.alien.mapper.StoreInfoMapper;
 import shop.alien.mapper.StoreOfficialAlbumMapper;
+import shop.alien.mapper.StoreVideoMapper;
+import shop.alien.store.service.StoreImgService;
 import shop.alien.store.service.StoreOfficialAlbumService;
 import shop.alien.store.util.CommonConstant;
 import java.util.Collections;
@@ -33,6 +38,12 @@ public class StoreOfficialAlbumServiceImpl extends ServiceImpl<StoreOfficialAlbu
     private final StoreOfficialAlbumMapper storeOfficialAlbumMapper;
     private final StoreImgMapper storeImgMapper;
 
+    private final StoreVideoMapper storeVideoMapper;
+
+    private final StoreInfoMapper storeInfoMapper;
+
+    private final StoreImgService storeImgService;
+
     /**
      * 创建官方相册
      *
@@ -113,13 +124,16 @@ public class StoreOfficialAlbumServiceImpl extends ServiceImpl<StoreOfficialAlbu
      * 查询条件:imgType = 2(官方相册),通过 business_id 关联到 store_official_album 表,按 albumName 筛选
      * </p>
      *
-     * @param storeId   门店ID,必填
+     * @param storeId   门店ID,必填,必须大于0
      * @param albumName 相册名称,可选。例如:酒水、餐食、环境、全部等。当为null或空字符串时,查询全部
+     * @param type      类型,必填。1:视频,2:相册,3:环境
+     * @param pageNum   页码,可选,默认1
+     * @param pageSize  每页数量,可选,默认10
      * @return 图片列表和总数
      */
     @Override
-    public StoreOfficialAlbumImgVo getOfficialAlbumImgList(Integer storeId, String albumName) {
-        log.info("开始获取官方相册图片列表,门店ID:{},相册名称:{}", storeId, albumName);
+    public StoreOfficialAlbumImgVo getOfficialAlbumImgList(Integer storeId, String albumName, Integer type, Integer pageNum, Integer pageSize) {
+        log.info("开始获取官方相册图片列表,门店ID:{},相册名称:{},类型:{}", storeId, albumName, type);
 
         // 参数校验
         if (storeId == null || storeId <= 0) {
@@ -127,52 +141,107 @@ public class StoreOfficialAlbumServiceImpl extends ServiceImpl<StoreOfficialAlbu
             throw new IllegalArgumentException("门店ID不能为空且必须大于0");
         }
 
-        // 先查询符合条件的官方相册ID列表
-        LambdaQueryWrapper<StoreOfficialAlbum> albumQueryWrapper = new LambdaQueryWrapper<>();
-        albumQueryWrapper.eq(StoreOfficialAlbum::getStoreId, storeId)
-                .eq(StoreOfficialAlbum::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE);
-
-        // 如果指定了相册名称,添加筛选条件
-        if (StringUtils.isNotBlank(albumName)) {
-            albumQueryWrapper.eq(StoreOfficialAlbum::getAlbumName, albumName);
+        if (type == null || type <= 0) {
+            log.warn("获取类型无效:{}", type);
+            throw new IllegalArgumentException("类型不能为空且必须大于0");
         }
-
-        List<StoreOfficialAlbum> albumList = storeOfficialAlbumMapper.selectList(albumQueryWrapper);
-
-        // 如果相册列表为空,直接返回空结果
-        if (CollectionUtils.isEmpty(albumList)) {
-            log.info("获取官方相册图片列表,门店ID:{},未查询到符合条件的相册", storeId);
+        //视频
+        if (type == 1) {
+            // 查询视频
+            LambdaQueryWrapper<StoreVideo> albumQueryWrapper = new LambdaQueryWrapper<>();
+            albumQueryWrapper.eq(StoreVideo::getStoreId, storeId)
+                    .eq(StoreVideo::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE);
+            List<StoreVideo> videoList = storeVideoMapper.selectList(albumQueryWrapper);
             StoreOfficialAlbumImgVo result = new StoreOfficialAlbumImgVo();
-            result.setImgList(Collections.emptyList());
-            result.setTotalCount(0);
+            result.setVideoList(videoList);
             return result;
         }
+        //相册
+        if (type == 2) {
+            // 先查询符合条件的官方相册ID列表
+            LambdaQueryWrapper<StoreOfficialAlbum> albumQueryWrapper = new LambdaQueryWrapper<>();
+            albumQueryWrapper.eq(StoreOfficialAlbum::getStoreId, storeId)
+                    .eq(StoreOfficialAlbum::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE);
+
+            // 如果指定了相册名称,添加筛选条件
+            if (StringUtils.isNotBlank(albumName)) {
+                albumQueryWrapper.eq(StoreOfficialAlbum::getAlbumName, albumName);
+            }
 
-        // 提取相册ID列表
-        List<Integer> albumIds = albumList.stream()
-                .map(StoreOfficialAlbum::getId)
-                .collect(Collectors.toList());
+            List<StoreOfficialAlbum> albumList = storeOfficialAlbumMapper.selectList(albumQueryWrapper);
 
-        log.debug("查询到符合条件的相册数量:{},相册ID列表:{}", albumList.size(), albumIds);
+            // 如果相册列表为空,直接返回空结果
+            if (CollectionUtils.isEmpty(albumList)) {
+                log.info("获取官方相册图片列表,门店ID:{},未查询到符合条件的相册", storeId);
+                StoreOfficialAlbumImgVo result = new StoreOfficialAlbumImgVo();
+                result.setImgList(Collections.emptyList());
+                result.setTotalCount(0);
+                return result;
+            }
 
-        // 查询这些相册下的所有图片(imgType = 2 表示官方相册)
-        LambdaQueryWrapper<StoreImg> imgQueryWrapper = new LambdaQueryWrapper<>();
-        imgQueryWrapper.eq(StoreImg::getStoreId, storeId)
-                .eq(StoreImg::getImgType, CommonConstant.STORE_IMG_ALBUM) // imgType = 2 表示官方相册
-                .in(StoreImg::getBusinessId, albumIds) // business_id 关联到相册ID
-                .eq(StoreImg::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE)
-                .orderByAsc(StoreImg::getImgSort);
+            // 提取相册ID列表
+            List<Integer> albumIds = albumList.stream()
+                    .map(StoreOfficialAlbum::getId)
+                    .collect(Collectors.toList());
 
-        List<StoreImg> imgList = storeImgMapper.selectList(imgQueryWrapper);
+            log.debug("查询到符合条件的相册数量:{},相册ID列表:{}", albumList.size(), albumIds);
 
-        // 构建返回结果
-        StoreOfficialAlbumImgVo result = new StoreOfficialAlbumImgVo();
-        result.setImgList(imgList);
-        result.setTotalCount(imgList.size());
+            // 查询这些相册下的所有图片(imgType = 2 表示官方相册)
+            LambdaQueryWrapper<StoreImg> imgQueryWrapper = new LambdaQueryWrapper<>();
+            imgQueryWrapper.eq(StoreImg::getStoreId, storeId)
+                    .eq(StoreImg::getImgType, CommonConstant.STORE_IMG_ALBUM) // imgType = 2 表示官方相册
+                    .in(StoreImg::getBusinessId, albumIds) // business_id 关联到相册ID
+                    .eq(StoreImg::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE)
+                    .orderByAsc(StoreImg::getImgSort);
 
-        log.info("获取官方相册图片列表完成,门店ID:{},相册名称:{},返回图片数量:{}",
-                storeId, albumName, result.getTotalCount());
+            List<StoreImg> imgList = storeImgMapper.selectList(imgQueryWrapper);
+            // 构建返回结果
+            StoreOfficialAlbumImgVo result = new StoreOfficialAlbumImgVo();
+            result.setImgList(imgList);
+            result.setTotalCount(imgList.size());
+
+            log.info("获取官方相册图片列表完成,门店ID:{},相册名称:{},返回图片数量:{}",
+                    storeId, albumName, result.getTotalCount());
 
+            return result;
+        }
+        //封面
+        if (type == 3) {
+            StoreInfo storeInfo = storeInfoMapper.getStoreInfo(storeId);
+
+            if (storeInfo == null) {
+                log.warn("获取门店信息失败,门店ID:{}", storeId);
+                StoreOfficialAlbumImgVo result = new StoreOfficialAlbumImgVo();
+                result.setImgList(Collections.emptyList());
+                result.setTotalCount(0);
+                return result;
+            }
+            // 单图模式 封面
+            if (storeInfo.getImgMode() == 0) {
+                List<StoreImg> imgList = storeImgService.getByCover(storeId, 20);
+                StoreOfficialAlbumImgVo result = new StoreOfficialAlbumImgVo();
+                result.setImgList(imgList);
+                return result;
+            }
+            // 多图模式 封面
+            if (storeInfo.getImgMode() == 1) {
+                List<StoreImg> imgList = storeImgService.getByCover(storeId, 21);
+                StoreOfficialAlbumImgVo result = new StoreOfficialAlbumImgVo();
+                result.setImgList(imgList);
+                return result;
+            }
+        }
+        //环境
+        if (type == 4){
+            List<StoreImg> imgList = storeImgService.getByCover(storeId, 4);
+            StoreOfficialAlbumImgVo result = new StoreOfficialAlbumImgVo();
+            result.setImgList(imgList);
+        }
+
+        // 默认返回空结果
+        StoreOfficialAlbumImgVo result = new StoreOfficialAlbumImgVo();
+        result.setImgList(Collections.emptyList());
+        result.setTotalCount(0);
         return result;
     }
 

+ 23 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StorePriceServiceImpl.java

@@ -0,0 +1,23 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.StorePrice;
+import shop.alien.mapper.StorePriceMapper;
+import shop.alien.store.service.StorePriceService;
+
+/**
+ * 通用价目表 服务实现类
+ *
+ * @author auto-generated
+ * @since 2025-01-01
+ */
+@Service
+@RequiredArgsConstructor
+@Transactional(rollbackFor = Exception.class)
+public class StorePriceServiceImpl extends ServiceImpl<StorePriceMapper, StorePrice> implements StorePriceService {
+
+}
+

+ 519 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffCommentServiceImpl.java

@@ -0,0 +1,519 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeLikeRecord;
+import shop.alien.entity.store.StoreStaffComment;
+import shop.alien.entity.store.StoreStaffReview;
+import shop.alien.entity.store.dto.StoreStaffCommentRequestDto;
+import shop.alien.entity.store.dto.StoreStaffReplyDto;
+import shop.alien.entity.store.vo.StoreStaffCommentVo;
+import shop.alien.mapper.LifeLikeRecordMapper;
+import shop.alien.mapper.StoreStaffCommentMapper;
+import shop.alien.store.service.StoreStaffCommentService;
+import shop.alien.store.service.StoreStaffReviewService;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 员工评论 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Transactional
+@Service
+public class StoreStaffCommentServiceImpl extends ServiceImpl<StoreStaffCommentMapper, StoreStaffComment> implements StoreStaffCommentService {
+
+    private static final String LIKE_TYPE_COMMENT = "10"; // 员工评论点赞类型
+    private static final int HEAD_TYPE_COMMENT = 0; // 首评类型
+    private static final int HEAD_TYPE_REPLY = 1; // 回复类型
+
+    private final StoreStaffCommentMapper storeStaffCommentMapper;
+    private final StoreStaffReviewService storeStaffReviewService;
+    private final LifeLikeRecordMapper lifeLikeRecordMapper;
+
+    public StoreStaffCommentServiceImpl(StoreStaffCommentMapper storeStaffCommentMapper,
+                                        @Lazy StoreStaffReviewService storeStaffReviewService,
+                                        LifeLikeRecordMapper lifeLikeRecordMapper) {
+        this.storeStaffCommentMapper = storeStaffCommentMapper;
+        this.storeStaffReviewService = storeStaffReviewService;
+        this.lifeLikeRecordMapper = lifeLikeRecordMapper;
+    }
+
+    @Override
+    public R<StoreStaffComment> createComment(StoreStaffComment comment) {
+        log.info("创建评论, comment={}", comment);
+        
+        // 参数校验
+        R<String> validateResult = validateComment(comment);
+        if (!validateResult.isSuccess()) {
+            return R.fail(validateResult.getMsg());
+        }
+
+        // 验证评价是否存在
+        StoreStaffReview review = storeStaffReviewService.getById(comment.getReviewId());
+        if (review == null || review.getDeleteFlag() == 1) {
+            return R.fail("评价不存在或已删除");
+        }
+
+        // 设置评论属性
+        setCommentDefaults(comment, review);
+
+        boolean success = this.save(comment);
+        if (success) {
+            // 更新评价的评论数
+            updateReviewCommentCount(review.getId(), 1);
+            log.info("创建评论成功, 评论ID={}", comment.getId());
+            return R.data(comment, "评论成功");
+        } else {
+            log.error("创建评论失败");
+            return R.fail("创建评论失败");
+        }
+    }
+
+    @Override
+    public R<List<StoreStaffCommentVo>> getCommentListByReviewId(Integer reviewId, Integer currentUserId) {
+        log.info("根据评价ID查询评论列表, reviewId={}, currentUserId={}", reviewId, currentUserId);
+
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+
+        List<StoreStaffCommentVo> comments = storeStaffCommentMapper.getCommentListByReviewId(reviewId, currentUserId);
+
+        // 为每个首评加载回复列表
+        if (!CollectionUtils.isEmpty(comments)) {
+            comments.forEach(comment -> {
+                List<StoreStaffCommentVo> replies = storeStaffCommentMapper.getReplyListByHeadId(comment.getId(), currentUserId);
+                comment.setReplyList(replies != null ? replies : new java.util.ArrayList<>());
+            });
+        }
+
+        return R.data(comments);
+    }
+
+    @Override
+    public R<Boolean> likeComment(StoreStaffCommentRequestDto requestDto) {
+        log.info("点赞评论, requestDto={}", requestDto);
+
+        Integer commentId = requestDto.getCommentId();
+        Integer userId = requestDto.getUserId();
+
+        if (commentId == null) {
+            return R.fail("评论ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 验证评论是否存在
+        StoreStaffComment comment = this.getById(commentId);
+        if (comment == null || comment.getDeleteFlag() == 1) {
+            return R.fail("评论不存在或已删除");
+        }
+
+        // 检查是否已点赞
+        if (isLiked(commentId, userId, LIKE_TYPE_COMMENT)) {
+            return R.data(true, "已点赞");
+        }
+
+        // 执行点赞
+        return doLikeComment(commentId, userId);
+    }
+
+    @Override
+    public R<Boolean> cancelLikeComment(StoreStaffCommentRequestDto requestDto) {
+        log.info("取消点赞评论, requestDto={}", requestDto);
+
+        Integer commentId = requestDto.getCommentId();
+        Integer userId = requestDto.getUserId();
+        
+        if (commentId == null) {
+            return R.fail("评论ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 执行取消点赞
+        return doCancelLikeComment(commentId, userId);
+    }
+
+    @Override
+    public R<StoreStaffComment> createReply(StoreStaffReplyDto replyDto) {
+        log.info("创建回复, replyDto={}", replyDto);
+
+        // 参数校验
+        R<String> validateResult = validateReplyDto(replyDto);
+        if (!validateResult.isSuccess()) {
+            return R.fail(validateResult.getMsg());
+        }
+
+        // 验证首评是否存在
+        StoreStaffComment headComment = this.getById(replyDto.getCommentId());
+        if (headComment == null || headComment.getDeleteFlag() == 1) {
+            return R.fail("评论不存在或已删除");
+        }
+        if (headComment.getHeadType() != HEAD_TYPE_COMMENT) {
+            return R.fail("只能回复首评");
+        }
+
+        // 构建回复对象
+        StoreStaffComment reply = buildReplyFromDto(replyDto, headComment);
+
+        boolean success = this.save(reply);
+        if (success) {
+            // 更新首评的回复数
+            updateHeadCommentReplyCount(headComment.getId(), 1);
+            // 更新评价的评论数
+            updateReviewCommentCount(reply.getReviewId(), 1);
+            log.info("创建回复成功, 回复ID={}", reply.getId());
+            return R.data(reply, "回复成功");
+        } else {
+            log.error("创建回复失败");
+            return R.fail("创建回复失败");
+        }
+    }
+
+    @Override
+    public R<List<StoreStaffCommentVo>> getReplyListByHeadId(Integer headId, Integer currentUserId) {
+        log.info("根据首评ID查询回复列表, headId={}, currentUserId={}", headId, currentUserId);
+
+        if (headId == null) {
+            return R.fail("首评ID不能为空");
+        }
+
+        List<StoreStaffCommentVo> replies = storeStaffCommentMapper.getReplyListByHeadId(headId, currentUserId);
+        return R.data(replies);
+    }
+
+    @Override
+    public R<Boolean> deleteReply(Integer replyId, Integer userId) {
+        log.info("删除回复, replyId={}, userId={}", replyId, userId);
+
+        if (replyId == null) {
+            return R.fail("回复ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 查询回复
+        StoreStaffComment reply = this.getById(replyId);
+        if (reply == null) {
+            return R.fail("回复不存在");
+        }
+        if (reply.getHeadType() != HEAD_TYPE_REPLY) {
+            return R.fail("该记录不是回复");
+        }
+
+        // 验证是否为回复用户
+        if (!reply.getSendUserId().equals(userId)) {
+            return R.fail("只能删除自己的回复");
+        }
+
+        // 删除回复(逻辑删除)
+        boolean success = this.removeById(reply.getId());
+
+        if (success) {
+            // 更新首评的回复数
+            updateHeadCommentReplyCount(reply.getHeadId(), -1);
+            // 更新评价的评论数
+            updateReviewCommentCount(reply.getReviewId(), -1);
+            log.info("删除回复成功, replyId={}", replyId);
+            return R.data(true, "删除成功");
+        } else {
+            log.error("删除回复失败, replyId={}", replyId);
+            return R.fail("删除回复失败");
+        }
+    }
+
+    @Override
+    public R<Boolean> deleteReviewComment(StoreStaffComment comment) {
+        Integer id = comment.getId();
+        Integer userId = comment.getUserId();
+        log.info("删除评论, id={}, userId={}", id, userId);
+        
+        if (id == null) {
+            return R.fail("评论ID不能为空");
+        }
+
+        // 查询评论
+        StoreStaffComment commentEntity = this.getById(id);
+        if (commentEntity == null) {
+            return R.fail("评论不存在");
+        }
+
+        // 当userId有值时,验证是否为评论用户
+        if (userId != null && !commentEntity.getSendUserId().equals(userId)) {
+            return R.fail("只能删除自己的评论");
+        }
+
+        // 计算需要减少的评论数
+        int commentCountToReduce = 1;
+
+        // 判断是否为首评论
+        if (commentEntity.getHeadType() != null && commentEntity.getHeadType() == HEAD_TYPE_COMMENT) {
+            // 如果是首评论,需要先删除所有子评论
+            LambdaQueryWrapper<StoreStaffComment> childQueryWrapper = new LambdaQueryWrapper<>();
+            childQueryWrapper.eq(StoreStaffComment::getHeadId, id)
+                    .eq(StoreStaffComment::getHeadType, HEAD_TYPE_REPLY)
+                    .eq(StoreStaffComment::getDeleteFlag, 0);
+            List<StoreStaffComment> childComments = this.list(childQueryWrapper);
+
+            if (!CollectionUtils.isEmpty(childComments)) {
+                List<Integer> childIds = childComments.stream()
+                        .map(StoreStaffComment::getId)
+                        .collect(Collectors.toList());
+                boolean deleteChildrenResult = this.removeByIds(childIds);
+                if (!deleteChildrenResult) {
+                    log.warn("删除子评论失败, 首评论ID={}, 子评论数量={}", id, childIds.size());
+                    return R.fail("删除子评论失败");
+                }
+                commentCountToReduce += childComments.size();
+            }
+        }
+
+        // 删除评论本身
+        boolean result = this.removeById(id);
+        if (result) {
+            // 更新评价的评论数
+            updateReviewCommentCount(commentEntity.getReviewId(), -commentCountToReduce);
+            
+            if (userId != null) {
+                log.info("用户删除评论成功, 评论ID={}, userId={}", id, userId);
+            } else {
+                log.info("管理员删除评论成功, 评论ID={}", id);
+            }
+            return R.success("删除成功");
+        }
+        return R.fail("删除失败");
+    }
+
+    /**
+     * 校验评论参数
+     */
+    private R<String> validateComment(StoreStaffComment comment) {
+        if (comment == null) {
+            return R.fail("评论信息不能为空");
+        }
+        if (comment.getReviewId() == null) {
+            return R.fail("评价ID不能为空");
+        }
+        if (!StringUtils.hasText(comment.getCommentContent())) {
+            return R.fail("评论内容不能为空");
+        }
+        if (comment.getSendUserId() == null) {
+            return R.fail("用户ID不能为空");
+        }
+        if (comment.getSendUserType() == null) {
+            return R.fail("发送用户类型不能为空");
+        }
+        if (comment.getReceiveUserType() == null) {
+            return R.fail("接收用户类型不能为空");
+        }
+        return R.success("校验评论参数成功");
+    }
+
+    /**
+     * 校验回复DTO参数
+     */
+    private R<String> validateReplyDto(StoreStaffReplyDto replyDto) {
+        if (replyDto == null) {
+            return R.fail("回复信息不能为空");
+        }
+        if (replyDto.getCommentId() == null) {
+            return R.fail("评论ID不能为空");
+        }
+        if (!StringUtils.hasText(replyDto.getReplyContent())) {
+            return R.fail("回复内容不能为空");
+        }
+        if (replyDto.getUserId() == null) {
+            return R.fail("用户ID不能为空");
+        }
+        return R.success("校验评论参数成功");
+    }
+
+    /**
+     * 设置评论默认值
+     */
+    private void setCommentDefaults(StoreStaffComment comment, StoreStaffReview review) {
+        if (comment.getReceiveUserId() == null) {
+            comment.setReceiveUserId(review.getUserId());
+        }
+        if (comment.getLikeCount() == null) {
+            comment.setLikeCount(0);
+        }
+        if (comment.getReplyCount() == null) {
+            comment.setReplyCount(0);
+        }
+        if (comment.getHeadType() == null) {
+            comment.setHeadType(HEAD_TYPE_COMMENT);
+        }
+        comment.setCreatedUserId(comment.getSendUserId());
+        comment.setCreatedTime(new Date());
+    }
+
+    /**
+     * 构建回复对象
+     */
+    private StoreStaffComment buildReplyFromDto(StoreStaffReplyDto replyDto, StoreStaffComment headComment) {
+        StoreStaffComment reply = new StoreStaffComment();
+        reply.setReviewId(headComment.getReviewId());
+        reply.setSendUserId(replyDto.getUserId());
+        
+        Integer receiveUserId = replyDto.getReplyToUserId() != null 
+                ? replyDto.getReplyToUserId() 
+                : headComment.getSendUserId();
+        reply.setReceiveUserId(receiveUserId);
+        
+        // 处理用户类型
+        Integer sendUserType = replyDto.getSendUserType();
+        if (sendUserType == null) {
+            sendUserType = headComment.getReceiveUserType();
+        }
+        if (sendUserType == null) {
+            throw new IllegalArgumentException("发送用户类型不能为空");
+        }
+        
+        Integer receiveUserType = replyDto.getReceiveUserType();
+        if (receiveUserType == null) {
+            receiveUserType = headComment.getSendUserType();
+        }
+        if (receiveUserType == null) {
+            throw new IllegalArgumentException("接收用户类型不能为空");
+        }
+        
+        reply.setSendUserType(sendUserType);
+        reply.setReceiveUserType(receiveUserType);
+        reply.setCommentContent(replyDto.getReplyContent());
+        reply.setLikeCount(0);
+        reply.setReplyCount(0);
+        reply.setHeadType(HEAD_TYPE_REPLY);
+        reply.setHeadId(replyDto.getCommentId());
+        reply.setCreatedUserId(replyDto.getUserId());
+        reply.setCreatedTime(new Date());
+        
+        return reply;
+    }
+
+    /**
+     * 检查是否已点赞
+     */
+    private boolean isLiked(Integer commentId, Integer userId, String type) {
+        LambdaQueryWrapper<LifeLikeRecord> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(LifeLikeRecord::getType, type)
+                .eq(LifeLikeRecord::getDianzanId, String.valueOf(userId))
+                .eq(LifeLikeRecord::getHuifuId, String.valueOf(commentId))
+                .eq(LifeLikeRecord::getDeleteFlag, 0);
+        List<LifeLikeRecord> records = lifeLikeRecordMapper.selectList(queryWrapper);
+        return !CollectionUtils.isEmpty(records);
+    }
+
+    /**
+     * 执行点赞评论
+     */
+    private R<Boolean> doLikeComment(Integer commentId, Integer userId) {
+        try {
+            // 插入点赞记录
+            LifeLikeRecord likeRecord = new LifeLikeRecord();
+            likeRecord.setDianzanId(String.valueOf(userId));
+            likeRecord.setHuifuId(String.valueOf(commentId));
+            likeRecord.setType(LIKE_TYPE_COMMENT);
+            likeRecord.setCreatedTime(new Date());
+            likeRecord.setCreatedUserId(userId);
+            lifeLikeRecordMapper.insert(likeRecord);
+
+            // 更新评论点赞数
+            LambdaUpdateWrapper<StoreStaffComment> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StoreStaffComment::getId, commentId);
+            updateWrapper.setSql("like_count = like_count + 1");
+            int result = storeStaffCommentMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                log.info("点赞评论成功, commentId={}", commentId);
+                return R.data(true, "点赞成功");
+            } else {
+                return R.fail("点赞失败");
+            }
+        } catch (Exception e) {
+            log.error("点赞评论异常, commentId={}, userId={}, error={}", commentId, userId, e.getMessage(), e);
+            return R.fail("点赞失败");
+        }
+    }
+
+    /**
+     * 执行取消点赞评论
+     */
+    private R<Boolean> doCancelLikeComment(Integer commentId, Integer userId) {
+        try {
+            // 查询点赞记录
+            LambdaQueryWrapper<LifeLikeRecord> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(LifeLikeRecord::getType, LIKE_TYPE_COMMENT)
+                    .eq(LifeLikeRecord::getDianzanId, String.valueOf(userId))
+                    .eq(LifeLikeRecord::getHuifuId, String.valueOf(commentId))
+                    .eq(LifeLikeRecord::getDeleteFlag, 0);
+            List<LifeLikeRecord> records = lifeLikeRecordMapper.selectList(queryWrapper);
+
+            if (CollectionUtils.isEmpty(records)) {
+                return R.data(true, "未点赞");
+            }
+
+            // 删除点赞记录(逻辑删除)
+            records.forEach(record -> lifeLikeRecordMapper.deleteById(record.getId()));
+
+            // 更新评论点赞数
+            LambdaUpdateWrapper<StoreStaffComment> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StoreStaffComment::getId, commentId);
+            updateWrapper.setSql("like_count = GREATEST(0, like_count - 1)");
+            int result = storeStaffCommentMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                log.info("取消点赞评论成功, commentId={}", commentId);
+                return R.data(true, "取消点赞成功");
+            } else {
+                return R.fail("取消点赞失败");
+            }
+        } catch (Exception e) {
+            log.error("取消点赞评论异常, commentId={}, userId={}, error={}", commentId, userId, e.getMessage(), e);
+            return R.fail("取消点赞失败");
+        }
+    }
+
+    /**
+     * 更新首评的回复数
+     */
+    private void updateHeadCommentReplyCount(Integer headId, int delta) {
+        StoreStaffComment headComment = this.getById(headId);
+        if (headComment != null) {
+            Integer currentCount = headComment.getReplyCount() == null ? 0 : headComment.getReplyCount();
+            headComment.setReplyCount(Math.max(0, currentCount + delta));
+            this.updateById(headComment);
+        }
+    }
+
+    /**
+     * 更新评价的评论数
+     */
+    private void updateReviewCommentCount(Integer reviewId, int delta) {
+        StoreStaffReview review = storeStaffReviewService.getById(reviewId);
+        if (review != null) {
+            Integer currentCount = review.getCommentCount() == null ? 0 : review.getCommentCount();
+            review.setCommentCount(Math.max(0, currentCount + delta));
+            storeStaffReviewService.updateById(review);
+            log.debug("更新评价评论数, reviewId={}, delta={}, 当前评论数={}", reviewId, delta, review.getCommentCount());
+        }
+    }
+}

Diff do ficheiro suprimidas por serem muito extensas
+ 969 - 470
alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffConfigServiceImpl.java


+ 523 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffReviewServiceImpl.java

@@ -0,0 +1,523 @@
+package shop.alien.store.service.impl;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeLikeRecord;
+import shop.alien.entity.store.StoreStaffComment;
+import shop.alien.entity.store.StoreStaffReview;
+import shop.alien.entity.store.dto.StoreStaffReviewDto;
+import shop.alien.entity.store.vo.StoreStaffCommentVo;
+import shop.alien.entity.store.vo.StoreStaffReviewDetailVo;
+import shop.alien.entity.store.vo.StoreStaffReviewListWithStaffVo;
+import shop.alien.entity.store.vo.StoreStaffReviewVo;
+import shop.alien.entity.store.StoreStaffConfig;
+import shop.alien.mapper.LifeLikeRecordMapper;
+import shop.alien.mapper.StoreStaffCommentMapper;
+import shop.alien.mapper.StoreStaffConfigMapper;
+import shop.alien.mapper.StoreStaffReviewMapper;
+import shop.alien.store.service.StoreStaffCommentService;
+import shop.alien.store.service.StoreStaffReviewService;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 员工评价 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Transactional
+@Service
+@RequiredArgsConstructor
+public class StoreStaffReviewServiceImpl extends ServiceImpl<StoreStaffReviewMapper, StoreStaffReview> implements StoreStaffReviewService {
+
+    private static final String LIKE_TYPE_REVIEW = "9"; // 员工评价点赞类型
+
+    private final StoreStaffReviewMapper storeStaffReviewMapper;
+    private final StoreStaffCommentService storeStaffCommentService;
+    private final StoreStaffCommentMapper storeStaffCommentMapper;
+    private final LifeLikeRecordMapper lifeLikeRecordMapper;
+    private final StoreStaffConfigMapper storeStaffConfigMapper;
+
+    @Override
+    public R<StoreStaffReview> createReview(StoreStaffReviewDto reviewDto) {
+        log.info("创建员工评价, reviewDto={}", reviewDto);
+
+        // 参数校验
+        R<String> validateResult = validateReviewDto(reviewDto);
+        if (!validateResult.isSuccess()) {
+            return R.fail(validateResult.getMsg());
+        }
+
+        // 创建评价
+        StoreStaffReview review = buildReviewFromDto(reviewDto);
+        boolean success = this.save(review);
+        
+        if (success) {
+            log.info("创建评价成功, 评价ID={}", review.getId());
+            // 更新员工评分
+            updateStaffServiceScore(review.getStaffUserId());
+            return R.data(review, "提交成功");
+        } else {
+            log.error("创建评价失败");
+            return R.fail("创建评价失败");
+        }
+    }
+
+    @Override
+    public R<IPage<StoreStaffReviewVo>> getReviewList(int pageNum, int pageSize, Integer staffUserId, Integer userId, Integer currentUserId) {
+        log.info("分页查询评价列表, pageNum={}, pageSize={}, staffUserId={}, userId={}, currentUserId={}",
+                pageNum, pageSize, staffUserId, userId, currentUserId);
+
+        Page<StoreStaffReviewVo> page = new Page<>(pageNum, pageSize);
+        IPage<StoreStaffReviewVo> result = storeStaffReviewMapper.getReviewListWithUser(page, staffUserId, userId, currentUserId);
+
+        // 处理评价图片JSON字符串转换为列表
+        result.getRecords().forEach(this::parseReviewImages);
+
+        return R.data(result);
+    }
+
+    @Override
+    public R<StoreStaffReviewDetailVo> getReviewDetail(Integer reviewId, Integer currentUserId) {
+        log.info("获取评价详情, reviewId={}, currentUserId={}", reviewId, currentUserId);
+
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+
+        // 查询评价详情
+        StoreStaffReviewVo reviewVo = storeStaffReviewMapper.getReviewDetailById(reviewId, currentUserId);
+        if (reviewVo == null) {
+            return R.fail("评价不存在");
+        }
+
+        // 处理评价图片
+        parseReviewImages(reviewVo);
+
+        // 查询评论列表
+        List<StoreStaffCommentVo> comments = storeStaffCommentMapper.getCommentListByReviewId(reviewId, currentUserId);
+
+        // 为每个评论查询回复列表
+        if (!CollectionUtils.isEmpty(comments)) {
+            comments.forEach(comment -> {
+                List<StoreStaffCommentVo> replies = storeStaffCommentMapper.getReplyListByHeadId(comment.getId(), currentUserId);
+                comment.setReplyList(replies != null ? replies : new ArrayList<>());
+            });
+        }
+
+        // 查询人员信息
+        StoreStaffConfig staffInfo = null;
+        if (reviewVo.getStaffUserId() != null) {
+            try {
+                staffInfo = storeStaffConfigMapper.selectById(reviewVo.getStaffUserId());
+            } catch (Exception e) {
+                log.warn("查询人员信息失败, staffUserId={}, error={}", reviewVo.getStaffUserId(), e.getMessage());
+            }
+        }
+
+        // 构建返回结果
+        StoreStaffReviewDetailVo detailVo = new StoreStaffReviewDetailVo();
+        detailVo.setReview(reviewVo);
+        detailVo.setCommentList(comments != null ? comments : new ArrayList<>());
+        detailVo.setStaffInfo(staffInfo);
+
+        return R.data(detailVo);
+    }
+
+    @Override
+    public R<Boolean> likeReview(Integer reviewId, Integer userId) {
+        log.info("点赞评价, reviewId={}, userId={}", reviewId, userId);
+
+        // 参数校验
+        R<String> validateResult = validateLikeParams(reviewId, userId);
+        if (!validateResult.isSuccess()) {
+            return R.fail(validateResult.getMsg());
+        }
+
+        // 验证评价是否存在
+        StoreStaffReview review = this.getById(reviewId);
+        if (review == null || review.getDeleteFlag() == 1) {
+            return R.fail("评价不存在或已删除");
+        }
+
+        // 检查是否已点赞
+        if (isLiked(reviewId, userId, LIKE_TYPE_REVIEW)) {
+            return R.data(true, "已点赞");
+        }
+
+        // 执行点赞
+        return doLike(reviewId, userId, LIKE_TYPE_REVIEW, review);
+    }
+
+    @Override
+    public R<Boolean> cancelLikeReview(Integer reviewId, Integer userId) {
+        log.info("取消点赞评价, reviewId={}, userId={}", reviewId, userId);
+
+        // 参数校验
+        R<String> validateResult = validateLikeParams(reviewId, userId);
+        if (!validateResult.isSuccess()) {
+            return R.fail(validateResult.getMsg());
+        }
+
+        // 执行取消点赞
+        return doCancelLike(reviewId, userId, LIKE_TYPE_REVIEW, StoreStaffReview.class);
+    }
+
+    @Override
+    public R<Boolean> deleteReview(Integer reviewId, Integer userId) {
+        log.info("用户删除评价, reviewId={}, userId={}", reviewId, userId);
+
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+
+        // 查询评价
+        StoreStaffReview review = this.getById(reviewId);
+        if (review == null) {
+            return R.fail("评价不存在");
+        }
+
+        // 验证是否为评价用户
+        if (!review.getUserId().equals(userId)) {
+            return R.fail("只能删除自己的评价");
+        }
+
+        return deleteReviewInternal(reviewId, userId, review);
+    }
+
+    @Override
+    public R<Boolean> deleteReviewByAdmin(Integer reviewId) {
+        log.info("管理员删除评价, reviewId={}", reviewId);
+
+        if (reviewId == null) {
+            return R.fail("评价ID不能为空");
+        }
+
+        // 查询评价
+        StoreStaffReview review = this.getById(reviewId);
+        if (review == null) {
+            return R.fail("评价不存在");
+        }
+
+        return deleteReviewInternal(reviewId, null, review);
+    }
+
+    /**
+     * 校验评价DTO参数
+     */
+    private R<String> validateReviewDto(StoreStaffReviewDto reviewDto) {
+        if (reviewDto == null) {
+            return R.fail("评价信息不能为空");
+        }
+        if (reviewDto.getUserId() == null) {
+            return R.fail("用户ID不能为空");
+        }
+        if (reviewDto.getStaffUserId() == null) {
+            return R.fail("员工用户ID不能为空");
+        }
+        return R.success("校验评论参数成功");
+    }
+
+    /**
+     * 构建评价对象
+     */
+    private StoreStaffReview buildReviewFromDto(StoreStaffReviewDto reviewDto) {
+        StoreStaffReview review = new StoreStaffReview();
+        review.setUserId(reviewDto.getUserId());
+        review.setStaffUserId(reviewDto.getStaffUserId());
+        review.setOverallRating(reviewDto.getOverallRating());
+        review.setServiceAttitudeRating(reviewDto.getServiceAttitudeRating());
+        review.setResponseTimeRating(reviewDto.getResponseTimeRating());
+        review.setProfessionalAbilityRating(reviewDto.getProfessionalAbilityRating());
+        review.setReviewContent(reviewDto.getReviewContent());
+        review.setIsAnonymous(reviewDto.getIsAnonymous() != null ? reviewDto.getIsAnonymous() : 0);
+        review.setLikeCount(0);
+        review.setCommentCount(0);
+        review.setCreatedUserId(reviewDto.getUserId());
+        review.setCreatedTime(new Date());
+
+        // 处理评价图片
+        if (reviewDto.getReviewImages() != null && !reviewDto.getReviewImages().isEmpty()) {
+            JSONArray jsonArray = new JSONArray();
+            jsonArray.addAll(reviewDto.getReviewImages());
+            review.setReviewImages(jsonArray.toString());
+        }
+
+        return review;
+    }
+
+    /**
+     * 解析评价图片JSON字符串为列表
+     */
+    private void parseReviewImages(StoreStaffReviewVo vo) {
+        if (vo.getReviewImagesJson() != null && !vo.getReviewImagesJson().trim().isEmpty()) {
+            try {
+                List<String> images = JSON.parseArray(vo.getReviewImagesJson(), String.class);
+                vo.setReviewImages(images != null ? images : new ArrayList<>());
+            } catch (Exception e) {
+                log.warn("解析评价图片失败, reviewId={}, error={}", vo.getId(), e.getMessage());
+                vo.setReviewImages(new ArrayList<>());
+            }
+        } else {
+            vo.setReviewImages(new ArrayList<>());
+        }
+    }
+
+    /**
+     * 校验点赞参数
+     */
+    private R<String> validateLikeParams(Integer id, Integer userId) {
+        if (id == null) {
+            return R.fail("ID不能为空");
+        }
+        if (userId == null) {
+            return R.fail("用户ID不能为空");
+        }
+        return R.success("校验评论参数成功");
+    }
+
+    /**
+     * 检查是否已点赞
+     */
+    private boolean isLiked(Integer targetId, Integer userId, String type) {
+        LambdaQueryWrapper<LifeLikeRecord> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(LifeLikeRecord::getType, type)
+                .eq(LifeLikeRecord::getDianzanId, String.valueOf(userId))
+                .eq(LifeLikeRecord::getHuifuId, String.valueOf(targetId))
+                .eq(LifeLikeRecord::getDeleteFlag, 0);
+        List<LifeLikeRecord> records = lifeLikeRecordMapper.selectList(queryWrapper);
+        return !CollectionUtils.isEmpty(records);
+    }
+
+    /**
+     * 执行点赞操作
+     */
+    private R<Boolean> doLike(Integer reviewId, Integer userId, String type, StoreStaffReview review) {
+        try {
+            // 插入点赞记录
+            LifeLikeRecord likeRecord = new LifeLikeRecord();
+            likeRecord.setDianzanId(String.valueOf(userId));
+            likeRecord.setHuifuId(String.valueOf(reviewId));
+            likeRecord.setType(type);
+            likeRecord.setCreatedTime(new Date());
+            likeRecord.setCreatedUserId(userId);
+            lifeLikeRecordMapper.insert(likeRecord);
+
+            // 更新点赞数
+            LambdaUpdateWrapper<StoreStaffReview> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StoreStaffReview::getId, reviewId);
+            updateWrapper.setSql("like_count = like_count + 1");
+            int result = storeStaffReviewMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                log.info("点赞成功, reviewId={}", reviewId);
+                return R.data(true, "点赞成功");
+            } else {
+                return R.fail("点赞失败");
+            }
+        } catch (Exception e) {
+            log.error("点赞操作异常, reviewId={}, userId={}, error={}", reviewId, userId, e.getMessage(), e);
+            return R.fail("点赞失败");
+        }
+    }
+
+    /**
+     * 执行取消点赞操作
+     */
+    private <T> R<Boolean> doCancelLike(Integer targetId, Integer userId, String type, Class<T> entityClass) {
+        try {
+            // 查询点赞记录
+            LambdaQueryWrapper<LifeLikeRecord> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(LifeLikeRecord::getType, type)
+                    .eq(LifeLikeRecord::getDianzanId, String.valueOf(userId))
+                    .eq(LifeLikeRecord::getHuifuId, String.valueOf(targetId))
+                    .eq(LifeLikeRecord::getDeleteFlag, 0);
+            List<LifeLikeRecord> records = lifeLikeRecordMapper.selectList(queryWrapper);
+
+            if (CollectionUtils.isEmpty(records)) {
+                return R.data(true, "未点赞");
+            }
+
+            // 删除点赞记录(逻辑删除)
+            records.forEach(record -> lifeLikeRecordMapper.deleteById(record.getId()));
+
+            // 更新点赞数
+            if (entityClass == StoreStaffReview.class) {
+                LambdaUpdateWrapper<StoreStaffReview> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.eq(StoreStaffReview::getId, targetId);
+                updateWrapper.setSql("like_count = GREATEST(0, like_count - 1)");
+                storeStaffReviewMapper.update(null, updateWrapper);
+            }
+
+            log.info("取消点赞成功, targetId={}", targetId);
+            return R.data(true, "取消点赞成功");
+        } catch (Exception e) {
+            log.error("取消点赞操作异常, targetId={}, userId={}, error={}", targetId, userId, e.getMessage(), e);
+            return R.fail("取消点赞失败");
+        }
+    }
+
+    /**
+     * 内部删除评价方法
+     */
+    private R<Boolean> deleteReviewInternal(Integer reviewId, Integer userId, StoreStaffReview review) {
+        // 查询该评价下的所有评论
+        LambdaQueryWrapper<StoreStaffComment> commentWrapper = new LambdaQueryWrapper<>();
+        commentWrapper.eq(StoreStaffComment::getReviewId, reviewId)
+                .eq(StoreStaffComment::getDeleteFlag, 0);
+        List<StoreStaffComment> comments = storeStaffCommentService.list(commentWrapper);
+
+        // 保存员工ID,用于后续更新评分
+        Integer staffUserId = review.getStaffUserId();
+
+        // 删除评价(逻辑删除)
+        int num = storeStaffReviewMapper.deleteById(reviewId);
+
+        if (num > 0) {
+            // 级联删除该评价下的所有评论和回复
+            comments.forEach(comment -> deleteCommentAndReplies(comment, userId));
+
+            // 更新员工评分
+            updateStaffServiceScore(staffUserId);
+
+            if (userId != null) {
+                log.info("用户删除评价成功, reviewId={}, userId={}", reviewId, userId);
+            } else {
+                log.info("管理员删除评价成功, reviewId={}", reviewId);
+            }
+            return R.data(true, "删除成功");
+        } else {
+            log.error("删除评价失败, reviewId={}", reviewId);
+            return R.fail("删除评价失败");
+        }
+    }
+
+    /**
+     * 更新员工服务评分
+     * 计算公式:员工评分 = overallRating平均值 (保留1位小数)
+     *
+     * @param staffUserId 员工用户ID
+     */
+    private void updateStaffServiceScore(Integer staffUserId) {
+        if (staffUserId == null) {
+            log.warn("更新员工评分失败:员工ID为空");
+            return;
+        }
+
+        try {
+            // 计算平均评分(1-5星)
+            BigDecimal averageRating = storeStaffReviewMapper.getAverageRatingByStaffUserId(staffUserId);
+
+            BigDecimal serviceScore;
+            if (averageRating != null) {
+                // 保留1位小数,四舍五入
+                serviceScore = averageRating.setScale(1, RoundingMode.HALF_UP);
+                // 确保在0-5范围内
+                if (serviceScore.compareTo(BigDecimal.ZERO) < 0) {
+                    serviceScore = BigDecimal.ZERO;
+                } else if (serviceScore.compareTo(new BigDecimal("5.0")) > 0) {
+                    serviceScore = new BigDecimal("5.0");
+                }
+            } else {
+                // 如果没有评价,设置为0.0
+                serviceScore = BigDecimal.ZERO;
+                log.info("员工暂无评价,将评分设置为0.0, 员工ID={}", staffUserId);
+            }
+
+            // 更新员工评分
+            LambdaUpdateWrapper<StoreStaffConfig> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StoreStaffConfig::getId, staffUserId);
+            updateWrapper.set(StoreStaffConfig::getServiceScore, serviceScore);
+            int result = storeStaffConfigMapper.update(null, updateWrapper);
+
+            if (result > 0) {
+                log.info("更新员工评分成功, 员工ID={}, 平均评分={}, 服务评分={}", staffUserId, averageRating, serviceScore);
+            } else {
+                log.warn("更新员工评分失败,员工ID={}", staffUserId);
+            }
+        } catch (Exception e) {
+            log.error("更新员工评分异常,员工ID={}, 错误信息={}", staffUserId, e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 删除评论及其回复
+     */
+    private void deleteCommentAndReplies(StoreStaffComment comment, Integer userId) {
+        // 删除评论下的所有回复(逻辑删除)
+        LambdaUpdateWrapper<StoreStaffComment> replyUpdateWrapper = new LambdaUpdateWrapper<>();
+        replyUpdateWrapper.eq(StoreStaffComment::getHeadId, comment.getId())
+                .eq(StoreStaffComment::getHeadType, 1) // 1表示回复类型
+                .eq(StoreStaffComment::getDeleteFlag, 0);
+        replyUpdateWrapper.set(StoreStaffComment::getDeleteFlag, 1);
+        if (userId != null) {
+            replyUpdateWrapper.set(StoreStaffComment::getUpdatedUserId, userId);
+        }
+        replyUpdateWrapper.set(StoreStaffComment::getUpdatedTime, new Date());
+        storeStaffCommentService.update(replyUpdateWrapper);
+
+        // 删除评论
+        comment.setDeleteFlag(1);
+        if (userId != null) {
+            comment.setUpdatedUserId(userId);
+        }
+        comment.setUpdatedTime(new Date());
+        storeStaffCommentService.updateById(comment);
+    }
+
+    @Override
+    public R<StoreStaffReviewListWithStaffVo> getReviewListByStaffId(int pageNum, int pageSize, Integer staffUserId, Integer currentUserId) {
+        log.info("根据人员ID查询评价列表, pageNum={}, pageSize={}, staffUserId={}, currentUserId={}",
+                pageNum, pageSize, staffUserId, currentUserId);
+
+        if (staffUserId == null) {
+            return R.fail("人员ID不能为空");
+        }
+
+        // 查询人员信息
+        StoreStaffConfig staffInfo = null;
+        try {
+            staffInfo = storeStaffConfigMapper.selectById(staffUserId);
+            if (staffInfo == null) {
+                log.warn("人员信息不存在, staffUserId={}", staffUserId);
+            }
+        } catch (Exception e) {
+            log.warn("查询人员信息失败, staffUserId={}, error={}", staffUserId, e.getMessage());
+        }
+
+        // 查询评价列表
+        Page<StoreStaffReviewVo> page = new Page<>(pageNum, pageSize);
+        IPage<StoreStaffReviewVo> reviewList = storeStaffReviewMapper.getReviewListWithUser(page, staffUserId, null, currentUserId);
+
+        // 处理评价图片JSON字符串转换为列表
+        reviewList.getRecords().forEach(this::parseReviewImages);
+
+        // 构建返回结果
+        StoreStaffReviewListWithStaffVo result = new StoreStaffReviewListWithStaffVo();
+        result.setStaffInfo(staffInfo);
+        result.setReviewList(reviewList);
+
+        return R.data(result);
+    }
+}

+ 315 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffTitleServiceImpl.java

@@ -0,0 +1,315 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.StoreStaffTitle;
+import shop.alien.mapper.StoreStaffTitleMapper;
+import shop.alien.store.service.StoreStaffTitleService;
+import shop.alien.store.util.CommonConstant;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 员工标题服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class StoreStaffTitleServiceImpl extends ServiceImpl<StoreStaffTitleMapper, StoreStaffTitle>
+        implements StoreStaffTitleService {
+
+    private final StoreStaffTitleMapper storeStaffTitleMapper;
+
+    /**
+     * 分页查询员工标题列表
+     *
+     * @param page    分页页数,必须大于0
+     * @param size    分页条数,必须大于0且不超过100
+     * @param storeId 店铺ID,可选
+     * @return 员工标题列表分页结果
+     */
+    @Override
+    public IPage<StoreStaffTitle> queryStaffTitleList(Integer page, Integer size, Integer storeId) {
+        log.info("查询员工标题列表,参数:page={}, size={}, storeId={}", page, size, storeId);
+
+        // 参数校验
+        if (page == null || page < 1) {
+            log.warn("查询员工标题列表失败,分页页数无效:page={}", page);
+            throw new IllegalArgumentException("分页页数不能为空且必须大于0");
+        }
+        if (size == null || size < 1) {
+            log.warn("查询员工标题列表失败,分页条数无效:size={}", size);
+            throw new IllegalArgumentException("分页条数不能为空且必须大于0");
+        }
+        if (size > CommonConstant.MAX_PAGE_SIZE) {
+            log.warn("查询员工标题列表失败,分页条数超过最大值:size={}", size);
+            throw new IllegalArgumentException("分页条数不能超过" + CommonConstant.MAX_PAGE_SIZE);
+        }
+
+        try {
+            // 构建分页对象
+            IPage<StoreStaffTitle> staffTitlePage = new Page<>(page, size);
+
+            // 构建查询条件
+            LambdaQueryWrapper<StoreStaffTitle> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(StoreStaffTitle::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE);
+
+            // 如果指定了店铺ID,则按店铺ID过滤
+            if (storeId != null && storeId > 0) {
+                queryWrapper.eq(StoreStaffTitle::getStoreId, storeId);
+            }
+
+            // 按创建时间降序排序
+            queryWrapper.orderByDesc(StoreStaffTitle::getCreatedTime);
+
+            // 执行查询
+            IPage<StoreStaffTitle> result = storeStaffTitleMapper.selectPage(staffTitlePage, queryWrapper);
+
+            log.info("查询员工标题列表成功,共{}条记录,当前页{}条",
+                    result.getTotal(), result.getRecords() != null ? result.getRecords().size() : 0);
+            return result;
+        } catch (Exception e) {
+            log.error("查询员工标题列表异常,异常信息:{}", e.getMessage(), e);
+            throw new RuntimeException("查询员工标题列表失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 新增员工标题
+     *
+     * @param storeStaffTitle 员工标题信息
+     * @return 新增结果,成功返回1,失败返回0
+     */
+    @Override
+    public Integer addStaffTitle(StoreStaffTitle storeStaffTitle) {
+        log.info("新增员工标题,参数:{}", storeStaffTitle);
+
+        // 参数校验
+        if (storeStaffTitle == null) {
+            log.warn("新增员工标题失败,参数为空");
+            throw new IllegalArgumentException("员工标题信息不能为空");
+        }
+        if (StringUtils.isEmpty(storeStaffTitle.getTitleName())) {
+            log.warn("新增员工标题失败,标题名称为空");
+            throw new IllegalArgumentException("标题名称不能为空");
+        }
+
+        try {
+            // 自动计算员工数量
+            int staffCount = calculateStaffCount(storeStaffTitle.getStaffIds());
+            storeStaffTitle.setStaffCount(staffCount);
+
+            // 设置创建时间
+            storeStaffTitle.setCreatedTime(new Date());
+            storeStaffTitle.setUpdatedTime(new Date());
+
+            // 执行新增
+            int result = storeStaffTitleMapper.insert(storeStaffTitle);
+
+            if (result > 0) {
+                log.info("新增员工标题成功,id={},标题名称={},员工数量={}",
+                        storeStaffTitle.getId(), storeStaffTitle.getTitleName(), staffCount);
+            } else {
+                log.warn("新增员工标题失败,插入记录数为0");
+            }
+
+            return result;
+        } catch (Exception e) {
+            log.error("新增员工标题异常,异常信息:{}", e.getMessage(), e);
+            throw new RuntimeException("新增员工标题失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 更新员工标题
+     *
+     * @param storeStaffTitle 员工标题信息
+     * @return 更新结果,成功返回1,失败返回0
+     */
+    @Override
+    public Integer updateStaffTitle(StoreStaffTitle storeStaffTitle) {
+        log.info("更新员工标题,参数:{}", storeStaffTitle);
+
+        // 参数校验
+        if (storeStaffTitle == null) {
+            log.warn("更新员工标题失败,参数为空");
+            throw new IllegalArgumentException("员工标题信息不能为空");
+        }
+        if (storeStaffTitle.getId() == null || storeStaffTitle.getId() <= 0) {
+            log.warn("更新员工标题失败,ID无效:id={}", storeStaffTitle.getId());
+            throw new IllegalArgumentException("员工标题ID不能为空且必须大于0");
+        }
+        if (StringUtils.isEmpty(storeStaffTitle.getTitleName())) {
+            log.warn("更新员工标题失败,标题名称为空");
+            throw new IllegalArgumentException("标题名称不能为空");
+        }
+
+        try {
+            // 如果更新了员工IDs,重新计算员工数量
+            if (StringUtils.isNotEmpty(storeStaffTitle.getStaffIds())) {
+                int staffCount = calculateStaffCount(storeStaffTitle.getStaffIds());
+                storeStaffTitle.setStaffCount(staffCount);
+            }
+
+            // 设置更新时间
+            storeStaffTitle.setUpdatedTime(new Date());
+
+            // 执行更新
+            int result = storeStaffTitleMapper.updateById(storeStaffTitle);
+
+            if (result > 0) {
+                log.info("更新员工标题成功,id={},标题名称={}",
+                        storeStaffTitle.getId(), storeStaffTitle.getTitleName());
+            } else {
+                log.warn("更新员工标题失败,更新记录数为0,id={}", storeStaffTitle.getId());
+            }
+
+            return result;
+        } catch (Exception e) {
+            log.error("更新员工标题异常,id={},异常信息:{}", storeStaffTitle.getId(), e.getMessage(), e);
+            throw new RuntimeException("更新员工标题失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 删除员工标题(逻辑删除)
+     *
+     * @param id 员工标题ID,必须大于0
+     * @return 删除结果,成功返回1,失败返回0
+     */
+    @Override
+    public Integer deleteStaffTitle(Integer id) {
+        log.info("删除员工标题,参数:id={}", id);
+
+        // 参数校验
+        if (id == null || id <= 0) {
+            log.warn("删除员工标题失败,ID无效:id={}", id);
+            throw new IllegalArgumentException("员工标题ID不能为空且必须大于0");
+        }
+
+        try {
+            // 先查询标题信息,检查是否有员工
+            StoreStaffTitle title = storeStaffTitleMapper.selectById(id);
+            if (title == null) {
+                log.warn("删除员工标题失败,标题不存在:id={}", id);
+                throw new IllegalArgumentException("员工标题不存在");
+            }
+
+            // 校验:如果标题下有员工,不能删除
+            if (title.getStaffCount() != null && title.getStaffCount() > 0) {
+                log.warn("删除员工标题失败,标题下还有员工,不能删除:id={},员工数量={}", id, title.getStaffCount());
+                throw new IllegalArgumentException("该标题下还有员工,无法删除。请先移除标题下的所有员工后再删除");
+            }
+
+            // 双重检查:如果staffIds不为空,也不能删除
+            if (StringUtils.isNotEmpty(title.getStaffIds())) {
+                // 检查staffIds中是否有有效的员工ID
+                List<String> idList = Arrays.stream(title.getStaffIds().split(","))
+                        .map(String::trim)
+                        .filter(s -> !s.isEmpty())
+                        .collect(Collectors.toList());
+                if (!idList.isEmpty()) {
+                    log.warn("删除员工标题失败,标题下还有员工,不能删除:id={},员工IDs={}", id, title.getStaffIds());
+                    throw new IllegalArgumentException("该标题下还有员工,无法删除。请先移除标题下的所有员工后再删除");
+                }
+            }
+
+            // 逻辑删除(MyBatis-Plus会自动处理@TableLogic注解)
+            int result = storeStaffTitleMapper.deleteById(id);
+
+            if (result > 0) {
+                log.info("删除员工标题成功,id={}", id);
+            } else {
+                log.warn("删除员工标题失败,删除记录数为0,id={}", id);
+            }
+
+            return result;
+        } catch (IllegalArgumentException e) {
+            // 重新抛出IllegalArgumentException,让Controller层处理
+            throw e;
+        } catch (Exception e) {
+            log.error("删除员工标题异常,id={},异常信息:{}", id, e.getMessage(), e);
+            throw new RuntimeException("删除员工标题失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 查询员工标题详情
+     *
+     * @param storeId 门店ID,必须大于0
+     * @return 员工标题详情,如果不存在则返回null
+     */
+    @Override
+    public StoreStaffTitle getStaffTitleDetail(Integer storeId) {
+        log.info("查询员工标题详情,参数:storeId={}", storeId);
+
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            log.warn("查询员工标题详情失败,门店ID无效:storeId={}", storeId);
+            throw new IllegalArgumentException("门店ID不能为空且必须大于0");
+        }
+
+        try {
+            // 根据门店ID查询标题(一个门店只能有一个标题)
+            LambdaQueryWrapper<StoreStaffTitle> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(StoreStaffTitle::getStoreId, storeId)
+                    .eq(StoreStaffTitle::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE)
+                    .orderByDesc(StoreStaffTitle::getCreatedTime)
+                    .last("limit 1");
+            
+            StoreStaffTitle result = storeStaffTitleMapper.selectOne(queryWrapper);
+
+            if (result == null) {
+                log.warn("查询员工标题详情失败,记录不存在:storeId={}", storeId);
+            } else {
+                log.info("查询员工标题详情成功,storeId={},id={},标题名称={}", storeId, result.getId(), result.getTitleName());
+            }
+
+            return result;
+        } catch (Exception e) {
+            log.error("查询员工标题详情异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
+            throw new RuntimeException("查询员工标题详情失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 计算员工数量
+     * <p>
+     * 根据员工IDs字符串(逗号分隔)计算员工数量
+     * </p>
+     *
+     * @param staffIds 员工IDs字符串(逗号分隔)
+     * @return 员工数量
+     */
+    private int calculateStaffCount(String staffIds) {
+        if (StringUtils.isEmpty(staffIds)) {
+            return 0;
+        }
+
+        try {
+            // 按逗号分割,过滤空字符串,计算数量
+            List<String> idList = Arrays.stream(staffIds.split(","))
+                    .map(String::trim)
+                    .filter(s -> !s.isEmpty())
+                    .collect(Collectors.toList());
+
+            return idList.size();
+        } catch (Exception e) {
+            log.error("计算员工数量异常,staffIds={},异常信息:{}", staffIds, e.getMessage(), e);
+            return 0;
+        }
+    }
+}
+

+ 57 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreVideoServiceImpl.java

@@ -0,0 +1,57 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.StoreVideo;
+import shop.alien.mapper.StoreVideoMapper;
+import shop.alien.store.service.StoreVideoService;
+
+import java.util.List;
+
+/**
+ * 门店视频 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Transactional
+@Service
+@RequiredArgsConstructor
+public class StoreVideoServiceImpl extends ServiceImpl<StoreVideoMapper, StoreVideo> implements StoreVideoService {
+
+    /**
+     * 根据门店ID获取视频列表
+     *
+     * @param storeId 门店id
+     * @return 视频列表
+     */
+    @Override
+    public List<StoreVideo> getByStoreId(Integer storeId) {
+        LambdaQueryWrapper<StoreVideo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
+        lambdaQueryWrapper.eq(StoreVideo::getStoreId, storeId)
+                .orderByAsc(StoreVideo::getImgSort);
+        return this.list(lambdaQueryWrapper);
+    }
+
+    /**
+     * 根据门店ID和业务ID获取视频列表
+     *
+     * @param storeId 门店id
+     * @param businessId 业务ID
+     * @return 视频列表
+     */
+    @Override
+    public List<StoreVideo> getByStoreIdAndBusinessId(Integer storeId, Integer businessId) {
+        LambdaQueryWrapper<StoreVideo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
+        lambdaQueryWrapper.eq(StoreVideo::getStoreId, storeId)
+                .eq(StoreVideo::getBusinessId, businessId)
+                .orderByAsc(StoreVideo::getImgSort);
+        return this.list(lambdaQueryWrapper);
+    }
+}
+

+ 22 - 0
alien-store/src/main/java/shop/alien/store/util/CommonConstant.java

@@ -76,6 +76,28 @@ public class CommonConstant {
     public static final Integer DEFAULT_PAGE_NUM = 1;
 
     /**
+     * 最大分页大小(防止查询过多数据)
+     */
+    public static final Integer MAX_PAGE_SIZE = 100;
+
+    /**
+     * 演出审核状态:1-审核通过
+     */
+    public static final Integer PERFORMANCE_REVIEW_STATUS_APPROVED = 1;
+
+    /**
+     * 演出上线状态:1-上线
+     */
+    public static final Integer PERFORMANCE_ONLINE_STATUS_ONLINE = 1;
+
+    /**
+     * 演出频次:0-单次 1-每天定时 2-每周定时
+     */
+    public static final String PERFORMANCE_FREQUENCY_SINGLE = "0";
+    public static final String PERFORMANCE_FREQUENCY_DAILY = "1";
+    public static final String PERFORMANCE_FREQUENCY_WEEKLY = "2";
+
+    /**
      * 点赞类型:1-评论 2-社区动态 3-活动 4-推荐菜 5-店铺打卡 6-二手商品 7-律师评分 8-点赞员工
      */
     public static final String LIKE_TYPE_COMMENT = "1";

+ 146 - 0
alien-store/src/main/java/shop/alien/store/util/ai/AiGetPriceUtil.java

@@ -0,0 +1,146 @@
+package shop.alien.store.util.ai;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.HttpServerErrorException;
+import org.springframework.web.client.RestTemplate;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * AI 价格查询工具类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AiGetPriceUtil {
+
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+    
+    private RestTemplate restTemplate;
+
+    @Value("${ai.service.price-url}")
+    private String priceUrl;
+
+    /**
+     * 初始化 RestTemplate,设置超时时间为 180 秒
+     */
+    @PostConstruct
+    public void initRestTemplate() {
+        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+        // 设置连接超时时间为 180 秒(180000 毫秒)
+        factory.setConnectTimeout(180000);
+        // 设置读取超时时间为 180 秒(180000 毫秒)
+        factory.setReadTimeout(180000);
+        this.restTemplate = new RestTemplate(factory);
+    }
+
+    /**
+     * 根据菜品名称和位置查询价格
+     *
+     * @param dishName 菜品名称
+     * @param location 位置
+     * @return 价格查询响应,包含 code, message, data, details, timestamp
+     */
+    public JSONObject getDishPrice(String dishName, String location) {
+        if (!StringUtils.hasText(dishName)) {
+            log.warn("菜品名称为空,无法查询价格");
+            return buildErrorResponse("菜品名称不能为空");
+        }
+
+        if (!StringUtils.hasText(location)) {
+            log.warn("位置为空,无法查询价格");
+            return buildErrorResponse("位置不能为空");
+        }
+
+        // 获取AI服务访问令牌
+        String accessToken = aiAuthTokenUtil.getAccessToken();
+        if (!StringUtils.hasText(accessToken)) {
+            log.error("获取AI服务访问令牌失败,无法调用价格查询接口");
+            return buildErrorResponse("获取AI服务访问令牌失败");
+        }
+
+        try {
+            // 构建请求体
+            Map<String, Object> requestBody = new HashMap<>();
+            requestBody.put("dish_name", dishName);
+            requestBody.put("location", location);
+
+            // 构建请求头,添加Authorization
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            headers.set("Authorization", "Bearer " + accessToken);
+
+            HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
+
+            log.info("调用AI价格查询接口,菜品名称: {}, 位置: {}", dishName, location);
+            ResponseEntity<String> response = restTemplate.postForEntity(priceUrl, request, String.class);
+
+            if (response != null && response.getStatusCode() == HttpStatus.OK) {
+                String responseBody = response.getBody();
+                log.info("AI价格查询接口响应: {}", responseBody);
+
+                if (StringUtils.hasText(responseBody)) {
+                    JSONObject jsonObject = JSONObject.parseObject(responseBody);
+                    if (jsonObject != null) {
+                        Integer code = jsonObject.getInteger("code");
+                        if (code != null && code == 200) {
+                            log.info("成功查询菜品价格,菜品名称: {}, 位置: {}", dishName, location);
+                            return jsonObject;
+                        } else {
+                            String message = jsonObject.getString("message");
+                            log.warn("AI接口返回错误,code: {}, message: {}", code, message);
+                            return jsonObject;
+                        }
+                    }
+                }
+            } else {
+                log.error("AI价格查询接口调用失败,HTTP状态码: {}", 
+                        response != null ? response.getStatusCode() : "null");
+                return buildErrorResponse("AI价格查询接口调用失败");
+            }
+        } catch (HttpClientErrorException e) {
+            log.error("调用AI价格查询接口失败,HTTP客户端错误,状态码: {}, 响应体: {}, 菜品名称: {}, 位置: {}", 
+                    e.getStatusCode(), e.getResponseBodyAsString(), dishName, location, e);
+            return buildErrorResponse("调用AI价格查询接口失败: " + e.getMessage());
+        } catch (HttpServerErrorException e) {
+            log.error("调用AI价格查询接口失败,HTTP服务器错误,状态码: {}, 响应体: {}, 菜品名称: {}, 位置: {}", 
+                    e.getStatusCode(), e.getResponseBodyAsString(), dishName, location, e);
+            return buildErrorResponse("调用AI价格查询接口失败: " + e.getMessage());
+        } catch (Exception e) {
+            log.error("调用AI价格查询接口异常,菜品名称: {}, 位置: {}", dishName, location, e);
+            return buildErrorResponse("调用AI价格查询接口异常: " + e.getMessage());
+        }
+
+        return buildErrorResponse("价格查询失败");
+    }
+
+    /**
+     * 构建错误响应
+     *
+     * @param message 错误消息
+     * @return 错误响应JSON对象
+     */
+    private JSONObject buildErrorResponse(String message) {
+        JSONObject response = new JSONObject();
+        response.put("code", 500);
+        response.put("message", message);
+        response.put("data", null);
+        response.put("details", null);
+        response.put("timestamp", System.currentTimeMillis() / 1000.0);
+        return response;
+    }
+}
+

+ 5 - 4
alien-store/src/main/resources/bootstrap-dev.yml

@@ -6,15 +6,16 @@ spring:
     nacos:
       #注册中心
       discovery:
-        server-addr: 192.168.2.252:8848
+        server-addr: 8.130.79.5:8848
         username: nacos
-        password: ngfriend198092
+        password: Alien123456
 
       #配置中心
       config:
         enabled: true
-        server-addr: 192.168.2.252:8848
+        refresh-enabled: true
+        server-addr: 8.130.79.5:8848
         username: nacos
-        password: ngfriend198092
+        password: Alien123456
         group: DEFAULT_GROUP
         file-extension: yml

+ 1 - 0
alien-store/src/main/resources/bootstrap-prod.yml

@@ -14,6 +14,7 @@ spring:
       #配置中心
       config:
         enabled: true
+        refresh-enabled: true
         server-addr: localhost:8848
         username: nacos
         password: ngfriend198092

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff