5 Angajamente cd6ace95c9 ... 87795c128e

Autor SHA1 Permisiunea de a trimite mesaje. Dacă este dezactivată, utilizatorul nu va putea trimite nici un fel de mesaj Data
  penghao 87795c128e feat:中台意见反馈相关接口开发 6 zile în urmă
  penghao 599b679efd Merge remote-tracking branch 'origin/sit-plantform' into sit-plantform 6 zile în urmă
  penghao f5897fdad1 Merge remote-tracking branch 'origin/sit-plantform' into sit-plantform 6 zile în urmă
  panzhilin 266c234225 意见反馈模块功能开发(未完成)1.2 1 săptămână în urmă
  panzhilin 07046ad69e 意见反馈模块功能开发(未完成) 1 săptămână în urmă
28 a modificat fișierele cu 1749 adăugiri și 286 ștergeri
  1. 74 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeFeedback.java
  2. 66 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeImg.java
  3. 19 4
      alien-entity/src/main/java/shop/alien/entity/store/LifeLog.java
  4. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/FeedbackReplyDto.java
  5. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackAssignDto.java
  6. 39 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackDto.java
  7. 35 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackQueryDto.java
  8. 29 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackReplyWebDto.java
  9. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackStatusDto.java
  10. 29 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackAttachmentVo.java
  11. 41 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackLogVo.java
  12. 75 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackDetailVo.java
  13. 62 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackListVo.java
  14. 60 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackVo.java
  15. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeStaffListVo.java
  16. 92 0
      alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackMapper.java
  17. 44 0
      alien-entity/src/main/java/shop/alien/mapper/LifeImgMapper.java
  18. 24 0
      alien-entity/src/main/java/shop/alien/mapper/LifeLogMapper.java
  19. 207 0
      alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml
  20. 70 0
      alien-entity/src/main/resources/mapper/LifeImgMapper.xml
  21. 0 9
      alien-store/src/main/java/shop/alien/store/controller/LifeCouponController.java
  22. 131 0
      alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java
  23. 0 8
      alien-store/src/main/java/shop/alien/store/service/LifeCouponService.java
  24. 87 0
      alien-store/src/main/java/shop/alien/store/service/LifeFeedbackService.java
  25. 41 0
      alien-store/src/main/java/shop/alien/store/service/LifeImgService.java
  26. 0 265
      alien-store/src/main/java/shop/alien/store/service/impl/LifeCouponServiceImpl.java
  27. 374 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java
  28. 46 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeImgServiceImpl.java

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

@@ -0,0 +1,74 @@
+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.io.Serializable;
+import java.util.Date;
+
+/**
+ * 意见反馈表
+ */
+@Data
+@JsonInclude
+@TableName("life_feedback")
+@ApiModel(value = "LifeFeedback对象", description = "意见反馈")
+public class LifeFeedback implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "用户ID")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    @TableField("feedback_source")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    @TableField("feedback_way")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈类型:0-优化建议,1-问题")
+    @TableField("feedback_type")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈内容")
+    @TableField("content")
+    private String content;
+
+    @ApiModelProperty(value = "联系方式(手机号或邮箱)")
+    @TableField("contact_way")
+    private String contactWay;
+
+    @ApiModelProperty(value = "反馈时间")
+    @TableField("feedback_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date feedbackTime;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决")
+    @TableField("handle_status")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "跟进人员ID(关联life_sys表的id)")
+    @TableField("staff_id")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "更新时间")
+    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+}
+

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

@@ -0,0 +1,66 @@
+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.io.Serializable;
+import java.util.Date;
+
+/**
+ * 反馈图片表
+ */
+@Data
+@JsonInclude
+@TableName("life_img")
+@ApiModel(value = "LifeImg对象", description = "反馈图片")
+public class LifeImg implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "反馈ID(关联life_feedback)")
+    @TableField("feedback_id")
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "文件类型:1-图片,2-视频")
+    @TableField("file_type")
+    private Integer fileType;
+
+    @ApiModelProperty(value = "图片/视频URL")
+    @TableField("img_url")
+    private String imgUrl;
+
+    @ApiModelProperty(value = "缩略图URL(视频的封面图)")
+    @TableField("thumbnail_url")
+    private String thumbnailUrl;
+
+    @ApiModelProperty(value = "上传时间")
+    @TableField("upload_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date uploadTime;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "update_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}
+

+ 19 - 4
alien-entity/src/main/java/shop/alien/entity/store/LifeLog.java

@@ -3,24 +3,39 @@ 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.io.Serializable;
 import java.util.Date;
 
 /**
- * 日志
+ * 意见反馈日志
  */
 @Data
 @JsonInclude
 @TableName("life_log")
-public class LifeLog {
+@ApiModel(value = "LifeLog对象", description = "意见反馈日志")
+public class LifeLog implements Serializable {
+    private static final long serialVersionUID = 1L;
 
+    @ApiModelProperty(value = "主键ID")
     @TableId(value = "id", type = IdType.AUTO)
-    private String id;
+    private Integer id;
 
+    @ApiModelProperty(value = "意见反馈主表ID")
+    @TableField("feedback_id")
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "日志内容")
+    @TableField("context")
     private String context;
 
+    @ApiModelProperty(value = "操作类型:0-问题解决状态,1-分配跟踪人员,3-回复用户")
+    @TableField("type")
+    private String type;
+
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")
     @TableLogic
@@ -36,7 +51,7 @@ public class LifeLog {
     private Integer createdUserId;
 
     @ApiModelProperty(value = "修改时间")
-    @TableField(value = "updated_time", fill = FieldFill.UPDATE)
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date updatedTime;
 

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/FeedbackReplyDto.java

@@ -0,0 +1,26 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 平台回复反馈DTO
+ */
+@Data
+@ApiModel(value = "FeedbackReplyDto对象", description = "平台回复反馈DTO")
+public class FeedbackReplyDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "原始反馈ID")
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "平台工作人员ID")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "回复内容")
+    private String content;
+}
+

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackAssignDto.java

@@ -0,0 +1,26 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台分配跟踪人员DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackAssignDto对象", description = "中台分配跟踪人员DTO")
+public class LifeFeedbackAssignDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID", required = true)
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "跟踪人员ID(关联life_sys表的id)", required = true)
+    private Integer staffId;
+
+    @ApiModelProperty(value = "操作人员ID")
+    private Integer operatorId;
+}
+

+ 39 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackDto.java

@@ -0,0 +1,39 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 意见反馈提交DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackDto对象", description = "意见反馈提交DTO")
+public class LifeFeedbackDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "反馈来源:1-用户端,2-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈方式:1-主动反馈,2-平台回复")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈类型:1-优化建议,2-问题")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈内容")
+    private String content;
+
+    @ApiModelProperty(value = "联系方式(手机号或邮箱)")
+    private String contactWay;
+
+    @ApiModelProperty(value = "图片URL列表")
+    private List<String> imgUrlList;
+}
+

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

@@ -0,0 +1,35 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台意见反馈查询DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackQueryDto对象", description = "中台意见反馈查询DTO")
+public class LifeFeedbackQueryDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "页码", required = true)
+    private Integer page = 1;
+
+    @ApiModelProperty(value = "每页数量", required = true)
+    private Integer size = 10;
+}
+

+ 29 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackReplyWebDto.java

@@ -0,0 +1,29 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台回复用户DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackReplyWebDto对象", description = "中台回复用户DTO")
+public class LifeFeedbackReplyWebDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID", required = true)
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "回复内容", required = true)
+    private String content;
+
+    @ApiModelProperty(value = "操作人员ID")
+    private Integer operatorId;
+
+    @ApiModelProperty(value = "用户端回复内容")
+    private String userReply;
+}
+

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackStatusDto.java

@@ -0,0 +1,26 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台更新反馈处理状态DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackStatusDto对象", description = "中台更新反馈处理状态DTO")
+public class LifeFeedbackStatusDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID", required = true)
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决", required = true)
+    private Integer handleStatus;
+
+    // @ApiModelProperty(value = "操作人员ID")
+    // private Integer operatorId;
+}
+

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

@@ -0,0 +1,29 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 反馈附件VO
+ */
+@Data
+@ApiModel(value = "FeedbackAttachmentVo对象", description = "反馈附件VO")
+public class FeedbackAttachmentVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "附件ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "文件类型:1-图片,2-视频")
+    private Integer fileType;
+
+    @ApiModelProperty(value = "文件URL")
+    private String fileUrl;
+
+    @ApiModelProperty(value = "缩略图URL(视频的封面图)")
+    private String thumbnailUrl;
+}
+

+ 41 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackLogVo.java

@@ -0,0 +1,41 @@
+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.io.Serializable;
+import java.util.Date;
+
+/**
+ * 反馈操作日志VO
+ */
+@Data
+@ApiModel(value = "FeedbackLogVo对象", description = "反馈操作日志VO")
+public class FeedbackLogVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "日志ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "操作类型:0-问题解决状态,1-分配跟踪人员,3-回复用户")
+    private Integer type;
+
+    @ApiModelProperty(value = "操作类型名称")
+    private String typeName;
+
+    @ApiModelProperty(value = "日志内容")
+    private String context;
+
+    @ApiModelProperty(value = "子内容(用于回复用户时显示用户回复)")
+    private String subContext;
+
+    @ApiModelProperty(value = "操作时间")
+    @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "操作人姓名")
+    private String operatorName;
+}
+

+ 75 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackDetailVo.java

@@ -0,0 +1,75 @@
+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.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 中台意见反馈详情VO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackDetailVo对象", description = "中台意见反馈详情VO")
+public class LifeFeedbackDetailVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "账号(手机号)")
+    private String phone;
+
+    @ApiModelProperty(value = "跟踪人员姓名+联系方式")
+    private String staffInfo;
+
+    @ApiModelProperty(value = "跟踪人员ID")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈来源名称")
+    private String feedbackSourceName;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈方式名称")
+    private String feedbackWayName;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈类型名称")
+    private String feedbackTypeName;
+
+    @ApiModelProperty(value = "反馈时间")
+    @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "GMT+8")
+    private Date feedbackTime;
+
+    @ApiModelProperty(value = "问题描述")
+    private String content;
+
+    @ApiModelProperty(value = "联系方式")
+    private String contactWay;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "处理状态名称")
+    private String handleStatusName;
+
+    @ApiModelProperty(value = "附件列表")
+    private List<FeedbackAttachmentVo> attachments;
+
+    @ApiModelProperty(value = "操作日志列表")
+    private List<FeedbackLogVo> logs;
+}
+

+ 62 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackListVo.java

@@ -0,0 +1,62 @@
+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.io.Serializable;
+import java.util.Date;
+
+/**
+ * 中台意见反馈列表VO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackListVo对象", description = "中台意见反馈列表VO")
+public class LifeFeedbackListVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "账号(手机号)")
+    private String phone;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈类型名称")
+    private String feedbackTypeName;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈方式名称")
+    private String feedbackWayName;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈来源名称")
+    private String feedbackSourceName;
+
+    @ApiModelProperty(value = "反馈时间")
+    @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "GMT+8")
+    private Date feedbackTime;
+
+    @ApiModelProperty(value = "跟踪人员姓名+部门")
+    private String staffInfo;
+
+    @ApiModelProperty(value = "跟踪人员ID")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "处理状态名称")
+    private String handleStatusName;
+}
+

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

@@ -0,0 +1,60 @@
+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.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 意见反馈展示VO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackVo对象", description = "意见反馈展示VO")
+public class LifeFeedbackVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "反馈来源:1-用户端,2-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈方式:1-主动反馈,2-平台回复")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈类型:1-优化建议,2-问题")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈内容")
+    private String content;
+
+    @ApiModelProperty(value = "联系方式")
+    private String contactWay;
+
+    @ApiModelProperty(value = "反馈时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date feedbackTime;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "跟进工作人员ID")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "跟进工作人员姓名")
+    private String staffName;
+
+    @ApiModelProperty(value = "附件图片列表")
+    private List<String> imgUrlList;
+
+    @ApiModelProperty(value = "平台反馈建议列表")
+    private List<LifeFeedbackVo> platformReplies;
+}
+

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeStaffListVo.java

@@ -0,0 +1,26 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台跟踪人员列表VO
+ */
+@Data
+@ApiModel(value = "LifeStaffListVo对象", description = "中台跟踪人员列表VO")
+public class LifeStaffListVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "人员ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "人员信息(姓名)")
+    private String staffInfo;
+
+    @ApiModelProperty(value = "姓名")
+    private String realName;
+}
+

+ 92 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackMapper.java

@@ -0,0 +1,92 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.store.LifeFeedback;
+import shop.alien.entity.store.vo.LifeFeedbackDetailVo;
+import shop.alien.entity.store.vo.LifeFeedbackListVo;
+import shop.alien.entity.store.vo.LifeFeedbackVo;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 意见反馈 Mapper 接口
+ */
+@Mapper
+public interface LifeFeedbackMapper extends BaseMapper<LifeFeedback> {
+
+    /**
+     * 查询用户反馈列表(带工作人员名称)
+     * @param page 分页对象
+     * @param userId 用户ID
+     * @param feedbackSource 反馈来源
+     * @param feedbackWay 反馈方式
+     * @param handleStatus 处理状态
+     * @return 反馈列表
+     */
+    IPage<LifeFeedbackVo> selectFeedbackListWithStaff(
+            Page<LifeFeedbackVo> page,
+            @Param("userId") Integer userId,
+            @Param("feedbackSource") Integer feedbackSource,
+            @Param("feedbackWay") Integer feedbackWay,
+            @Param("handleStatus") Integer handleStatus
+    );
+
+    /**
+     * 查询反馈详情(带工作人员名称)
+     * @param feedbackId 反馈ID
+     * @return 反馈详情
+     */
+    LifeFeedbackVo selectFeedbackDetail(@Param("feedbackId") Integer feedbackId);
+
+    /**
+     * 统计待处理反馈数量
+     * @param feedbackSource 反馈来源
+     * @return 待处理数量
+     */
+    Integer countPendingFeedback(@Param("feedbackSource") Integer feedbackSource);
+
+    /**
+     * 查询平台回复列表
+     * @param userId 用户ID
+     * @param feedbackSource 反馈来源
+     * @param startTime 开始时间
+     * @return 平台回复列表
+     */
+    List<LifeFeedbackVo> selectPlatformReplies(
+            @Param("userId") Integer userId,
+            @Param("feedbackSource") Integer feedbackSource,
+            @Param("startTime") Date startTime
+    );
+
+    // ==================== Web中台接口 ====================
+
+    /**
+     * 中台-查询意见反馈列表
+     * @param page 分页对象
+     * @param feedbackType 反馈类型
+     * @param handleStatus 处理状态
+     * @param feedbackSource 反馈来源
+     * @param feedbackWay 反馈方式
+     * @return 反馈列表
+     */
+    IPage<LifeFeedbackListVo> selectWebFeedbackList(
+            Page<LifeFeedbackListVo> page,
+            @Param("feedbackType") Integer feedbackType,
+            @Param("handleStatus") Integer handleStatus,
+            @Param("feedbackSource") Integer feedbackSource,
+            @Param("feedbackWay") Integer feedbackWay
+    );
+
+    /**
+     * 中台-查询反馈详情
+     * @param feedbackId 反馈ID
+     * @return 反馈详情
+     */
+    LifeFeedbackDetailVo selectWebFeedbackDetail(@Param("feedbackId") Integer feedbackId);
+}
+

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

@@ -0,0 +1,44 @@
+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.LifeImg;
+
+import java.util.List;
+
+/**
+ * 反馈图片 Mapper 接口
+ */
+@Mapper
+public interface LifeImgMapper extends BaseMapper<LifeImg> {
+
+    /**
+     * 根据反馈ID查询图片列表
+     * @param feedbackId 反馈ID
+     * @return 图片列表
+     */
+    List<LifeImg> selectByFeedbackId(@Param("feedbackId") Integer feedbackId);
+
+    /**
+     * 批量插入图片
+     * @param list 图片列表
+     * @return 插入数量
+     */
+    Integer batchInsert(@Param("list") List<LifeImg> list);
+
+    /**
+     * 根据反馈ID删除图片(逻辑删除)
+     * @param feedbackId 反馈ID
+     * @return 删除数量
+     */
+    Integer deleteByFeedbackId(@Param("feedbackId") Integer feedbackId);
+
+    /**
+     * 查询反馈的图片URL列表
+     * @param feedbackId 反馈ID
+     * @return 图片URL列表
+     */
+    List<String> selectImgUrlsByFeedbackId(@Param("feedbackId") Integer feedbackId);
+}
+

+ 24 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeLogMapper.java

@@ -2,11 +2,35 @@ package shop.alien.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 import shop.alien.entity.store.LifeLog;
+import shop.alien.entity.store.vo.FeedbackLogVo;
+
+import java.util.List;
 
 /**
  * 日志
  */
 @Mapper
 public interface LifeLogMapper extends BaseMapper<LifeLog> {
+
+    /**
+     * 根据反馈ID查询操作日志列表
+     * @param feedbackId 反馈ID
+     * @return 日志列表
+     */
+    @Select("SELECT l.id, l.type, l.context, l.created_time AS createdTime, " +
+            "CASE l.type " +
+            "   WHEN 0 THEN '问题解决状态' " +
+            "   WHEN 1 THEN '分配跟踪人员' " +
+            "   WHEN 2 THEN '创建反馈工单' " +
+            "   WHEN 3 THEN '回复用户' " +
+            "END AS typeName, " +
+            "s.user_name AS operatorName " +
+            "FROM life_log l " +
+            "LEFT JOIN life_sys s ON l.created_user_id = s.id " +
+            "WHERE l.feedback_id = #{feedbackId} AND l.delete_flag = 0 " +
+            "ORDER BY l.created_time DESC")
+    List<FeedbackLogVo> selectLogsByFeedbackId(@Param("feedbackId") Integer feedbackId);
 }

+ 207 - 0
alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml

@@ -0,0 +1,207 @@
+<?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.LifeFeedbackMapper">
+
+    <!-- 通用结果映射 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.LifeFeedback">
+        <id column="id" property="id" />
+        <result column="user_id" property="userId" />
+        <result column="feedback_source" property="feedbackSource" />
+        <result column="feedback_way" property="feedbackWay" />
+        <result column="feedback_type" property="feedbackType" />
+        <result column="content" property="content" />
+        <result column="contact_way" property="contactWay" />
+        <result column="feedback_time" property="feedbackTime" />
+        <result column="staff_id" property="staffId" />
+        <result column="handle_status" property="handleStatus" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+    </resultMap>
+
+    <!-- 基础字段 -->
+    <sql id="Base_Column_List">
+        id, user_id, feedback_source, feedback_way, feedback_type, 
+        content, contact_way, feedback_time, staff_id, handle_status,
+        create_time, update_time
+    </sql>
+
+    <!-- 查询用户反馈列表(带工作人员名称) -->
+    <select id="selectFeedbackListWithStaff" resultType="shop.alien.entity.store.vo.LifeFeedbackVo">
+        SELECT 
+            f.id,
+            f.user_id AS userId,
+            f.feedback_source AS feedbackSource,
+            f.feedback_way AS feedbackWay,
+            f.feedback_type AS feedbackType,
+            f.content,
+            f.contact_way AS contactWay,
+            f.feedback_time AS feedbackTime,
+            f.staff_id AS staffId,
+            f.handle_status AS handleStatus,
+            s.user_name AS staffName
+        FROM life_feedback f
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE 1=1
+        <if test="userId != null">
+            AND f.user_id = #{userId}
+        </if>
+        <if test="feedbackSource != null">
+            AND f.feedback_source = #{feedbackSource}
+        </if>
+        <if test="feedbackWay != null">
+            AND f.feedback_way = #{feedbackWay}
+        </if>
+        <if test="handleStatus != null">
+            AND f.handle_status = #{handleStatus}
+        </if>
+        ORDER BY f.feedback_time DESC
+    </select>
+
+    <!-- 查询反馈详情(带工作人员名称和图片) -->
+    <select id="selectFeedbackDetail" resultType="shop.alien.entity.store.vo.LifeFeedbackVo">
+        SELECT 
+            f.id,
+            f.user_id AS userId,
+            f.feedback_source AS feedbackSource,
+            f.feedback_way AS feedbackWay,
+            f.feedback_type AS feedbackType,
+            f.content,
+            f.contact_way AS contactWay,
+            f.feedback_time AS feedbackTime,
+            f.staff_id AS staffId,
+            f.handle_status AS handleStatus,
+            s.user_name AS staffName
+        FROM life_feedback f
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE f.id = #{feedbackId}
+    </select>
+
+    <!-- 统计待处理反馈数量 -->
+    <select id="countPendingFeedback" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM life_feedback
+        WHERE handle_status = 0
+        <if test="feedbackSource != null">
+            AND feedback_source = #{feedbackSource}
+        </if>
+    </select>
+
+    <!-- 查询平台回复列表 -->
+    <select id="selectPlatformReplies" resultType="shop.alien.entity.store.vo.LifeFeedbackVo">
+        SELECT 
+            f.id,
+            f.user_id AS userId,
+            f.feedback_source AS feedbackSource,
+            f.feedback_way AS feedbackWay,
+            f.feedback_type AS feedbackType,
+            f.content,
+            f.feedback_time AS feedbackTime,
+            f.staff_id AS staffId,
+            f.handle_status AS handleStatus,
+            s.user_name AS staffName
+        FROM life_feedback f
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE f.user_id = #{userId}
+        AND f.feedback_source = #{feedbackSource}
+        AND f.feedback_way = 1
+        AND f.feedback_time >= #{startTime}
+        ORDER BY f.feedback_time ASC
+    </select>
+
+    <!-- ==================== Web中台接口 ==================== -->
+
+    <!-- 中台-查询意见反馈列表 -->
+    <select id="selectWebFeedbackList" resultType="shop.alien.entity.store.vo.LifeFeedbackListVo">
+        SELECT 
+            f.id,
+            u.user_name AS nickName,
+            u.user_phone AS phone,
+            f.feedback_type AS feedbackType,
+            CASE f.feedback_type 
+                WHEN 0 THEN 'bug反馈' 
+                WHEN 1 THEN '优化反馈' 
+                WHEN 2 THEN '新增功能反馈' 
+            END AS feedbackTypeName,
+            f.feedback_way AS feedbackWay,
+            CASE f.feedback_way 
+                WHEN 0 THEN '用户反馈' 
+                WHEN 1 THEN 'AI识别' 
+            END AS feedbackWayName,
+            f.feedback_source AS feedbackSource,
+            CASE f.feedback_source 
+                WHEN 0 THEN '用户端' 
+                WHEN 1 THEN '商家端' 
+            END AS feedbackSourceName,
+            f.feedback_time AS feedbackTime,
+            f.staff_id AS staffId,
+            CONCAT(IFNULL(s.user_name, '')) AS staffInfo,
+            f.handle_status AS handleStatus,
+            CASE f.handle_status 
+                WHEN 0 THEN '处理中' 
+                WHEN 1 THEN '已解决' 
+                WHEN 2 THEN '未分配' 
+                WHEN 3 THEN '无需解决' 
+            END AS handleStatusName
+        FROM life_feedback f
+        LEFT JOIN life_user u ON f.user_id = u.id
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE 1=1
+        <if test="feedbackType != null">
+            AND f.feedback_type = #{feedbackType}
+        </if>
+        <if test="handleStatus != null">
+            AND f.handle_status = #{handleStatus}
+        </if>
+        <if test="feedbackSource != null">
+            AND f.feedback_source = #{feedbackSource}
+        </if>
+        <if test="feedbackWay != null">
+            AND f.feedback_way = #{feedbackWay}
+        </if>
+        ORDER BY f.feedback_time DESC
+    </select>
+
+    <!-- 中台-查询反馈详情 -->
+    <select id="selectWebFeedbackDetail" resultType="shop.alien.entity.store.vo.LifeFeedbackDetailVo">
+        SELECT 
+            f.id,
+            u.user_name AS nickName,
+            u.user_phone AS phone,
+            f.staff_id AS staffId,
+            CONCAT(IFNULL(s.user_name, '')) AS staffInfo,
+            f.feedback_source AS feedbackSource,
+            CASE f.feedback_source 
+                WHEN 0 THEN '用户端' 
+                WHEN 1 THEN '商家端' 
+            END AS feedbackSourceName,
+            f.feedback_way AS feedbackWay,
+            CASE f.feedback_way 
+                WHEN 0 THEN '用户反馈' 
+                WHEN 1 THEN 'AI识别' 
+            END AS feedbackWayName,
+            f.feedback_type AS feedbackType,
+            CASE f.feedback_type 
+                WHEN 0 THEN 'bug反馈' 
+                WHEN 1 THEN '优化反馈' 
+                WHEN 2 THEN '新增功能反馈' 
+            END AS feedbackTypeName,
+            f.feedback_time AS feedbackTime,
+            f.content,
+            f.contact_way AS contactWay,
+            f.handle_status AS handleStatus,
+            CASE f.handle_status 
+                WHEN 0 THEN '处理中' 
+                WHEN 1 THEN '已解决' 
+                WHEN 2 THEN '未分配' 
+                WHEN 3 THEN '无需解决' 
+            END AS handleStatusName
+        FROM life_feedback f
+        LEFT JOIN life_user u ON f.user_id = u.id
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE f.id = #{feedbackId}
+    </select>
+
+</mapper>
+

+ 70 - 0
alien-entity/src/main/resources/mapper/LifeImgMapper.xml

@@ -0,0 +1,70 @@
+<?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.LifeImgMapper">
+
+    <!-- 通用结果映射 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.LifeImg">
+        <id column="id" property="id" />
+        <result column="feedback_id" property="feedbackId" />
+        <result column="file_type" property="fileType" />
+        <result column="img_url" property="imgUrl" />
+        <result column="thumbnail_url" property="thumbnailUrl" />
+        <result column="upload_time" property="uploadTime" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+        <result column="created_user_id" property="createdUserId" />
+        <result column="updated_user_id" property="updatedUserId" />
+    </resultMap>
+
+    <!-- 基础字段 -->
+    <sql id="Base_Column_List">
+        id, feedback_id, file_type, img_url, thumbnail_url,
+        upload_time, create_time, update_time, created_user_id, updated_user_id
+    </sql>
+
+    <!-- 根据反馈ID查询图片列表 -->
+    <select id="selectByFeedbackId" resultMap="BaseResultMap">
+        SELECT 
+            <include refid="Base_Column_List" />
+        FROM life_img
+        WHERE feedback_id = #{feedbackId}
+        ORDER BY id ASC
+    </select>
+
+    <!-- 批量插入图片 -->
+    <insert id="batchInsert" parameterType="java.util.List">
+        INSERT INTO life_img (
+            feedback_id, file_type, img_url, thumbnail_url,
+            upload_time, create_time, created_user_id
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+                #{item.feedbackId},
+                #{item.fileType},
+                #{item.imgUrl},
+                #{item.thumbnailUrl},
+                NOW(),
+                NOW(),
+                #{item.createdUserId}
+            )
+        </foreach>
+    </insert>
+
+    <!-- 根据反馈ID删除图片 -->
+    <delete id="deleteByFeedbackId">
+        DELETE FROM life_img 
+        WHERE feedback_id = #{feedbackId}
+    </delete>
+
+    <!-- 查询反馈的图片URL列表 -->
+    <select id="selectImgUrlsByFeedbackId" resultType="java.lang.String">
+        SELECT img_url
+        FROM life_img
+        WHERE feedback_id = #{feedbackId}
+        ORDER BY id ASC
+    </select>
+
+</mapper>
+

+ 0 - 9
alien-store/src/main/java/shop/alien/store/controller/LifeCouponController.java

@@ -6,7 +6,6 @@ import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
-import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.EssentialHolidayComparison;
 import shop.alien.entity.store.LifeCoupon;
@@ -182,12 +181,4 @@ public class LifeCouponController {
         }
         return R.fail("失败");
     }
-
-    @ApiOperation("导入假期管理")
-    //@ApiImplicitParams({@ApiImplicitParam(name = "file", value = "Excel文件", dataType = "MultipartFile", paramType = "form", required = true)})
-    @PostMapping("/importHoliday")
-    public R<String> importHoliday(MultipartFile file) {
-        log.info("LifeCouponController.importHoliday fileName={}", file.getOriginalFilename());
-        return lifeCouponService.importHolidayFromExcel(file);
-    }
 }

+ 131 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java

@@ -0,0 +1,131 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+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.dto.*;
+import shop.alien.entity.store.vo.LifeFeedbackDetailVo;
+import shop.alien.entity.store.vo.LifeFeedbackListVo;
+import shop.alien.entity.store.vo.LifeFeedbackVo;
+import shop.alien.store.service.LifeFeedbackService;
+
+/**
+ * 意见反馈 Controller
+ */
+@Api(tags = {"意见反馈模块"})
+@Slf4j
+@CrossOrigin
+@RestController
+@RequestMapping("/feedback")
+@RequiredArgsConstructor
+public class LifeFeedbackController {
+
+    private final LifeFeedbackService lifeFeedbackService;
+
+    @ApiOperation(value = "提交反馈", httpMethod = "POST")
+    @PostMapping("/submit")
+    public R<String> submitFeedback(@RequestBody LifeFeedbackDto dto) {
+        log.info("LifeFeedbackController.submitFeedback, dto={}", dto);
+        return lifeFeedbackService.submitFeedback(dto);
+    }
+
+    @ApiOperation(value = "平台回复反馈", httpMethod = "POST")
+    @PostMapping("/reply")
+    public R<String> replyFeedback(@RequestBody FeedbackReplyDto dto) {
+        log.info("LifeFeedbackController.replyFeedback, dto={}", dto);
+        return lifeFeedbackService.replyFeedback(dto);
+    }
+
+    @ApiOperation(value = "查询用户历史反馈列表", httpMethod = "GET")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "feedbackSource", value = "反馈来源:1-用户端,2-商家端", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "size", value = "每页数量", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/list")
+    public R<IPage<LifeFeedbackVo>> getFeedbackList(
+            @RequestParam("userId") Integer userId,
+            @RequestParam("feedbackSource") Integer feedbackSource,
+            @RequestParam(value = "page", defaultValue = "1") int page,
+            @RequestParam(value = "size", defaultValue = "10") int size) {
+        log.info("LifeFeedbackController.getFeedbackList, userId={}, feedbackSource={}, page={}, size={}", 
+                userId, feedbackSource, page, size);
+        IPage<LifeFeedbackVo> result = lifeFeedbackService.getFeedbackList(userId, feedbackSource, page, size);
+        return R.data(result);
+    }
+
+    @ApiOperation(value = "查询反馈详情", httpMethod = "GET")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "feedbackId", value = "反馈ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/detail")
+    public R<LifeFeedbackVo> getFeedbackDetail(@RequestParam("feedbackId") Integer feedbackId) {
+        log.info("LifeFeedbackController.getFeedbackDetail, feedbackId={}", feedbackId);
+        return lifeFeedbackService.getFeedbackDetail(feedbackId);
+    }
+
+    @ApiOperation(value = "更新反馈处理状态", httpMethod = "POST")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "feedbackId", value = "反馈ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "handleStatus", value = "处理状态:0-待处理,1-处理中,2-已完成", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "staffId", value = "跟进工作人员ID", dataType = "Integer", paramType = "query", required = false)
+    })
+    @PostMapping("/updateStatus")
+    public R<String> updateHandleStatus(
+            @RequestParam("feedbackId") Integer feedbackId,
+            @RequestParam("handleStatus") Integer handleStatus,
+            @RequestParam(value = "staffId", required = false) Integer staffId) {
+        log.info("LifeFeedbackController.updateHandleStatus, feedbackId={}, handleStatus={}, staffId={}", 
+                feedbackId, handleStatus, staffId);
+        return lifeFeedbackService.updateHandleStatus(feedbackId, handleStatus, staffId);
+    }
+
+    // ==================== 中台接口 ====================
+
+    @ApiOperation(value = "中台-查询意见反馈列表", httpMethod = "GET")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "feedbackType", value = "反馈类型:0-优化建议,1-问题", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "handleStatus", value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "feedbackSource", value = "反馈来源:0-用户端,1-商家端", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "feedbackWay", value = "反馈方式:0-用户反馈,1-AI识别", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "size", value = "每页数量", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/platform/list")
+    public R<IPage<LifeFeedbackListVo>> getWebFeedbackList(LifeFeedbackQueryDto queryDto) {
+        log.info("LifeFeedbackController.getWebFeedbackList, queryDto={}", queryDto);
+        return lifeFeedbackService.getWebFeedbackList(queryDto);
+    }
+
+    @ApiOperation(value = "中台-查询反馈详情", httpMethod = "GET")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "feedbackId", value = "反馈ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/platform/detail")
+    public R<LifeFeedbackDetailVo> getWebFeedbackDetail(@RequestParam("feedbackId") Integer feedbackId) {
+        log.info("LifeFeedbackController.getWebFeedbackDetail, feedbackId={}", feedbackId);
+        return lifeFeedbackService.getWebFeedbackDetail(feedbackId);
+    }
+
+    @ApiOperation(value = "中台-回复用户", httpMethod = "POST")
+    @PostMapping("/platform/reply")
+    public R<String> webReplyUser(@RequestBody LifeFeedbackReplyWebDto replyDto) {
+        log.info("LifeFeedbackController.webReplyUser, replyDto={}", replyDto);
+        return lifeFeedbackService.webReplyUser(replyDto);
+    }
+
+    @ApiOperation(value = "中台-更新处理状态", httpMethod = "POST")
+    @PostMapping("/platform/updateStatus")
+    public R<String> updateWebFeedbackStatus(@RequestBody LifeFeedbackStatusDto statusDto) {
+        log.info("LifeFeedbackController.updateWebFeedbackStatus, statusDto={}", statusDto);
+        return lifeFeedbackService.updateWebFeedbackStatus(statusDto);
+    }
+}
+

+ 0 - 8
alien-store/src/main/java/shop/alien/store/service/LifeCouponService.java

@@ -3,7 +3,6 @@ package shop.alien.store.service;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import org.springframework.web.bind.annotation.RequestParam;
 import shop.alien.entity.store.EssentialHolidayComparison;
@@ -64,11 +63,4 @@ public interface LifeCouponService extends IService<LifeCoupon> {
      * @return LifeCouponVo
      */
     shop.alien.entity.store.vo.LifeCouponVo getNewCouponDetail(String id);
-
-    /**
-     * 导入假期管理
-     * @param file Excel文件
-     * @return 导入结果
-     */
-    R<String> importHolidayFromExcel(MultipartFile file);
 }

+ 87 - 0
alien-store/src/main/java/shop/alien/store/service/LifeFeedbackService.java

@@ -0,0 +1,87 @@
+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.LifeFeedback;
+import shop.alien.entity.store.dto.*;
+import shop.alien.entity.store.vo.LifeFeedbackDetailVo;
+import shop.alien.entity.store.vo.LifeFeedbackListVo;
+import shop.alien.entity.store.vo.LifeFeedbackVo;
+
+/**
+ * 意见反馈 Service
+ */
+public interface LifeFeedbackService extends IService<LifeFeedback> {
+
+    /**
+     * 提交反馈
+     * @param dto 反馈信息
+     * @return 反馈结果
+     */
+    R<String> submitFeedback(LifeFeedbackDto dto);
+
+    /**
+     * 平台回复反馈
+     * @param dto 回复信息
+     * @return 回复结果
+     */
+    R<String> replyFeedback(FeedbackReplyDto dto);
+
+    /**
+     * 查询用户历史反馈列表
+     * @param userId 用户ID
+     * @param feedbackSource 反馈来源
+     * @param page 页码
+     * @param size 每页数量
+     * @return 反馈列表
+     */
+    IPage<LifeFeedbackVo> getFeedbackList(Integer userId, Integer feedbackSource, int page, int size);
+
+    /**
+     * 查询反馈详情
+     * @param feedbackId 反馈ID
+     * @return 反馈详情
+     */
+    R<LifeFeedbackVo> getFeedbackDetail(Integer feedbackId);
+
+    /**
+     * 更新反馈处理状态
+     * @param feedbackId 反馈ID
+     * @param handleStatus 处理状态
+     * @param staffId 跟进人员ID
+     * @return 更新结果
+     */
+    R<String> updateHandleStatus(Integer feedbackId, Integer handleStatus, Integer staffId);
+
+    // ==================== 中台接口 ====================
+
+    /**
+     * 中台-查询意见反馈列表
+     * @param queryDto 查询条件
+     * @return 反馈列表
+     */
+    R<IPage<LifeFeedbackListVo>> getWebFeedbackList(LifeFeedbackQueryDto queryDto);
+
+    /**
+     * 中台-查询反馈详情
+     * @param feedbackId 反馈ID
+     * @return 反馈详情
+     */
+    R<LifeFeedbackDetailVo> getWebFeedbackDetail(Integer feedbackId);
+
+    /**
+     * 中台-回复用户
+     * @param replyDto 回复信息
+     * @return 回复结果
+     */
+    R<String> webReplyUser(LifeFeedbackReplyWebDto replyDto);
+
+    /**
+     * 中台-更新反馈处理状态
+     * @param statusDto 状态信息
+     * @return 更新结果
+     */
+    R<String> updateWebFeedbackStatus(LifeFeedbackStatusDto statusDto);
+}
+

+ 41 - 0
alien-store/src/main/java/shop/alien/store/service/LifeImgService.java

@@ -0,0 +1,41 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.LifeImg;
+
+import java.util.List;
+
+/**
+ * 反馈图片 Service
+ */
+public interface LifeImgService extends IService<LifeImg> {
+
+    /**
+     * 根据反馈ID查询图片列表
+     * @param feedbackId 反馈ID
+     * @return 图片列表
+     */
+    List<LifeImg> getByFeedbackId(Integer feedbackId);
+
+    /**
+     * 批量保存图片
+     * @param imgList 图片列表
+     * @return 是否成功
+     */
+    boolean batchSave(List<LifeImg> imgList);
+
+    /**
+     * 根据反馈ID删除图片
+     * @param feedbackId 反馈ID
+     * @return 是否成功
+     */
+    boolean removeByFeedbackId(Integer feedbackId);
+
+    /**
+     * 查询反馈的图片URL列表
+     * @param feedbackId 反馈ID
+     * @return 图片URL列表
+     */
+    List<String> getImgUrlsByFeedbackId(Integer feedbackId);
+}
+

+ 0 - 265
alien-store/src/main/java/shop/alien/store/service/impl/LifeCouponServiceImpl.java

@@ -8,14 +8,10 @@ import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 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.apache.poi.ss.usermodel.*;
-import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
-import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.LifeDiscountCouponStoreFriendDto;
@@ -27,10 +23,8 @@ import shop.alien.store.service.LifeDiscountCouponStoreFriendService;
 import shop.alien.util.common.UniqueRandomNumGenerator;
 import shop.alien.util.common.constant.OrderStatusEnum;
 
-import java.io.InputStream;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.text.SimpleDateFormat;
 import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.time.format.TextStyle;
@@ -44,7 +38,6 @@ import java.util.stream.Collectors;
  * @version 1.0
  * @date 2024/12/23 15:08
  */
-@Slf4j
 @Service
 @RequiredArgsConstructor
 public class LifeCouponServiceImpl extends ServiceImpl<LifeCouponMapper, LifeCoupon> implements LifeCouponService {
@@ -701,262 +694,4 @@ public class LifeCouponServiceImpl extends ServiceImpl<LifeCouponMapper, LifeCou
         return lifeCouponMapper.getNewCouponDetail(id);
     }
 
-    @Override
-    public R<String> importHolidayFromExcel(MultipartFile file) {
-        log.info("LifeCouponServiceImpl.importHolidayFromExcel fileName={}", file.getOriginalFilename());
-
-        if (file == null || file.isEmpty()) {
-            return R.fail("上传文件为空");
-        }
-
-        String fileName = file.getOriginalFilename();
-        if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
-            return R.fail("文件格式不正确,请上传Excel文件");
-        }
-
-        List<String> errorMessages = new ArrayList<>();
-        int successCount = 0;
-        int totalCount = 0;
-
-        try (InputStream inputStream = file.getInputStream();
-             XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) {
-            Sheet sheet = workbook.getSheetAt(0);
-
-            // 获取表头(第6行,索引为5)
-            Row headerRow = sheet.getRow(5);
-            if (headerRow == null) {
-                return R.fail("Excel文件格式不正确,缺少表头");
-            }
-
-            // 构建字段映射(表头名称 -> 列索引)
-            Map<String, Integer> headerMap = new HashMap<>();
-            for (int i = 0; i < headerRow.getLastCellNum(); i++) {
-                Cell cell = headerRow.getCell(i);
-                if (cell != null) {
-                    String headerName = getCellValueAsString(cell);
-                    if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(headerName)) {
-                        headerMap.put(headerName.trim(), i);
-                    }
-                }
-            }
-
-            // 验证表头
-            if (!headerMap.containsKey("年份") || !headerMap.containsKey("节日名称") 
-                    || !headerMap.containsKey("开始时间") || !headerMap.containsKey("结束时间")) {
-                return R.fail("Excel文件格式不正确,缺少必要的表头字段(年份、节日名称、开始时间、结束时间)");
-            }
-
-            // 读取数据行(从第7行开始,索引为6)
-            for (int rowIndex = 6; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
-                Row row = sheet.getRow(rowIndex);
-                if (row == null) {
-                    continue;
-                }
-
-                // 检查是否为空行
-                boolean isEmptyRow = true;
-                for (int i = 0; i < row.getLastCellNum(); i++) {
-                    Cell cell = row.getCell(i);
-                    if (cell != null && cell.getCellType() != CellType.BLANK) {
-                        String cellValue = getCellValueAsString(cell);
-                        if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(cellValue)) {
-                            isEmptyRow = false;
-                            break;
-                        }
-                    }
-                }
-                if (isEmptyRow) {
-                    continue;
-                }
-
-                totalCount++;
-                EssentialHolidayComparison holiday = new EssentialHolidayComparison();
-
-                try {
-                    // 读取年份(必填)
-                    Integer yearColIndex = headerMap.get("年份");
-                    if (yearColIndex == null) {
-                        throw new RuntimeException("缺少年份字段");
-                    }
-                    Cell yearCell = row.getCell(yearColIndex);
-                    String yearValue = getCellValueAsString(yearCell);
-                    if (StringUtils.isEmpty(yearValue)) {
-                        throw new RuntimeException("年份不能为空");
-                    }
-                    // 处理年份可能是数字的情况
-                    if (yearCell != null && yearCell.getCellType() == CellType.NUMERIC) {
-                        double numericValue = yearCell.getNumericCellValue();
-                        yearValue = String.valueOf((long) numericValue);
-                    }
-                    holiday.setParticularYear(yearValue.trim());
-
-                    // 读取节日名称(必填)
-                    Integer nameColIndex = headerMap.get("节日名称");
-                    if (nameColIndex == null) {
-                        throw new RuntimeException("缺少节日名称字段");
-                    }
-                    Cell nameCell = row.getCell(nameColIndex);
-                    String nameValue = getCellValueAsString(nameCell);
-                    if (StringUtils.isEmpty(nameValue)) {
-                        throw new RuntimeException("节日名称不能为空");
-                    }
-                    holiday.setFestivalName(nameValue.trim());
-
-                    // 读取开始时间(必填,格式:2026-01-01)
-                    Integer startTimeColIndex = headerMap.get("开始时间");
-                    if (startTimeColIndex == null) {
-                        throw new RuntimeException("缺少开始时间字段");
-                    }
-                    Cell startTimeCell = row.getCell(startTimeColIndex);
-                    String startTimeValue = getCellValueAsString(startTimeCell);
-                    if (StringUtils.isEmpty(startTimeValue)) {
-                        throw new RuntimeException("开始时间不能为空");
-                    }
-                    Date startTime = parseDate(startTimeValue.trim(), rowIndex + 1);
-                    holiday.setStartTime(startTime);
-
-                    // 读取结束时间(必填,格式:2026-01-01)
-                    Integer endTimeColIndex = headerMap.get("结束时间");
-                    if (endTimeColIndex == null) {
-                        throw new RuntimeException("缺少结束时间字段");
-                    }
-                    Cell endTimeCell = row.getCell(endTimeColIndex);
-                    String endTimeValue = getCellValueAsString(endTimeCell);
-                    if (StringUtils.isEmpty(endTimeValue)) {
-                        throw new RuntimeException("结束时间不能为空");
-                    }
-                    Date endTime = parseDate(endTimeValue.trim(), rowIndex + 1);
-                    holiday.setEndTime(endTime);
-
-                    // 验证结束时间必须大于等于开始时间
-                    if (endTime.before(startTime)) {
-                        throw new RuntimeException("结束时间必须大于等于开始时间");
-                    }
-
-                    // 读取状态(可选,默认启用)
-                    Integer statusColIndex = headerMap.get("状态");
-                    int openFlag = 1; // 默认启用
-                    if (statusColIndex != null) {
-                        Cell statusCell = row.getCell(statusColIndex);
-                        String statusValue = getCellValueAsString(statusCell);
-                        if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(statusValue)) {
-                            String status = statusValue.trim();
-                            if ("启用".equals(status) || "1".equals(status)) {
-                                openFlag = 1;
-                            } else if ("禁用".equals(status) || "0".equals(status)) {
-                                openFlag = 0;
-                            } else {
-                                throw new RuntimeException("状态格式错误,请输入'启用'或'禁用'");
-                            }
-                        }
-                    }
-                    holiday.setOpenFlag(openFlag);
-                    holiday.setDelFlag(0);
-
-                    // 设置节日日期为开始时间
-                    LocalDate startLocalDate = startTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
-                    holiday.setFestivalDate(startLocalDate);
-
-                    // 保存数据
-                    essentialHolidayComparisonMapper.insert(holiday);
-                    successCount++;
-                } catch (Exception e) {
-                    errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
-                    log.error("导入第{}行数据失败", rowIndex + 1, e);
-                }
-            }
-        } catch (Exception e) {
-            log.error("导入Excel失败", e);
-            return R.fail("导入失败:" + e.getMessage());
-        }
-
-        // 构建返回消息
-        StringBuilder message = new StringBuilder();
-        message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
-        if (!errorMessages.isEmpty()) {
-            message.append("\n失败详情:\n");
-            for (int i = 0; i < Math.min(errorMessages.size(), 10); i++) {
-                message.append(errorMessages.get(i)).append("\n");
-            }
-            if (errorMessages.size() > 10) {
-                message.append("...还有").append(errorMessages.size() - 10).append("条错误信息");
-            }
-        }
-
-        return R.success(message.toString());
-    }
-
-    /**
-     * 解析日期字符串
-     */
-    private Date parseDate(String dateStr, int rowNum) {
-        try {
-            // 尝试多种日期格式
-            SimpleDateFormat[] formats = {
-                new SimpleDateFormat("yyyy-MM-dd"),
-                new SimpleDateFormat("yyyy/MM/dd"),
-                new SimpleDateFormat("yyyy年MM月dd日")
-            };
-
-            for (SimpleDateFormat format : formats) {
-                try {
-                    return format.parse(dateStr);
-                } catch (Exception e) {
-                    // 继续尝试下一个格式
-                }
-            }
-
-            // 如果都失败,尝试解析数字日期(Excel日期格式)
-            try {
-                double numericValue = Double.parseDouble(dateStr);
-                return org.apache.poi.ss.usermodel.DateUtil.getJavaDate(numericValue);
-            } catch (Exception e) {
-                // 忽略
-            }
-
-            throw new RuntimeException("日期格式错误,请使用格式:2026-01-01");
-        } catch (Exception e) {
-            throw new RuntimeException("日期格式错误,请使用格式:2026-01-01");
-        }
-    }
-
-    /**
-     * 获取单元格值(字符串格式)
-     */
-    private String getCellValueAsString(Cell cell) {
-        if (cell == null) {
-            return null;
-        }
-
-        switch (cell.getCellType()) {
-            case STRING:
-                return cell.getStringCellValue();
-            case NUMERIC:
-                if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
-                    // 日期格式
-                    Date date = cell.getDateCellValue();
-                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
-                    return sdf.format(date);
-                } else {
-                    // 处理数字,避免科学计数法
-                    double numericValue = cell.getNumericCellValue();
-                    if (numericValue == (long) numericValue) {
-                        return String.valueOf((long) numericValue);
-                    } else {
-                        return String.valueOf(numericValue);
-                    }
-                }
-            case BOOLEAN:
-                return String.valueOf(cell.getBooleanCellValue());
-            case FORMULA:
-                try {
-                    return cell.getStringCellValue();
-                } catch (Exception e) {
-                    return String.valueOf(cell.getNumericCellValue());
-                }
-            default:
-                return null;
-        }
-    }
-
 }

+ 374 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java

@@ -0,0 +1,374 @@
+package shop.alien.store.service.impl;
+
+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.beans.BeanUtils;
+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.LifeFeedback;
+import shop.alien.entity.store.LifeImg;
+import shop.alien.entity.store.LifeLog;
+import shop.alien.entity.store.dto.*;
+import shop.alien.entity.store.vo.*;
+import shop.alien.mapper.LifeFeedbackMapper;
+import shop.alien.mapper.LifeLogMapper;
+import shop.alien.store.service.LifeFeedbackService;
+import shop.alien.store.service.LifeImgService;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 意见反馈 Service实现类
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional(rollbackFor = Exception.class)
+public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, LifeFeedback> implements LifeFeedbackService {
+
+    private final LifeFeedbackMapper lifeFeedbackMapper;
+    private final LifeImgService lifeImgService;
+    private final LifeLogMapper lifeLogMapper;
+
+    @Override
+    public R<String> submitFeedback(LifeFeedbackDto dto) {
+        try {
+            // 1. 参数校验
+            if (dto.getUserId() == null) {
+                return R.fail("用户ID不能为空");
+            }
+            if (dto.getFeedbackSource() == null) {
+                return R.fail("反馈来源不能为空");
+            }
+            if (dto.getFeedbackType() == null) {
+                return R.fail("反馈类型不能为空");
+            }
+            if (dto.getContent() == null || dto.getContent().trim().isEmpty()) {
+                return R.fail("反馈内容不能为空");
+            }
+
+            // 2. 创建反馈记录(使用MyBatis Plus的save方法)
+            LifeFeedback feedback = new LifeFeedback();
+            BeanUtils.copyProperties(dto, feedback);
+            feedback.setFeedbackTime(new Date());
+            feedback.setHandleStatus(0); // 处理中
+            feedback.setCreateTime(new Date());
+
+            boolean saveResult = this.save(feedback);
+            if (!saveResult) {
+                return R.fail("提交反馈失败");
+            }
+
+            // 3. 保存附件图片(使用批量插入)
+            if (!CollectionUtils.isEmpty(dto.getImgUrlList())) {
+                List<LifeImg> imgList = new java.util.ArrayList<>();
+                for (String imgUrl : dto.getImgUrlList()) {
+                    LifeImg img = new LifeImg();
+                    img.setFeedbackId(feedback.getId());
+                    img.setImgUrl(imgUrl);
+                    img.setFileType(1); // 默认图片
+                    img.setUploadTime(new Date());
+                    img.setCreateTime(new Date());
+                    img.setCreatedUserId(dto.getUserId());
+                    imgList.add(img);
+                }
+                lifeImgService.batchSave(imgList);
+            }
+
+            // 4. 记录日志 - 创建反馈工单
+            saveFeedbackLog(feedback.getId(), 2, "商家主动反馈/AI识别");
+
+            return R.success("提交成功");
+        } catch (Exception e) {
+            log.error("提交反馈失败", e);
+            return R.fail("提交反馈失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<String> replyFeedback(FeedbackReplyDto dto) {
+        try {
+            // 1. 参数校验
+            if (dto.getFeedbackId() == null) {
+                return R.fail("反馈ID不能为空");
+            }
+            if (dto.getStaffId() == null) {
+                return R.fail("工作人员ID不能为空");
+            }
+            if (dto.getContent() == null || dto.getContent().trim().isEmpty()) {
+                return R.fail("回复内容不能为空");
+            }
+
+            // 2. 查询原始反馈
+            LifeFeedback originalFeedback = lifeFeedbackMapper.selectById(dto.getFeedbackId());
+            if (originalFeedback == null) {
+                return R.fail("反馈记录不存在");
+            }
+
+            // 3. 创建平台回复记录(使用MyBatis Plus的save方法)
+            LifeFeedback reply = new LifeFeedback();
+            reply.setUserId(originalFeedback.getUserId());
+            reply.setFeedbackSource(originalFeedback.getFeedbackSource());
+            reply.setFeedbackWay(1); // 平台回复(AI识别方式)
+            reply.setFeedbackType(originalFeedback.getFeedbackType());
+            reply.setContent(dto.getContent());
+            reply.setFeedbackTime(new Date());
+            reply.setStaffId(dto.getStaffId());
+            reply.setHandleStatus(1); // 已解决
+            reply.setCreateTime(new Date());
+
+            boolean saveResult = this.save(reply);
+            if (!saveResult) {
+                return R.fail("回复失败");
+            }
+
+            // 4. 更新原始反馈的处理状态和跟进人员(使用MyBatis Plus的updateById方法)
+            LifeFeedback updateFeedback = new LifeFeedback();
+            updateFeedback.setId(dto.getFeedbackId());
+            updateFeedback.setHandleStatus(0); // 处理中
+            updateFeedback.setStaffId(dto.getStaffId());
+            updateFeedback.setUpdateTime(new Date());
+            this.updateById(updateFeedback);
+
+            // 5. 记录日志
+            saveLog("平台回复反馈,原始反馈ID:" + dto.getFeedbackId() + ",回复ID:" + reply.getId());
+
+            return R.success("回复成功");
+        } catch (Exception e) {
+            log.error("回复反馈失败", e);
+            return R.fail("回复反馈失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public IPage<LifeFeedbackVo> getFeedbackList(Integer userId, Integer feedbackSource, int page, int size) {
+        try {
+            // 使用自定义SQL查询(已包含工作人员名称)
+            Page<LifeFeedbackVo> pageParam = new Page<>(page, size);
+            IPage<LifeFeedbackVo> voPage = lifeFeedbackMapper.selectFeedbackListWithStaff(
+                    pageParam, userId, feedbackSource, 1, null
+            );
+
+            // 为每条记录查询附件图片
+            voPage.getRecords().forEach(vo -> {
+                List<String> imgUrls = lifeImgService.getImgUrlsByFeedbackId(vo.getId());
+                vo.setImgUrlList(imgUrls);
+            });
+
+            return voPage;
+        } catch (Exception e) {
+            log.error("查询反馈列表失败", e);
+            return new Page<>(page, size);
+        }
+    }
+
+    @Override
+    public R<LifeFeedbackVo> getFeedbackDetail(Integer feedbackId) {
+        try {
+            // 1. 使用自定义SQL查询反馈详情(已包含工作人员名称)
+            LifeFeedbackVo vo = lifeFeedbackMapper.selectFeedbackDetail(feedbackId);
+            if (vo == null) {
+                return R.fail("反馈记录不存在");
+            }
+
+            // 2. 查询附件图片
+            List<String> imgUrls = lifeImgService.getImgUrlsByFeedbackId(feedbackId);
+            vo.setImgUrlList(imgUrls);
+
+            // 3. 查询平台回复列表(如果是主动反馈)
+            if (vo.getFeedbackWay() == 1) {
+                List<LifeFeedbackVo> replyList = lifeFeedbackMapper.selectPlatformReplies(
+                        vo.getUserId(), vo.getFeedbackSource(), vo.getFeedbackTime()
+                );
+                vo.setPlatformReplies(replyList);
+            }
+
+            return R.data(vo);
+        } catch (Exception e) {
+            log.error("查询反馈详情失败", e);
+            return R.fail("查询反馈详情失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<String> updateHandleStatus(Integer feedbackId, Integer handleStatus, Integer staffId) {
+        try {
+            // 使用MyBatis Plus的updateById方法
+            LifeFeedback feedback = new LifeFeedback();
+            feedback.setId(feedbackId);
+            feedback.setHandleStatus(handleStatus);
+            feedback.setStaffId(staffId);
+            feedback.setUpdateTime(new Date());
+
+            boolean result = this.updateById(feedback);
+            if (result) {
+                saveLog("更新反馈处理状态,反馈ID:" + feedbackId + ",状态:" + handleStatus);
+                return R.success("更新成功");
+            }
+            return R.fail("更新失败");
+        } catch (Exception e) {
+            log.error("更新反馈处理状态失败", e);
+            return R.fail("更新失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 保存操作日志
+     */
+    private void saveLog(String context) {
+        try {
+            LifeLog log = new LifeLog();
+            log.setContext(context);
+            log.setCreatedTime(new Date());
+            lifeLogMapper.insert(log);
+        } catch (Exception e) {
+            log.error("保存日志失败", e);
+        }
+    }
+
+    // ==================== 中台接口实现 ====================
+
+    @Override
+    public R<IPage<LifeFeedbackListVo>> getWebFeedbackList(LifeFeedbackQueryDto queryDto) {
+        try {
+            Page<LifeFeedbackListVo> pageParam = new Page<>(queryDto.getPage(), queryDto.getSize());
+            IPage<LifeFeedbackListVo> result = lifeFeedbackMapper.selectWebFeedbackList(
+                    pageParam,
+                    queryDto.getFeedbackType(),
+                    queryDto.getHandleStatus(),
+                    queryDto.getFeedbackSource(),
+                    queryDto.getFeedbackWay()
+            );
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("中台-查询意见反馈列表失败", e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<LifeFeedbackDetailVo> getWebFeedbackDetail(Integer feedbackId) {
+        try {
+            if (feedbackId == null) {
+                return R.fail("反馈ID不能为空");
+            }
+
+            // 1. 查询反馈详情
+            LifeFeedbackDetailVo detail = lifeFeedbackMapper.selectWebFeedbackDetail(feedbackId);
+            if (detail == null) {
+                return R.fail("反馈记录不存在");
+            }
+
+            // 2. 查询附件列表(图片/视频)
+            List<FeedbackAttachmentVo> attachments = new ArrayList<>();
+            List<LifeImg> imgList = lifeImgService.getByFeedbackId(feedbackId);
+            if (!CollectionUtils.isEmpty(imgList)) {
+                for (LifeImg img : imgList) {
+                    FeedbackAttachmentVo attachment = new FeedbackAttachmentVo();
+                    attachment.setId(img.getId());
+                    attachment.setFileType(img.getFileType() != null ? img.getFileType() : 1);
+                    attachment.setFileUrl(img.getImgUrl());
+                    attachment.setThumbnailUrl(img.getThumbnailUrl());
+                    attachments.add(attachment);
+                }
+            }
+            detail.setAttachments(attachments);
+
+            return R.data(detail);
+        } catch (Exception e) {
+            log.error("中台-查询反馈详情失败", e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> webReplyUser(LifeFeedbackReplyWebDto replyDto) {
+        try {
+            // 1. 参数校验
+            if (replyDto.getFeedbackId() == null) {
+                return R.fail("反馈ID不能为空");
+            }
+            if (replyDto.getContent() == null || replyDto.getContent().trim().isEmpty()) {
+                return R.fail("回复内容不能为空");
+            }
+
+            // 2. 查询原始反馈
+            LifeFeedback feedback = lifeFeedbackMapper.selectById(replyDto.getFeedbackId());
+            if (feedback == null) {
+                return R.fail("反馈记录不存在");
+            }
+
+            // 3. 记录回复日志(类型3-回复用户)
+            String logContent = replyDto.getContent();
+            if (replyDto.getUserReply() != null && !replyDto.getUserReply().trim().isEmpty()) {
+                logContent = replyDto.getContent() + "||用户回复:" + replyDto.getUserReply();
+            }
+            saveFeedbackLog(replyDto.getFeedbackId(), 3, logContent);
+
+            return R.success("回复成功");
+        } catch (Exception e) {
+            log.error("中台-回复用户失败", e);
+            return R.fail("回复失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> updateWebFeedbackStatus(LifeFeedbackStatusDto statusDto) {
+        try {
+            // 1. 参数校验
+            if (statusDto.getFeedbackId() == null) {
+                return R.fail("反馈ID不能为空");
+            }
+
+            // 2. 更新状态为已解决
+            LifeFeedback updateFeedback = new LifeFeedback();
+            updateFeedback.setId(statusDto.getFeedbackId());
+            updateFeedback.setHandleStatus(1); // 已解决
+            updateFeedback.setUpdateTime(new Date());
+
+            boolean result = this.updateById(updateFeedback);
+            if (!result) {
+                return R.fail("更新失败");
+            }
+
+            // 3. 记录日志(类型0-问题解决状态)
+            String logContent = "问题已解决";
+            saveFeedbackLog(statusDto.getFeedbackId(), 0, logContent);
+
+            return R.success("更新成功");
+        } catch (Exception e) {
+            log.error("中台-更新反馈状态失败", e);
+            return R.fail("更新失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 保存反馈操作日志
+     * @param feedbackId 反馈ID
+     * @param type 操作类型:0-问题解决状态,1-分配跟踪人员,2-创建反馈工单,3-回复用户
+     * @param context 日志内容
+     */
+    private void saveFeedbackLog(Integer feedbackId, Integer type, String context) {
+        try {
+            LifeLog lifeLog = new LifeLog();
+            lifeLog.setFeedbackId(feedbackId);
+            lifeLog.setType(String.valueOf(type));
+            lifeLog.setContext(context);
+            lifeLog.setCreatedTime(new Date());
+            lifeLog.setDeleteFlag(0);
+            lifeLogMapper.insert(lifeLog);
+        } catch (Exception e) {
+            log.error("保存反馈日志失败", e);
+        }
+    }
+}
+

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

@@ -0,0 +1,46 @@
+package shop.alien.store.service.impl;
+
+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.LifeImg;
+import shop.alien.mapper.LifeImgMapper;
+import shop.alien.store.service.LifeImgService;
+
+import java.util.List;
+
+/**
+ * 反馈图片 Service实现类
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class LifeImgServiceImpl extends ServiceImpl<LifeImgMapper, LifeImg> implements LifeImgService {
+
+    private final LifeImgMapper lifeImgMapper;
+
+    @Override
+    public List<LifeImg> getByFeedbackId(Integer feedbackId) {
+        return lifeImgMapper.selectByFeedbackId(feedbackId);
+    }
+
+    @Override
+    public boolean batchSave(List<LifeImg> imgList) {
+        if (imgList == null || imgList.isEmpty()) {
+            return false;
+        }
+        return lifeImgMapper.batchInsert(imgList) > 0;
+    }
+
+    @Override
+    public boolean removeByFeedbackId(Integer feedbackId) {
+        return lifeImgMapper.deleteByFeedbackId(feedbackId) > 0;
+    }
+
+    @Override
+    public List<String> getImgUrlsByFeedbackId(Integer feedbackId) {
+        return lifeImgMapper.selectImgUrlsByFeedbackId(feedbackId);
+    }
+}
+