Sfoglia il codice sorgente

Merge remote-tracking branch 'origin/sit-three-categories' into sit-three-categories

liyafei 3 mesi fa
parent
commit
c488c0bf2c
61 ha cambiato i file con 6660 aggiunte e 591 eliminazioni
  1. 11 0
      alien-entity/src/main/java/shop/alien/entity/store/BarPerformance.java
  2. 96 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreStaffComment.java
  3. 15 35
      alien-entity/src/main/java/shop/alien/entity/store/StoreStaffConfig.java
  4. 100 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreStaffReview.java
  5. 103 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreStaffTitle.java
  6. 74 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreVideo.java
  7. 23 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffCommentRequestDto.java
  8. 0 3
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffConfigListQueryDto.java
  9. 35 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffReplyDto.java
  10. 47 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffReviewDto.java
  11. 79 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceDetailVo.java
  12. 49 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceGroupByDateVo.java
  13. 37 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceGroupListVo.java
  14. 60 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceGuestVo.java
  15. 66 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceListVo.java
  16. 54 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PerformanceScheduleVo.java
  17. 50 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StaffTitleGroupVo.java
  18. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOfficialAlbumImgVo.java
  19. 76 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffCommentVo.java
  20. 44 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffDetailWithPerformanceVo.java
  21. 25 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffReviewDetailVo.java
  22. 87 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreStaffReviewVo.java
  23. 54 0
      alien-entity/src/main/java/shop/alien/mapper/StoreStaffCommentMapper.java
  24. 44 0
      alien-entity/src/main/java/shop/alien/mapper/StoreStaffReviewMapper.java
  25. 14 0
      alien-entity/src/main/java/shop/alien/mapper/StoreStaffTitleMapper.java
  26. 16 0
      alien-entity/src/main/java/shop/alien/mapper/StoreVideoMapper.java
  27. 184 0
      alien-entity/src/main/resources/mapper/StoreStaffCommentMapper.xml
  28. 122 0
      alien-entity/src/main/resources/mapper/StoreStaffReviewMapper.xml
  29. 4 2
      alien-second/src/main/java/shop/alien/second/util/AiUserViolationUtils.java
  30. 93 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformDiscussionController.java
  31. 68 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/entity/StorePlatformDiscussion.java
  32. 17 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/mapper/StorePlatformDiscussionMapper.java
  33. 57 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformDiscussionService.java
  34. 191 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformDiscussionServiceImpl.java
  35. 42 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/vo/StorePlatformDiscussionReplyVo.java
  36. 26 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/vo/StorePlatformDiscussionUserVo.java
  37. 170 0
      alien-store-platform/接口文档/36-店铺问答讨论模块接口.md
  38. 197 0
      alien-store/src/main/java/shop/alien/store/controller/PerformanceListController.java
  39. 1 1
      alien-store/src/main/java/shop/alien/store/controller/StoreImgController.java
  40. 2 2
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  41. 14 6
      alien-store/src/main/java/shop/alien/store/controller/StoreOfficialAlbumController.java
  42. 186 0
      alien-store/src/main/java/shop/alien/store/controller/StoreStaffCommentController.java
  43. 161 82
      alien-store/src/main/java/shop/alien/store/controller/StoreStaffConfigController.java
  44. 151 0
      alien-store/src/main/java/shop/alien/store/controller/StoreStaffReviewController.java
  45. 252 0
      alien-store/src/main/java/shop/alien/store/controller/StoreStaffTitleController.java
  46. 176 0
      alien-store/src/main/java/shop/alien/store/controller/StoreVideoController.java
  47. 56 0
      alien-store/src/main/java/shop/alien/store/service/PerformanceListService.java
  48. 5 2
      alien-store/src/main/java/shop/alien/store/service/StoreOfficialAlbumService.java
  49. 88 0
      alien-store/src/main/java/shop/alien/store/service/StoreStaffCommentService.java
  50. 50 0
      alien-store/src/main/java/shop/alien/store/service/StoreStaffConfigService.java
  51. 83 0
      alien-store/src/main/java/shop/alien/store/service/StoreStaffReviewService.java
  52. 73 0
      alien-store/src/main/java/shop/alien/store/service/StoreStaffTitleService.java
  53. 33 0
      alien-store/src/main/java/shop/alien/store/service/StoreVideoService.java
  54. 906 0
      alien-store/src/main/java/shop/alien/store/service/impl/PerformanceListServiceImpl.java
  55. 107 38
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOfficialAlbumServiceImpl.java
  56. 519 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffCommentServiceImpl.java
  57. 575 419
      alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffConfigServiceImpl.java
  58. 414 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffReviewServiceImpl.java
  59. 315 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffTitleServiceImpl.java
  60. 57 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreVideoServiceImpl.java
  61. 2 1
      alien-util/src/main/java/shop/alien/util/common/VideoUtils.java

+ 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;
 }

+ 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;
+}
+

+ 15 - 35
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,41 +117,23 @@ public class StoreStaffConfig extends Model<StoreStaffConfig> {
     @TableField("online_status")
     private Integer onlineStatus;
 
-    @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(exist = false)
-    private List<StoreStaffFitnessCertification> fitnessHonorList;
-
-    @ApiModelProperty(value = "运动健身-从业经历列表")
-    @TableField(exist = false)
-    private List<StoreStaffFitnessExperience> fitnessExperienceList;
-
-    @ApiModelProperty(value = "运动健身-基本信息")
-    @TableField(exist = false)
-    private StoreStaffFitnessBase fitnessBase;
+    // @ApiModelProperty(value = "经营板块id(词典表 键为 business_section)----废弃!")
+    // @TableField("business_section")
+    // private Integer businessSection;
 
     @ApiModelProperty(value = "点赞数量")
     @TableField("like_count")
     private Integer likeCount;
 
+    @ApiModelProperty(value = "标题id")
+    @TableField("title_id")
+    private Integer titleId;
+
     @ApiModelProperty(value = "今日是否有演出(true-有演出,false-无演出)")
     @TableField(exist = false)
     private Boolean hasPerformanceToday;
+
+    @ApiModelProperty(value = "好评数量")
+    @TableField(exist = false)
+    private Integer positiveCommentsCount;
 }

+ 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;
+}
+

+ 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;
+}
+

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

@@ -0,0 +1,25 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+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;
+}
+

+ 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;
+}
+

+ 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);
+}
+

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

@@ -0,0 +1,44 @@
+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);
+}
+

+ 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> {
+}
+

+ 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>
+

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

@@ -0,0 +1,122 @@
+<?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>
+
+</mapper>
+

+ 4 - 2
alien-second/src/main/java/shop/alien/second/util/AiUserViolationUtils.java

@@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.map.HashedMap;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
 import org.springframework.http.*;
 import org.springframework.stereotype.Component;
 import org.springframework.util.LinkedMultiValueMap;
@@ -27,13 +28,14 @@ import java.util.Map;
 @Slf4j
 @Component
 @RequiredArgsConstructor
+@RefreshScope
 public class AiUserViolationUtils {
 
     private final RestTemplate restTemplate;
     private final LifeUserMapper lifeUserMapper;
     private final LifeMessageMapper lifeMessageMapper;
 
-    @Value("${third-party-login.base-url1:http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login}")
+    @Value("${third-party-login.base-url}")
     private String loginUrl;
 
     @Value("${third-party-user-name.base-url:UdUser}")
@@ -42,7 +44,7 @@ public class AiUserViolationUtils {
     @Value("${third-party-pass-word.base-url:123456}")
     private String passWord;
 
-    @Value("${third-party-userComplaintRecordUrl.base-url1:http://192.168.2.250:9000/ai/auto-review/api/v1/user_complaint_record/submit}")
+    @Value("${third-party-userComplaintRecordUrl.base-url}")
     private String userComplaintRecordUrl;
 
 

+ 93 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformDiscussionController.java

@@ -0,0 +1,93 @@
+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;
+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.StorePlatformDiscussion;
+import shop.alien.storeplatform.service.StorePlatformDiscussionService;
+import shop.alien.storeplatform.vo.StorePlatformDiscussionReplyVo;
+import shop.alien.storeplatform.vo.StorePlatformDiscussionUserVo;
+
+import java.util.List;
+
+/**
+ * 商户平台-店铺讨论Controller
+ *
+ * @author alien
+ * @since 2025-12-30
+ */
+@Slf4j
+@Api(tags = {"商户平台-店铺问答讨论"})
+@RestController
+@RequestMapping("/platformStoreDiscussion")
+@RequiredArgsConstructor
+public class StorePlatformDiscussionController {
+
+    private final StorePlatformDiscussionService storePlatformDiscussionService;
+
+    @ApiOperation("发布一级问答讨论 (主贴)")
+    @PostMapping("/postTopic")
+    public R<Boolean> postTopic(@RequestBody StorePlatformDiscussion discussion) {
+        log.info("StorePlatformDiscussionController.postTopic?discussion={}", discussion);
+        try {
+            boolean success = storePlatformDiscussionService.postTopic(discussion);
+            return success ? R.success("发布成功") : R.fail("发布失败");
+        } catch (Exception 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<StorePlatformDiscussionUserVo>> list(@ApiParam(value = "店铺ID", required = true) @RequestParam Integer storeId) {
+        log.info("StorePlatformDiscussionController.list?storeId={}", 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 = storePlatformDiscussionService.removeById(id);
+        return success ? R.success("删除成功") : R.fail("删除失败");
+    }
+}

+ 68 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/entity/StorePlatformDiscussion.java

@@ -0,0 +1,68 @@
+package shop.alien.storeplatform.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 店铺讨论表
+ *
+ * @author alien
+ * @since 2025-12-30
+ */
+@Data
+@TableName("store_discussion")
+@ApiModel(value = "StoreDiscussion对象", description = "店铺讨论表")
+public class StorePlatformDiscussion {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "店铺ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "用户ID")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "讨论内容")
+    @TableField("content")
+    private String content;
+
+    @ApiModelProperty(value = "父级讨论ID (用于回复)")
+    @TableField("parent_id")
+    private Integer parentId;
+
+    @ApiModelProperty(value = "根讨论ID (一级讨论的ID)")
+    @TableField("root_id")
+    private Integer rootId;
+
+    @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;
+}

+ 17 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/mapper/StorePlatformDiscussionMapper.java

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

+ 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);
+}

+ 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;
+}
+

+ 170 - 0
alien-store-platform/接口文档/36-店铺问答讨论模块接口.md

@@ -0,0 +1,170 @@
+# 商户平台-店铺问答讨论模块接口文档
+
+## 模块概述
+
+本模块提供店铺讨论功能,允许用户在商户平台上针对特定店铺发表讨论、回复他人讨论,并支持讨论的列表查询和删除。类似于论坛功能,旨在增强用户与店铺之间的互动。
+
+---
+
+## 接口列表
+
+1. [发表讨论/回复](#接口一发表讨论回复) - 用户发表新讨论或回复已有讨论
+2. [获取店铺讨论列表](#接口二获取店铺讨论列表) - 查询指定店铺的所有讨论记录
+3. [删除讨论](#接口三删除讨论) - 根据ID逻辑删除讨论记录
+
+---
+
+## 接口一:发表讨论/回复
+
+### 接口信息
+
+- **接口名称**: 发表讨论/回复
+- **接口路径**: `POST /platformStoreDiscussion/post`
+- **请求方式**: POST
+- **接口描述**: 用户对店铺发表主贴讨论,或对已有讨论进行回复。
+
+### 请求参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| storeId | Integer | 是 | 店铺ID |
+| userId | Integer | 是 | 用户ID |
+| content | String | 是 | 讨论内容 |
+| parentId | Integer | 否 | 父级讨论ID (发表主贴填0或不传,回复填被回复ID) |
+
+### 请求示例
+
+```json
+{
+    "storeId": 1,
+    "userId": 1001,
+    "content": "这家店的服务非常到位,强烈推荐!",
+    "parentId": 0
+}
+```
+
+### 响应参数
+
+```json
+{
+    "code": 200,
+    "success": true,
+    "data": true,
+    "msg": "发表成功"
+}
+```
+
+---
+
+## 接口二:获取店铺讨论列表
+
+### 接口信息
+
+- **接口名称**: 获取店铺讨论列表
+- **接口路径**: `GET /platformStoreDiscussion/list`
+- **请求方式**: GET
+- **接口描述**: 获取指定店铺下的所有讨论记录,按创建时间倒序排列。
+
+### 请求参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| storeId | Integer | 是 | 店铺ID |
+
+### 请求示例
+
+```http
+GET /platformStoreDiscussion/list?storeId=1
+```
+
+### 响应参数
+
+```json
+{
+    "code": 200,
+    "success": true,
+    "data": [
+        {
+            "id": 1,
+            "storeId": 1,
+            "userId": 1001,
+            "content": "这家店的服务非常到位,强烈推荐!",
+            "parentId": 0,
+            "deleteFlag": 0,
+            "createdTime": "2025-12-30 14:00:00",
+            "createdUserId": 1001,
+            "updatedTime": "2025-12-30 14:00:00",
+            "updatedUserId": null
+        }
+    ],
+    "msg": "操作成功"
+}
+```
+
+---
+
+## 接口三:删除讨论
+
+### 接口信息
+
+- **接口名称**: 删除讨论
+- **接口路径**: `DELETE /platformStoreDiscussion/delete/{id}`
+- **请求方式**: DELETE
+- **接口描述**: 根据讨论ID逻辑删除该条记录。
+
+### 请求参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| id | Integer | 是 | 讨论记录ID (路径参数) |
+
+### 请求示例
+
+```http
+DELETE /platformStoreDiscussion/delete/1
+```
+
+### 响应参数
+
+```json
+{
+    "code": 200,
+    "success": true,
+    "data": true,
+    "msg": "删除成功"
+}
+```
+
+---
+
+## 业务规则说明
+
+1. **父子关系**: 
+   - `parentId` 为 `0` 或 `null` 表示该记录是一条独立的讨论(主贴)。
+   - `parentId` 大于 `0` 表示该记录是对另一条讨论的回复。
+2. **删除逻辑**: 采用逻辑删除,更新 `delete_flag` 字段为 `1`。
+3. **排序规则**: 列表接口默认按 `created_time` 倒序排列,确保最新的讨论显示在最前面。
+
+---
+
+## 数据库设计 (`store_discussion`)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| id | int | 主键,自增 |
+| store_id | int | 店铺ID |
+| user_id | int | 用户ID |
+| content | text | 讨论内容 |
+| parent_id | int | 父级讨论ID,默认为0 |
+| delete_flag | tinyint | 删除标记 (0:未删除, 1:已删除) |
+| created_time | datetime | 创建时间 |
+| created_user_id | int | 创建人ID |
+| updated_time | datetime | 修改时间 |
+| updated_user_id | int | 修改人ID |
+
+---
+
+**文档版本**: v1.0  
+**最后更新**: 2025-12-30  
+**维护人员**: alien
+

+ 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("查询演出详情失败,请稍后重试");
+        }
+    }
+}
+

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

@@ -269,7 +269,7 @@ public class StoreImgController {
     }
 
     /**
-     * 获取封面图片
+     * 获取封面图片/环境
      *storeId 商家id
      * @param imgType 图片类型
      *                0:其他, 1:入口图, 2:相册, 3:菜品, 4:环境, 5:价目表, 6:推荐菜, 7:菜单, 8:用户评论, 9:商家申诉, 10:商家头像, 11:店铺轮播图,20单图,21多图

+ 2 - 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);
         }

+ 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:{},相册名称:{},返回图片数量:{}",

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

@@ -0,0 +1,186 @@
+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)
+    @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", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @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", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", 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", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", 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)
+    @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", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @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", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", 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)
+    @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);
+    }
+}

+ 161 - 82
alien-store/src/main/java/shop/alien/store/controller/StoreStaffConfigController.java

@@ -1,27 +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
@@ -47,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)
     })
@@ -138,6 +136,42 @@ public class StoreStaffConfigController {
 
 
     /**
+     * 查询员工职位列表(商家端)
+     *
+     * @param storeId 店铺ID,必填
+     * @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查询员工列表,支持按状态和职位筛选,返回结果包含员工基本信息和今日是否有演出标识
@@ -166,7 +200,7 @@ 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={}", 
+        log.info("查询员工列表,参数:page={}, size={}, storeId={}, status={}, staffPosition={}",
                 page, size, storeId, status, staffPosition);
 
         try {
@@ -202,8 +236,8 @@ public class StoreStaffConfigController {
             // 调用服务层查询
             IPage<StoreStaffConfig> result = storeStaffConfigService.queryStaffList(
                     normalizedPage, normalizedSize, storeId, status, staffPosition);
-            
-            log.info("查询员工列表成功,店铺ID={},共{}条记录,当前页{}条", 
+
+            log.info("查询员工列表成功,店铺ID={},共{}条记录,当前页{}条",
                     storeId, result.getTotal(), result.getRecords() != null ? result.getRecords().size() : 0);
             return R.data(result);
         } catch (IllegalArgumentException e) {
@@ -217,100 +251,145 @@ public class StoreStaffConfigController {
 
     /**
      * 员工详情查询接口(用户端)
+     * <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)

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

@@ -0,0 +1,151 @@
+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.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)
+    @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", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @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", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", 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", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", 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)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "页容(默认10)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "staffUserId", value = "员工用户ID", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "userId", value = "评价用户ID", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(用于判断是否已点赞)", dataType = "int", paramType = "query")
+    })
+    @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("用户删除评价(只能删除自己的评价,删除评价时,会级联删除该评价下的所有评论和回复)")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID(必填,只能删除自己的评价)", dataType = "int", 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("管理员删除评价(可以删除任何评价,删除评价时,会级联删除该评价下的所有评论和回复)")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "reviewId", value = "评价ID", dataType = "int", 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);
+    }
+}

+ 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);
+}
+

+ 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);
 
     /**
      * 获取官方相册名称列表(客户端)

+ 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);
 }

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

@@ -0,0 +1,83 @@
+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.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);
+}
+

+ 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;
+        }
+    }
+}
+

+ 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;
     }
 

+ 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());
+        }
+    }
+}

File diff suppressed because it is too large
+ 575 - 419
alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffConfigServiceImpl.java


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

@@ -0,0 +1,414 @@
+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.StoreStaffReviewVo;
+import shop.alien.mapper.LifeLikeRecordMapper;
+import shop.alien.mapper.StoreStaffCommentMapper;
+import shop.alien.mapper.StoreStaffReviewMapper;
+import shop.alien.store.service.StoreStaffCommentService;
+import shop.alien.store.service.StoreStaffReviewService;
+
+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;
+
+    @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());
+            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<>());
+            });
+        }
+
+        // 构建返回结果
+        StoreStaffReviewDetailVo detailVo = new StoreStaffReviewDetailVo();
+        detailVo.setReview(reviewVo);
+        detailVo.setCommentList(comments != null ? comments : new ArrayList<>());
+
+        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);
+
+        // 删除评价(逻辑删除)
+        int num = storeStaffReviewMapper.deleteById(reviewId);
+
+        if (num > 0) {
+            // 级联删除该评价下的所有评论和回复
+            comments.forEach(comment -> deleteCommentAndReplies(comment, userId));
+
+            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("删除评价失败");
+        }
+    }
+
+    /**
+     * 删除评论及其回复
+     */
+    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);
+    }
+}

+ 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);
+    }
+}
+

+ 2 - 1
alien-util/src/main/java/shop/alien/util/common/VideoUtils.java

@@ -40,7 +40,8 @@ public class VideoUtils {
         // 如果为本地测试,ffmpegPath地址需求修改为本地安装程序的地址(环境变量不好用)
         String ffmpegPath = "ffmpeg";
         if ("windows".equals(OSUtil.getOsName())) {
-            ffmpegPath = "C:/Program Files (x86)/ffmpeg-6.0/bin/ffmpeg.exe";
+            // ffmpegPath = "C:/Program Files (x86)/ffmpeg-6.0/bin/ffmpeg.exe";
+            ffmpegPath = "D:/project/ext/ffmpeg-6.0/bin/ffmpeg.exe";
         }
 
         // 调用ffmpeg 执行截取命令,需要服务器中安装了ffmpeg并配置了环境变量

Some files were not shown because too many files changed in this diff