Explorar o código

feat:中台意见反馈相关接口开发

penghao hai 1 semana
pai
achega
87795c128e
Modificáronse 23 ficheiros con 816 adicións e 391 borrados
  1. 12 25
      alien-entity/src/main/java/shop/alien/entity/store/LifeFeedback.java
  2. 19 15
      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/LifeFeedbackAssignDto.java
  5. 35 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackQueryDto.java
  6. 29 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackReplyWebDto.java
  7. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackStatusDto.java
  8. 29 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackAttachmentVo.java
  9. 41 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackLogVo.java
  10. 75 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackDetailVo.java
  11. 62 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackListVo.java
  12. 4 1
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackVo.java
  13. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeStaffListVo.java
  14. 28 0
      alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackMapper.java
  15. 24 0
      alien-entity/src/main/java/shop/alien/mapper/LifeLogMapper.java
  16. 108 20
      alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml
  17. 15 16
      alien-entity/src/main/resources/mapper/LifeImgMapper.xml
  18. 0 9
      alien-store/src/main/java/shop/alien/store/controller/LifeCouponController.java
  19. 48 3
      alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java
  20. 0 8
      alien-store/src/main/java/shop/alien/store/service/LifeCouponService.java
  21. 33 2
      alien-store/src/main/java/shop/alien/store/service/LifeFeedbackService.java
  22. 0 265
      alien-store/src/main/java/shop/alien/store/service/impl/LifeCouponServiceImpl.java
  23. 157 23
      alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java

+ 12 - 25
alien-entity/src/main/java/shop/alien/entity/store/LifeFeedback.java

@@ -28,15 +28,15 @@ public class LifeFeedback implements Serializable {
     @TableField("user_id")
     private Integer userId;
 
-    @ApiModelProperty(value = "反馈来源:1-用户端,2-商家端")
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
     @TableField("feedback_source")
     private Integer feedbackSource;
 
-    @ApiModelProperty(value = "反馈方式:1-主动反馈,2-平台回复")
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
     @TableField("feedback_way")
     private Integer feedbackWay;
 
-    @ApiModelProperty(value = "反馈类型:1-优化建议,2-问题")
+    @ApiModelProperty(value = "反馈类型:0-优化建议,1-问题")
     @TableField("feedback_type")
     private Integer feedbackType;
 
@@ -53,35 +53,22 @@ public class LifeFeedback implements Serializable {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date feedbackTime;
 
-    @ApiModelProperty(value = "跟进工作人员ID(关联life_sys)")
-    @TableField("follow_up_staff")
-    private Integer followUpStaff;
-
-    @ApiModelProperty(value = "处理状态:0-待处理,1-处理中,2-已完成")
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决")
     @TableField("handle_status")
     private Integer handleStatus;
 
-    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
-    @TableField("delete_flag")
-    @TableLogic
-    private Integer deleteFlag;
+    @ApiModelProperty(value = "跟进人员ID(关联life_sys表的id)")
+    @TableField("staff_id")
+    private Integer staffId;
 
     @ApiModelProperty(value = "创建时间")
-    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @TableField(value = "create_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;
+    private Date createTime;
 
-    @ApiModelProperty(value = "修改时间")
-    @TableField(value = "updated_time", fill = FieldFill.UPDATE)
+    @ApiModelProperty(value = "更新时间")
+    @TableField(value = "update_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;
+    private Date updateTime;
 }
 

+ 19 - 15
alien-entity/src/main/java/shop/alien/entity/store/LifeImg.java

@@ -28,33 +28,37 @@ public class LifeImg implements Serializable {
     @TableField("feedback_id")
     private Integer feedbackId;
 
-    @ApiModelProperty(value = "图片URL")
+    @ApiModelProperty(value = "文件类型:1-图片,2-视频")
+    @TableField("file_type")
+    private Integer fileType;
+
+    @ApiModelProperty(value = "图片/视频URL")
     @TableField("img_url")
     private String imgUrl;
 
-    @ApiModelProperty(value = "图片排序")
-    @TableField("img_sort")
-    private Integer imgSort;
+    @ApiModelProperty(value = "缩略图URL(视频的封面图)")
+    @TableField("thumbnail_url")
+    private String thumbnailUrl;
 
-    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
-    @TableField("delete_flag")
-    @TableLogic
-    private Integer deleteFlag;
+    @ApiModelProperty(value = "上传时间")
+    @TableField("upload_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date uploadTime;
 
     @ApiModelProperty(value = "创建时间")
-    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @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 createdTime;
+    private Date updateTime;
 
     @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;

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

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

+ 4 - 1
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackVo.java

@@ -42,9 +42,12 @@ public class LifeFeedbackVo implements Serializable {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date feedbackTime;
 
-    @ApiModelProperty(value = "处理状态:0-待处理,1-处理中,2-已完成")
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决")
     private Integer handleStatus;
 
+    @ApiModelProperty(value = "跟进工作人员ID")
+    private Integer staffId;
+
     @ApiModelProperty(value = "跟进工作人员姓名")
     private String staffName;
 

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

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

@@ -6,6 +6,8 @@ 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;
@@ -60,5 +62,31 @@ public interface LifeFeedbackMapper extends BaseMapper<LifeFeedback> {
             @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);
 }
 

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

+ 108 - 20
alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml

@@ -14,20 +14,17 @@
         <result column="content" property="content" />
         <result column="contact_way" property="contactWay" />
         <result column="feedback_time" property="feedbackTime" />
-        <result column="follow_up_staff" property="followUpStaff" />
+        <result column="staff_id" property="staffId" />
         <result column="handle_status" property="handleStatus" />
-        <result column="delete_flag" property="deleteFlag" />
-        <result column="created_time" property="createdTime" />
-        <result column="created_user_id" property="createdUserId" />
-        <result column="updated_time" property="updatedTime" />
-        <result column="updated_user_id" property="updatedUserId" />
+        <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, follow_up_staff, handle_status,
-        delete_flag, created_time, created_user_id, updated_time, updated_user_id
+        content, contact_way, feedback_time, staff_id, handle_status,
+        create_time, update_time
     </sql>
 
     <!-- 查询用户反馈列表(带工作人员名称) -->
@@ -41,12 +38,12 @@
             f.content,
             f.contact_way AS contactWay,
             f.feedback_time AS feedbackTime,
-            f.follow_up_staff AS followUpStaff,
+            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.follow_up_staff = s.id
-        WHERE f.delete_flag = 0
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE 1=1
         <if test="userId != null">
             AND f.user_id = #{userId}
         </if>
@@ -73,20 +70,19 @@
             f.content,
             f.contact_way AS contactWay,
             f.feedback_time AS feedbackTime,
-            f.follow_up_staff AS followUpStaff,
+            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.follow_up_staff = s.id
-        WHERE f.id = #{feedbackId} AND f.delete_flag = 0
+        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 delete_flag = 0
-        AND handle_status = 0
+        WHERE handle_status = 0
         <if test="feedbackSource != null">
             AND feedback_source = #{feedbackSource}
         </if>
@@ -102,18 +98,110 @@
             f.feedback_type AS feedbackType,
             f.content,
             f.feedback_time AS feedbackTime,
-            f.follow_up_staff AS followUpStaff,
+            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.follow_up_staff = s.id
+        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 = 2
+        AND f.feedback_way = 1
         AND f.feedback_time >= #{startTime}
-        AND f.delete_flag = 0
         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>
 

+ 15 - 16
alien-entity/src/main/resources/mapper/LifeImgMapper.xml

@@ -8,19 +8,20 @@
     <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="img_sort" property="imgSort" />
-        <result column="delete_flag" property="deleteFlag" />
-        <result column="created_time" property="createdTime" />
+        <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_time" property="updatedTime" />
         <result column="updated_user_id" property="updatedUserId" />
     </resultMap>
 
     <!-- 基础字段 -->
     <sql id="Base_Column_List">
-        id, feedback_id, img_url, img_sort, 
-        delete_flag, created_time, created_user_id, updated_time, updated_user_id
+        id, feedback_id, file_type, img_url, thumbnail_url,
+        upload_time, create_time, update_time, created_user_id, updated_user_id
     </sql>
 
     <!-- 根据反馈ID查询图片列表 -->
@@ -29,22 +30,22 @@
             <include refid="Base_Column_List" />
         FROM life_img
         WHERE feedback_id = #{feedbackId}
-        AND delete_flag = 0
-        ORDER BY img_sort ASC
+        ORDER BY id ASC
     </select>
 
     <!-- 批量插入图片 -->
     <insert id="batchInsert" parameterType="java.util.List">
         INSERT INTO life_img (
-            feedback_id, img_url, img_sort, 
-            delete_flag, created_time, created_user_id
+            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.imgSort},
-                0,
+                #{item.thumbnailUrl},
+                NOW(),
                 NOW(),
                 #{item.createdUserId}
             )
@@ -53,8 +54,7 @@
 
     <!-- 根据反馈ID删除图片 -->
     <delete id="deleteByFeedbackId">
-        UPDATE life_img 
-        SET delete_flag = 1
+        DELETE FROM life_img 
         WHERE feedback_id = #{feedbackId}
     </delete>
 
@@ -63,8 +63,7 @@
         SELECT img_url
         FROM life_img
         WHERE feedback_id = #{feedbackId}
-        AND delete_flag = 0
-        ORDER BY img_sort ASC
+        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);
-    }
 }

+ 48 - 3
alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java

@@ -1,13 +1,17 @@
 package shop.alien.store.controller;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
-import io.swagger.annotations.*;
+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.FeedbackReplyDto;
-import shop.alien.entity.store.dto.LifeFeedbackDto;
+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;
 
@@ -82,5 +86,46 @@ public class LifeFeedbackController {
                 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);
 }

+ 33 - 2
alien-store/src/main/java/shop/alien/store/service/LifeFeedbackService.java

@@ -4,8 +4,9 @@ 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.FeedbackReplyDto;
-import shop.alien.entity.store.dto.LifeFeedbackDto;
+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;
 
 /**
@@ -52,5 +53,35 @@ public interface LifeFeedbackService extends IService<LifeFeedback> {
      * @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);
 }
 

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

+ 157 - 23
alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java

@@ -13,14 +13,14 @@ 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.FeedbackReplyDto;
-import shop.alien.entity.store.dto.LifeFeedbackDto;
-import shop.alien.entity.store.vo.LifeFeedbackVo;
+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;
 
@@ -58,9 +58,8 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
             LifeFeedback feedback = new LifeFeedback();
             BeanUtils.copyProperties(dto, feedback);
             feedback.setFeedbackTime(new Date());
-            feedback.setHandleStatus(0); // 待处理
-            feedback.setCreatedTime(new Date());
-            feedback.setCreatedUserId(dto.getUserId());
+            feedback.setHandleStatus(0); // 处理中
+            feedback.setCreateTime(new Date());
 
             boolean saveResult = this.save(feedback);
             if (!saveResult) {
@@ -70,21 +69,21 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
             // 3. 保存附件图片(使用批量插入)
             if (!CollectionUtils.isEmpty(dto.getImgUrlList())) {
                 List<LifeImg> imgList = new java.util.ArrayList<>();
-                int sort = 1;
                 for (String imgUrl : dto.getImgUrlList()) {
                     LifeImg img = new LifeImg();
                     img.setFeedbackId(feedback.getId());
                     img.setImgUrl(imgUrl);
-                    img.setImgSort(sort++);
-                    img.setCreatedTime(new Date());
+                    img.setFileType(1); // 默认图片
+                    img.setUploadTime(new Date());
+                    img.setCreateTime(new Date());
                     img.setCreatedUserId(dto.getUserId());
                     imgList.add(img);
                 }
                 lifeImgService.batchSave(imgList);
             }
 
-            // 4. 记录日志
-            saveLog("用户提交反馈,ID:" + feedback.getId());
+            // 4. 记录日志 - 创建反馈工单
+            saveFeedbackLog(feedback.getId(), 2, "商家主动反馈/AI识别");
 
             return R.success("提交成功");
         } catch (Exception e) {
@@ -117,14 +116,13 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
             LifeFeedback reply = new LifeFeedback();
             reply.setUserId(originalFeedback.getUserId());
             reply.setFeedbackSource(originalFeedback.getFeedbackSource());
-            reply.setFeedbackWay(2); // 平台回复
+            reply.setFeedbackWay(1); // 平台回复(AI识别方式)
             reply.setFeedbackType(originalFeedback.getFeedbackType());
             reply.setContent(dto.getContent());
             reply.setFeedbackTime(new Date());
-            reply.setFollowUpStaff(dto.getStaffId());
-            reply.setHandleStatus(2); // 已完成
-            reply.setCreatedTime(new Date());
-            reply.setCreatedUserId(dto.getStaffId());
+            reply.setStaffId(dto.getStaffId());
+            reply.setHandleStatus(1); // 已解决
+            reply.setCreateTime(new Date());
 
             boolean saveResult = this.save(reply);
             if (!saveResult) {
@@ -134,10 +132,9 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
             // 4. 更新原始反馈的处理状态和跟进人员(使用MyBatis Plus的updateById方法)
             LifeFeedback updateFeedback = new LifeFeedback();
             updateFeedback.setId(dto.getFeedbackId());
-            updateFeedback.setHandleStatus(1); // 处理中
-            updateFeedback.setFollowUpStaff(dto.getStaffId());
-            updateFeedback.setUpdatedTime(new Date());
-            updateFeedback.setUpdatedUserId(dto.getStaffId());
+            updateFeedback.setHandleStatus(0); // 处理中
+            updateFeedback.setStaffId(dto.getStaffId());
+            updateFeedback.setUpdateTime(new Date());
             this.updateById(updateFeedback);
 
             // 5. 记录日志
@@ -207,9 +204,8 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
             LifeFeedback feedback = new LifeFeedback();
             feedback.setId(feedbackId);
             feedback.setHandleStatus(handleStatus);
-            feedback.setFollowUpStaff(staffId);
-            feedback.setUpdatedTime(new Date());
-            feedback.setUpdatedUserId(staffId);
+            feedback.setStaffId(staffId);
+            feedback.setUpdateTime(new Date());
 
             boolean result = this.updateById(feedback);
             if (result) {
@@ -236,5 +232,143 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
             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);
+        }
+    }
 }