xucaiqin 1 тиждень тому
батько
коміт
8c05f1dbe2
26 змінених файлів з 1329 додано та 3 видалено
  1. 44 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsComplaintController.java
  2. 44 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/app/ComplaintsController.java
  3. 13 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/TComplaintAttachmentMapper.java
  4. 10 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/TComplaintHandleLogMapper.java
  5. 13 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/TComplaintMapper.java
  6. 158 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/TComplaint.java
  7. 96 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/TComplaintAttachment.java
  8. 56 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/TComplaintHandleLog.java
  9. 24 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintAppListReqVo.java
  10. 53 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintAppSubmitReqVo.java
  11. 42 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintManagePageReqVo.java
  12. 26 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintProcessReqVo.java
  13. 35 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintAppDetailResVo.java
  14. 42 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintAppItemResVo.java
  15. 34 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintAttachmentResVo.java
  16. 46 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintDetailResVo.java
  17. 42 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintHandleLogResVo.java
  18. 45 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintManageItemResVo.java
  19. 23 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintTypeOptionResVo.java
  20. 382 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsComplaintService.java
  21. 16 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/TComplaintAttachmentService.java
  22. 11 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/TComplaintHandleLogService.java
  23. 16 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/TComplaintService.java
  24. 3 3
      sckw-modules/sckw-system/src/main/resources/bootstrap.yml
  25. 25 0
      sckw-modules/sckw-system/src/main/resources/mapper/TComplaintAttachmentMapper.xml
  26. 30 0
      sckw-modules/sckw-system/src/main/resources/mapper/TComplaintMapper.xml

+ 44 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsComplaintController.java

@@ -0,0 +1,44 @@
+package com.sckw.system.controller;
+
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.core.web.response.result.PageDataResult;
+import com.sckw.system.model.vo.req.ComplaintManagePageReqVo;
+import com.sckw.system.model.vo.req.ComplaintProcessReqVo;
+import com.sckw.system.model.vo.res.ComplaintDetailResVo;
+import com.sckw.system.model.vo.res.ComplaintManageItemResVo;
+import com.sckw.system.service.KwsComplaintService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/kwsComplaint")
+@Tag(name = "投诉管理(PC)")
+@RequiredArgsConstructor
+public class KwsComplaintController {
+
+    private final KwsComplaintService kwsComplaintService;
+
+    @PostMapping("/page")
+    @Operation(summary = "投诉分页查询", description = "PC端投诉管理列表分页查询")
+    public BaseResult<PageDataResult<ComplaintManageItemResVo>> page(@RequestBody ComplaintManagePageReqVo reqVo) {
+        return BaseResult.success(kwsComplaintService.manageComplaintPage(reqVo));
+    }
+
+    @GetMapping("/detail")
+    @Operation(summary = "投诉详情", description = "PC端投诉详情(包含附件、处理记录)")
+    public BaseResult<ComplaintDetailResVo> detail(@RequestParam("id") Long id) {
+        return BaseResult.success(kwsComplaintService.manageComplaintDetail(id));
+    }
+
+    @PostMapping("/process")
+    @Operation(summary = "处理投诉", description = "PC端处理投诉,填写处理回复并更新状态")
+    public BaseResult<Boolean> process(@RequestBody @Valid ComplaintProcessReqVo reqVo) {
+        return BaseResult.success(kwsComplaintService.processComplaint(reqVo));
+    }
+
+
+}
+

+ 44 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/app/ComplaintsController.java

@@ -0,0 +1,44 @@
+package com.sckw.system.controller.app;
+
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.core.web.response.result.PageDataResult;
+import com.sckw.system.model.vo.req.ComplaintAppListReqVo;
+import com.sckw.system.model.vo.req.ComplaintAppSubmitReqVo;
+import com.sckw.system.model.vo.res.ComplaintAppDetailResVo;
+import com.sckw.system.model.vo.res.ComplaintAppItemResVo;
+import com.sckw.system.model.vo.res.ComplaintDetailResVo;
+import com.sckw.system.service.KwsComplaintService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/complaint")
+@Tag(name = "投诉相关接口(App)")
+@RequiredArgsConstructor
+public class ComplaintsController {
+
+    private final KwsComplaintService kwsComplaintService;
+
+    @PostMapping("/submit")
+    @Operation(summary = "提交投诉", description = "司机提交投诉,可上传单张图片作为证据")
+    public BaseResult<ComplaintDetailResVo> submit(@RequestBody @Valid ComplaintAppSubmitReqVo reqVo) {
+        return BaseResult.success(kwsComplaintService.submitAppComplaint(reqVo));
+    }
+
+    @PostMapping("/list")
+    @Operation(summary = "投诉列表", description = "分页查询当前用户投诉列表,支持按状态筛选")
+    public BaseResult<PageDataResult<ComplaintAppItemResVo>> list(@RequestBody @Valid ComplaintAppListReqVo reqVo) {
+        return BaseResult.success(kwsComplaintService.appComplaintPage(reqVo));
+    }
+
+    @GetMapping("/detail")
+    @Operation(summary = "投诉详情", description = "根据投诉ID查询详情")
+    public BaseResult<ComplaintAppDetailResVo> detail(@RequestParam("id") Long id) {
+        return BaseResult.success(kwsComplaintService.appComplaintDetail(id));
+    }
+
+}
+

+ 13 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/TComplaintAttachmentMapper.java

@@ -0,0 +1,13 @@
+package com.sckw.system.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.system.model.TComplaintAttachment;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* @date 2026-06-06 15:14:09
+* @author xucaiqin
+*/
+@Mapper
+public interface TComplaintAttachmentMapper extends BaseMapper<TComplaintAttachment> {
+}

+ 10 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/TComplaintHandleLogMapper.java

@@ -0,0 +1,10 @@
+package com.sckw.system.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.system.model.TComplaintHandleLog;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface TComplaintHandleLogMapper extends BaseMapper<TComplaintHandleLog> {
+}
+

+ 13 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/TComplaintMapper.java

@@ -0,0 +1,13 @@
+package com.sckw.system.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.system.model.TComplaint;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* @date 2026-06-06 15:14:09
+* @author xucaiqin
+*/
+@Mapper
+public interface TComplaintMapper extends BaseMapper<TComplaint> {
+}

+ 158 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/TComplaint.java

@@ -0,0 +1,158 @@
+package com.sckw.system.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 投诉主表
+* @date 2026-06-06 15:14:09
+* @author xucaiqin
+*/
+@Schema(description="投诉主表")
+@Data
+@TableName(value = "t_complaint")
+public class TComplaint {
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description="")
+    private Long id;
+
+    /**
+     * 投诉编号,如CT20260603001
+     */
+    @TableField(value = "complaint_no")
+    @Schema(description="投诉编号,如CT20260603001")
+    private String complaintNo;
+
+    /**
+     * 来源渠道:1司机App 2扫码
+     */
+    @TableField(value = "channel")
+    @Schema(description="来源渠道:1司机App 2扫码")
+    private Integer channel;
+
+    /**
+     * 投诉类型编码
+     */
+    @TableField(value = "type_code")
+    @Schema(description="投诉类型编码")
+    private Integer typeCode;
+
+    /**
+     * 对象名称快照
+     */
+    @TableField(value = "target_name")
+    @Schema(description="对象名称快照")
+    private String targetName;
+
+
+    /**
+     * 投诉人ID(若有)
+     */
+    @TableField(value = "submitter_id")
+    @Schema(description="投诉人ID(若有)")
+    private Long submitterId;
+
+    /**
+     * 投诉人名称快照
+     */
+    @TableField(value = "submitter_name")
+    @Schema(description="投诉人名称快照")
+    private String submitterName;
+
+    /**
+     * 是否匿名:1是0否
+     */
+    @TableField(value = "is_anonymous")
+    @Schema(description="是否匿名:1是0否")
+    private Boolean isAnonymous;
+
+    /**
+     * 投诉内容
+     */
+    @TableField(value = "content")
+    @Schema(description="投诉内容")
+    private String content;
+
+    /**
+     * 处理状态:0待处理 1已处理
+     */
+    @TableField(value = "`status`")
+    @Schema(description="处理状态:0待处理 1已处理")
+    private Integer status;
+
+    /**
+     * 发生时间
+     */
+    @TableField(value = "occur_time")
+    @Schema(description="发生时间")
+    private LocalDateTime occurTime;
+
+    /**
+     * 处理完成时间
+     */
+    @TableField(value = "processed_at")
+    @Schema(description="处理完成时间")
+    private LocalDateTime processedAt;
+
+    /**
+     * 处理人ID(后台用户)
+     */
+    @TableField(value = "processor_id")
+    @Schema(description="处理人ID(后台用户)")
+    private Long processorId;
+
+    /**
+     * 处理人名称快照
+     */
+    @TableField(value = "processor_name")
+    @Schema(description="处理人名称快照")
+    private String processorName;
+
+    /**
+     * 处理回复(PC端“处理”填写)
+     */
+    @TableField(value = "process_reply")
+    @Schema(description="处理回复(PC端“处理”填写)")
+    private String processReply;
+
+    /**
+     * 创建人
+     */
+    @TableField(value = "create_by")
+    @Schema(description="创建人")
+    private Long createBy;
+
+    /**
+     * 创建时间
+     */
+    @TableField(value = "create_time")
+    @Schema(description="创建时间")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新人
+     */
+    @TableField(value = "update_by")
+    @Schema(description="更新人")
+    private Long updateBy;
+
+    /**
+     * 更新时间
+     */
+    @TableField(value = "update_time")
+    @Schema(description="更新时间")
+    private LocalDateTime updateTime;
+
+    /**
+     * 删除标识(0正常/1删除)
+     */
+    @TableField(value = "del_flag")
+    @Schema(description="删除标识(0正常/1删除)")
+    private Integer delFlag;
+}

+ 96 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/TComplaintAttachment.java

@@ -0,0 +1,96 @@
+package com.sckw.system.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.time.LocalDateTime;
+import lombok.Data;
+
+/**
+* @date 2026-06-06 15:14:09
+* @author xucaiqin
+*/
+/**
+ * 投诉附件表
+ */
+@Schema(description="投诉附件表")
+@Data
+@TableName(value = "t_complaint_attachment")
+public class TComplaintAttachment {
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description="")
+    private Long id;
+
+    @TableField(value = "complaint_id")
+    @Schema(description="")
+    private Long complaintId;
+
+    /**
+     * 附件URL/对象存储地址
+     */
+    @TableField(value = "file_url")
+    @Schema(description="附件URL/对象存储地址")
+    private String fileUrl;
+
+    @TableField(value = "file_name")
+    @Schema(description="")
+    private String fileName;
+
+    /**
+     * image/jpeg 等
+     */
+    @TableField(value = "file_type")
+    @Schema(description="image/jpeg 等")
+    private String fileType;
+
+    /**
+     * 字节
+     */
+    @TableField(value = "file_size")
+    @Schema(description="字节")
+    private Long fileSize;
+
+    /**
+     * 排序,单张就固定1
+     */
+    @TableField(value = "sort_no")
+    @Schema(description="排序,单张就固定1")
+    private Integer sortNo;
+
+    /**
+     * 创建人
+     */
+    @TableField(value = "create_by")
+    @Schema(description="创建人")
+    private Long createBy;
+
+    /**
+     * 创建时间
+     */
+    @TableField(value = "create_time")
+    @Schema(description="创建时间")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新人
+     */
+    @TableField(value = "update_by")
+    @Schema(description="更新人")
+    private Long updateBy;
+
+    /**
+     * 更新时间
+     */
+    @TableField(value = "update_time")
+    @Schema(description="更新时间")
+    private LocalDateTime updateTime;
+
+    /**
+     * 删除标识(0正常/1删除)
+     */
+    @TableField(value = "del_flag")
+    @Schema(description="删除标识(0正常/1删除)")
+    private Integer delFlag;
+}

+ 56 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/TComplaintHandleLog.java

@@ -0,0 +1,56 @@
+package com.sckw.system.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "投诉处理记录")
+@Data
+@TableName(value = "t_complaint_handle_log")
+public class TComplaintHandleLog {
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField(value = "complaint_id")
+    private Long complaintId;
+
+    @TableField(value = "action")
+    private String action;
+
+    @TableField(value = "from_status")
+    private Integer fromStatus;
+
+    @TableField(value = "to_status")
+    private Integer toStatus;
+
+    @TableField(value = "operator_id")
+    private Long operatorId;
+
+    @TableField(value = "operator_name")
+    private String operatorName;
+
+    @TableField(value = "remark")
+    private String remark;
+
+    @TableField(value = "create_by")
+    private Long createBy;
+
+    @TableField(value = "create_time")
+    private LocalDateTime createTime;
+
+    @TableField(value = "update_by")
+    private Long updateBy;
+
+    @TableField(value = "update_time")
+    private LocalDateTime updateTime;
+
+    @TableField(value = "del_flag")
+    private Integer delFlag;
+}
+

+ 24 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintAppListReqVo.java

@@ -0,0 +1,24 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@Schema(description = "App投诉列表查询请求")
+public class ComplaintAppListReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "处理状态:0待处理 1已处理;不传/传-1表示全部", example = "0")
+    private Integer status = -1;
+
+    @Schema(description = "当前页码", example = "1")
+    private Integer pageNum = 1;
+
+    @Schema(description = "每页数量", example = "10")
+    private Integer pageSize = 10;
+}

+ 53 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintAppSubmitReqVo.java

@@ -0,0 +1,53 @@
+package com.sckw.system.model.vo.req;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@Schema(description = "App提交投诉请求")
+public class ComplaintAppSubmitReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @NotNull(message = "投诉类型不能为空")
+    @Schema(description = "投诉类型编码(字典值)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    private Integer typeCode;
+
+    @Schema(description = "发生时间", example = "10001")
+    @NotNull(message = "发生时间不能为空")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime occurTime;
+
+    @NotBlank(message = "投诉对象不能为空")
+    @Schema(description = "投诉对象名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    private String targetName;
+
+    @Schema(description = "是否匿名:true/false", example = "true")
+    private Boolean isAnonymous = false;
+
+    @NotBlank(message = "投诉内容不能为空")
+    @Schema(description = "投诉内容", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String content;
+
+    @Schema(description = "附件URL(单张图片)", example = "https://xxx/1.jpg")
+    private String fileUrl;
+
+    @Schema(description = "附件名称", example = "1.jpg")
+    private String fileName;
+
+    @Schema(description = "附件类型", example = "image/jpeg")
+    private String fileType;
+
+    @Schema(description = "附件大小(字节)", example = "12345")
+    private Long fileSize;
+}

+ 42 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintManagePageReqVo.java

@@ -0,0 +1,42 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@Schema(description = "PC投诉管理分页查询请求")
+public class ComplaintManagePageReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "投诉编号(支持模糊)", example = "CT20260603")
+    private String complaintNo;
+
+    @Schema(description = "投诉类型编码(字典值)", example = "2")
+    private Integer typeCode;
+
+    @Schema(description = "投诉对象", example = "李四")
+    private String targetName;
+
+    @Schema(description = "投诉人", example = "张三")
+    private String submitterKeyword;
+
+    @Schema(description = "开始日期(yyyy-MM-dd)", example = "2026-06-01")
+    private String startDate;
+
+    @Schema(description = "结束日期(yyyy-MM-dd)", example = "2026-06-06")
+    private String endDate;
+
+    @Schema(description = "处理状态:0待处理 1已处理;不传/传-1表示全部", example = "0")
+    private Integer status = -1;
+
+    @Schema(description = "当前页码", example = "1")
+    private Integer pageNum = 1;
+
+    @Schema(description = "每页数量", example = "10")
+    private Integer pageSize = 10;
+}

+ 26 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintProcessReqVo.java

@@ -0,0 +1,26 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@Schema(description = "PC处理投诉请求")
+public class ComplaintProcessReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @NotNull(message = "投诉ID不能为空")
+    @Schema(description = "投诉ID", required = true, example = "1")
+    private Long id;
+
+    @NotBlank(message = "处理回复不能为空")
+    @Schema(description = "处理回复")
+    private String processReply;
+}
+

+ 35 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintAppDetailResVo.java

@@ -0,0 +1,35 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@Schema(description = "App投诉详情")
+public class ComplaintAppDetailResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+    private String complaintNo;
+    private Integer typeCode;
+    private String typeName;
+    private String targetName;
+    private Boolean isAnonymous;
+    private String submitterName;
+    private String content;
+    private Integer status;
+    private String statusName;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime occurTime;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime processedAt;
+    private String processReply;
+    private List<ComplaintAttachmentResVo> attachments;
+}

+ 42 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintAppItemResVo.java

@@ -0,0 +1,42 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@Schema(description = "App投诉列表项")
+public class ComplaintAppItemResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "投诉ID", example = "1")
+    private Long id;
+
+    @Schema(description = "投诉编号", example = "CT20260603000001")
+    private String complaintNo;
+
+    @Schema(description = "投诉类型编码(字典值)", example = "2")
+    private Integer typeCode;
+
+    @Schema(description = "投诉类型名称", example = "服务态度")
+    private String typeName;
+
+    @Schema(description = "投诉对象", example = "李四")
+    private String targetName;
+
+    @Schema(description = "发生时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime occurTime;
+
+    @Schema(description = "处理状态:0待处理 1已处理", example = "0")
+    private Integer status;
+
+    @Schema(description = "处理状态名称", example = "待处理")
+    private String statusName;
+}

+ 34 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintAttachmentResVo.java

@@ -0,0 +1,34 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@Schema(description = "投诉附件")
+public class ComplaintAttachmentResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "附件ID", example = "1")
+    private Long id;
+
+    @Schema(description = "附件URL", example = "https://xxx/1.jpg")
+    private String fileUrl;
+
+    @Schema(description = "附件名称", example = "1.jpg")
+    private String fileName;
+
+    @Schema(description = "附件类型", example = "image/jpeg")
+    private String fileType;
+
+    @Schema(description = "附件大小(字节)", example = "12345")
+    private Long fileSize;
+
+    @Schema(description = "排序", example = "1")
+    private Integer sortNo;
+}
+

+ 46 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintDetailResVo.java

@@ -0,0 +1,46 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@Schema(description = "投诉详情")
+public class ComplaintDetailResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+    private String complaintNo;
+    private Integer channel;
+    private Integer typeCode;
+    private String typeName;
+
+    private String targetName;
+
+    private Long submitterId;
+    private String submitterName;
+    private Boolean isAnonymous;
+
+    private String content;
+
+    private Integer status;
+    private String statusName;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime occurTime;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime processedAt;
+    private Long processorId;
+    private String processorName;
+    private String processReply;
+
+    private List<ComplaintAttachmentResVo> attachments;
+    private List<ComplaintHandleLogResVo> handleLogs;
+}

+ 42 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintHandleLogResVo.java

@@ -0,0 +1,42 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@Schema(description = "投诉处理记录")
+public class ComplaintHandleLogResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "记录ID", example = "1")
+    private Long id;
+
+    @Schema(description = "动作", example = "PROCESS")
+    private String action;
+
+    @Schema(description = "原状态", example = "0")
+    private Integer fromStatus;
+
+    @Schema(description = "新状态", example = "1")
+    private Integer toStatus;
+
+    @Schema(description = "操作人ID", example = "1001")
+    private Long operatorId;
+
+    @Schema(description = "操作人名称", example = "管理员")
+    private String operatorName;
+
+    @Schema(description = "说明/回复")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createTime;
+}

+ 45 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintManageItemResVo.java

@@ -0,0 +1,45 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@Schema(description = "PC投诉管理列表项")
+public class ComplaintManageItemResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "投诉ID", example = "1")
+    private Long id;
+
+    @Schema(description = "投诉编号", example = "CT20260603000001")
+    private String complaintNo;
+
+    @Schema(description = "投诉类型编码(字典值)", example = "2")
+    private Integer typeCode;
+
+    @Schema(description = "投诉类型名称", example = "服务态度")
+    private String typeName;
+
+    @Schema(description = "投诉对象", example = "李四")
+    private String targetName;
+
+    @Schema(description = "投诉人显示名(匿名显示为“匿名”)", example = "匿名")
+    private String submitterNameDisplay;
+
+    @Schema(description = "发生时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime occurTime;
+
+    @Schema(description = "处理状态:0待处理 1已处理", example = "0")
+    private Integer status;
+
+    @Schema(description = "处理状态名称", example = "待处理")
+    private String statusName;
+}

+ 23 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintTypeOptionResVo.java

@@ -0,0 +1,23 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@AllArgsConstructor
+@Schema(description = "投诉类型选项")
+public class ComplaintTypeOptionResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "类型编码(字典值)", example = "2")
+    private Integer code;
+
+    @Schema(description = "类型名称", example = "服务态度")
+    private String name;
+}

+ 382 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsComplaintService.java

@@ -0,0 +1,382 @@
+package com.sckw.system.service;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.sckw.core.exception.SystemException;
+import com.sckw.core.model.constant.Global;
+import com.sckw.core.web.constant.HttpStatus;
+import com.sckw.core.web.context.LoginUserHolder;
+import com.sckw.core.web.model.LoginUserInfo;
+import com.sckw.core.web.response.result.PageDataResult;
+import com.sckw.system.model.TComplaint;
+import com.sckw.system.model.TComplaintAttachment;
+import com.sckw.system.model.TComplaintHandleLog;
+import com.sckw.system.model.vo.req.ComplaintAppListReqVo;
+import com.sckw.system.model.vo.req.ComplaintAppSubmitReqVo;
+import com.sckw.system.model.vo.req.ComplaintManagePageReqVo;
+import com.sckw.system.model.vo.req.ComplaintProcessReqVo;
+import com.sckw.system.model.vo.res.*;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
+
+
+@Service
+@RequiredArgsConstructor
+public class KwsComplaintService {
+
+    private static final DateTimeFormatter DATE_NO_DASH = DateTimeFormatter.ofPattern("yyyyMMdd");
+
+    private final TComplaintService tComplaintService;
+    private final TComplaintAttachmentService tComplaintAttachmentService;
+    private final TComplaintHandleLogService tComplaintHandleLogService;
+
+    @Transactional(rollbackFor = Exception.class)
+    public ComplaintDetailResVo submitAppComplaint(ComplaintAppSubmitReqVo reqVo) {
+        LoginUserInfo loginUserInfo = LoginUserHolder.get();
+
+        LocalDateTime now = LocalDateTime.now();
+
+        TComplaint complaint = new TComplaint();
+        complaint.setComplaintNo(buildComplaintNo(now));
+        complaint.setChannel(1);
+        complaint.setTypeCode(reqVo.getTypeCode());
+        complaint.setTargetName(reqVo.getTargetName());
+        if (Objects.nonNull(loginUserInfo)) {
+            complaint.setSubmitterId(loginUserInfo.getId());
+            complaint.setSubmitterName(loginUserInfo.getUserName());
+        }
+
+        complaint.setIsAnonymous(Boolean.TRUE.equals(reqVo.getIsAnonymous()));
+        complaint.setContent(reqVo.getContent());
+        complaint.setStatus(0);
+        complaint.setOccurTime(reqVo.getOccurTime());
+
+        complaint.setCreateBy(LoginUserHolder.getUserId());
+        complaint.setCreateTime(now);
+        complaint.setDelFlag(Global.UN_DELETED);
+
+        if (!tComplaintService.save(complaint)) {
+            throw new SystemException(HttpStatus.CRUD_FAIL_CODE, "提交投诉失败");
+        }
+
+        if (StrUtil.isNotBlank(reqVo.getFileUrl())) {
+            TComplaintAttachment attachment = new TComplaintAttachment();
+            attachment.setComplaintId(complaint.getId());
+            attachment.setFileUrl(reqVo.getFileUrl());
+            attachment.setFileName(reqVo.getFileName());
+            attachment.setFileType(reqVo.getFileType());
+            attachment.setFileSize(reqVo.getFileSize());
+            attachment.setSortNo(1);
+            attachment.setCreateBy(LoginUserHolder.getUserId());
+            attachment.setCreateTime(now);
+            attachment.setDelFlag(Global.UN_DELETED);
+            if (!tComplaintAttachmentService.save(attachment)) {
+                throw new SystemException(HttpStatus.CRUD_FAIL_CODE, "保存投诉附件失败");
+            }
+        }
+
+        return buildComplaintDetail(complaint, true);
+    }
+
+    public PageDataResult<ComplaintAppItemResVo> appComplaintPage(ComplaintAppListReqVo reqVo) {
+        Page<TComplaint> page = new Page<>(reqVo.getPageNum(), reqVo.getPageSize());
+        LambdaQueryWrapper<TComplaint> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TComplaint::getDelFlag, Global.UN_DELETED)
+                .eq(TComplaint::getCreateBy, LoginUserHolder.getUserId())
+                .orderByDesc(TComplaint::getCreateTime);
+
+        if (reqVo.getStatus() != null && reqVo.getStatus() >= 0) {
+            wrapper.eq(TComplaint::getStatus, reqVo.getStatus());
+        }
+
+        Page<TComplaint> complaintPage = tComplaintService.page(page, wrapper);
+        return PageDataResult.of(page, complaintPage.getRecords().stream().map(this::toAppItem).collect(Collectors.toList()));
+    }
+
+    public ComplaintAppDetailResVo appComplaintDetail(Long complaintId) {
+        TComplaint complaint = tComplaintService.getById(complaintId);
+        if (Objects.isNull(complaint) || !Objects.equals(complaint.getDelFlag(), Global.UN_DELETED)) {
+            throw new SystemException(HttpStatus.QUERY_FAIL_CODE, "投诉记录不存在");
+        }
+        ComplaintDetailResVo detail = buildComplaintDetail(complaint, true);
+        return toAppDetail(detail);
+    }
+
+    public PageDataResult<ComplaintManageItemResVo> manageComplaintPage(ComplaintManagePageReqVo reqVo) {
+        Page<TComplaint> page = new Page<>(reqVo.getPageNum(), reqVo.getPageSize());
+        LambdaQueryWrapper<TComplaint> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TComplaint::getDelFlag, Global.UN_DELETED)
+                .orderByDesc(TComplaint::getOccurTime);
+
+        if (StrUtil.isNotBlank(reqVo.getComplaintNo())) {
+            wrapper.like(TComplaint::getComplaintNo, reqVo.getComplaintNo().trim());
+        }
+        if (reqVo.getTypeCode() != null) {
+            wrapper.eq(TComplaint::getTypeCode, reqVo.getTypeCode());
+        }
+        if (StrUtil.isNotBlank(reqVo.getTargetName())) {
+            wrapper.like(TComplaint::getTargetName, reqVo.getTargetName().trim());
+        }
+        if (StrUtil.isNotBlank(reqVo.getSubmitterKeyword())) {
+            wrapper.eq(TComplaint::getIsAnonymous, 0);
+            wrapper.like(TComplaint::getSubmitterName, reqVo.getSubmitterKeyword().trim());
+        }
+        if (reqVo.getStatus() != null && reqVo.getStatus() >= 0) {
+            wrapper.eq(TComplaint::getStatus, reqVo.getStatus());
+        }
+
+        LocalDateTime startTime = parseStartDate(reqVo.getStartDate());
+        LocalDateTime endTimeExclusive = parseEndDateExclusive(reqVo.getEndDate());
+        if (startTime != null) {
+            wrapper.ge(TComplaint::getOccurTime, startTime);
+        }
+        if (endTimeExclusive != null) {
+            wrapper.lt(TComplaint::getOccurTime, endTimeExclusive);
+        }
+
+        Page<TComplaint> complaintPage = tComplaintService.page(page, wrapper);
+        return PageDataResult.of(page, complaintPage.getRecords().stream().map(this::toManageItem).collect(Collectors.toList()));
+    }
+
+    public ComplaintDetailResVo manageComplaintDetail(Long complaintId) {
+        TComplaint complaint = tComplaintService.getById(complaintId);
+        if (Objects.isNull(complaint) || !Objects.equals(complaint.getDelFlag(), Global.UN_DELETED)) {
+            throw new SystemException(HttpStatus.QUERY_FAIL_CODE, "投诉记录不存在");
+        }
+        return buildComplaintDetail(complaint, true);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean processComplaint(ComplaintProcessReqVo reqVo) {
+        TComplaint complaint = tComplaintService.getById(reqVo.getId());
+        if (Objects.isNull(complaint) || !Objects.equals(complaint.getDelFlag(), Global.UN_DELETED)) {
+            throw new SystemException(HttpStatus.QUERY_FAIL_CODE, "投诉记录不存在");
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        Integer fromStatus = complaint.getStatus();
+
+        Long operatorId = LoginUserHolder.getUserId();
+        String operatorName = LoginUserHolder.getUserName();
+
+        boolean updated = tComplaintService.lambdaUpdate()
+                .eq(TComplaint::getId, reqVo.getId())
+                .set(TComplaint::getStatus, 1)
+                .set(TComplaint::getProcessedAt, now)
+                .set(TComplaint::getProcessorId, operatorId)
+                .set(TComplaint::getProcessorName, operatorName)
+                .set(TComplaint::getProcessReply, reqVo.getProcessReply())
+                .set(TComplaint::getUpdateBy, operatorId)
+                .set(TComplaint::getUpdateTime, now)
+                .update();
+
+        if (!updated) {
+            throw new SystemException(HttpStatus.CRUD_FAIL_CODE, "处理投诉失败");
+        }
+
+        TComplaintHandleLog log = new TComplaintHandleLog();
+        log.setComplaintId(reqVo.getId());
+        log.setAction("PROCESS");
+        log.setFromStatus(fromStatus == null ? 0 : fromStatus);
+        log.setToStatus(1);
+        log.setOperatorId(operatorId);
+        log.setOperatorName(operatorName);
+        log.setRemark(reqVo.getProcessReply());
+        log.setCreateBy(operatorId);
+        log.setCreateTime(now);
+        log.setUpdateBy(operatorId);
+        log.setUpdateTime(now);
+        log.setDelFlag(Global.UN_DELETED);
+        if (!tComplaintHandleLogService.save(log)) {
+            throw new SystemException(HttpStatus.CRUD_FAIL_CODE, "保存处理记录失败");
+        }
+
+        return true;
+    }
+
+
+    private ComplaintAppItemResVo toAppItem(TComplaint complaint) {
+        ComplaintAppItemResVo vo = new ComplaintAppItemResVo();
+        vo.setId(complaint.getId());
+        vo.setComplaintNo(complaint.getComplaintNo());
+        vo.setTypeCode(complaint.getTypeCode());
+        vo.setTypeName(typeName(complaint.getTypeCode()));
+        vo.setTargetName(complaint.getTargetName());
+        vo.setOccurTime(complaint.getOccurTime());
+        vo.setStatus(complaint.getStatus());
+        vo.setStatusName(statusName(complaint.getStatus()));
+        return vo;
+    }
+
+    private ComplaintManageItemResVo toManageItem(TComplaint complaint) {
+        ComplaintManageItemResVo vo = new ComplaintManageItemResVo();
+        vo.setId(complaint.getId());
+        vo.setComplaintNo(complaint.getComplaintNo());
+        vo.setTypeCode(complaint.getTypeCode());
+        vo.setTypeName(typeName(complaint.getTypeCode()));
+        vo.setTargetName(complaint.getTargetName());
+        vo.setSubmitterNameDisplay(Boolean.TRUE.equals(complaint.getIsAnonymous()) ? "匿名" : complaint.getSubmitterName());
+        vo.setOccurTime(complaint.getOccurTime());
+        vo.setStatus(complaint.getStatus());
+        vo.setStatusName(statusName(complaint.getStatus()));
+        return vo;
+    }
+
+    private ComplaintDetailResVo buildComplaintDetail(TComplaint complaint, boolean maskSubmitter) {
+        ComplaintDetailResVo res = new ComplaintDetailResVo();
+        res.setId(complaint.getId());
+        res.setComplaintNo(complaint.getComplaintNo());
+        res.setChannel(complaint.getChannel());
+        res.setTypeCode(complaint.getTypeCode());
+        res.setTypeName(typeName(complaint.getTypeCode()));
+        res.setTargetName(complaint.getTargetName());
+        res.setSubmitterId(complaint.getSubmitterId());
+        res.setSubmitterName(maskSubmitter && Boolean.TRUE.equals(complaint.getIsAnonymous()) ? "匿名" : complaint.getSubmitterName());
+        res.setIsAnonymous(Boolean.TRUE.equals(complaint.getIsAnonymous()));
+        res.setContent(complaint.getContent());
+        res.setStatus(complaint.getStatus());
+        res.setStatusName(statusName(complaint.getStatus()));
+        res.setOccurTime(complaint.getOccurTime());
+        res.setProcessedAt(complaint.getProcessedAt());
+        res.setProcessorId(complaint.getProcessorId());
+        res.setProcessorName(complaint.getProcessorName());
+        res.setProcessReply(complaint.getProcessReply());
+
+        List<TComplaintAttachment> attachments = tComplaintAttachmentService.lambdaQuery()
+                .eq(TComplaintAttachment::getDelFlag, Global.UN_DELETED)
+                .eq(TComplaintAttachment::getComplaintId, complaint.getId())
+                .orderByAsc(TComplaintAttachment::getSortNo)
+                .list();
+        if (CollUtil.isEmpty(attachments)) {
+            res.setAttachments(new ArrayList<>());
+        } else {
+            res.setAttachments(attachments.stream().map(this::toAttachment).collect(Collectors.toList()));
+        }
+
+        List<TComplaintHandleLog> logs = tComplaintHandleLogService.lambdaQuery()
+                .eq(TComplaintHandleLog::getDelFlag, Global.UN_DELETED)
+                .eq(TComplaintHandleLog::getComplaintId, complaint.getId())
+                .orderByDesc(TComplaintHandleLog::getCreateTime)
+                .list();
+        if (CollUtil.isEmpty(logs)) {
+            res.setHandleLogs(new ArrayList<>());
+        } else {
+            res.setHandleLogs(logs.stream().map(this::toHandleLog).collect(Collectors.toList()));
+        }
+
+        return res;
+    }
+
+    private ComplaintAttachmentResVo toAttachment(TComplaintAttachment attachment) {
+        ComplaintAttachmentResVo vo = new ComplaintAttachmentResVo();
+        vo.setId(attachment.getId());
+        vo.setFileUrl(attachment.getFileUrl());
+        vo.setFileName(attachment.getFileName());
+        vo.setFileType(attachment.getFileType());
+        vo.setFileSize(attachment.getFileSize());
+        vo.setSortNo(attachment.getSortNo());
+        return vo;
+    }
+
+    private ComplaintHandleLogResVo toHandleLog(TComplaintHandleLog log) {
+        ComplaintHandleLogResVo vo = new ComplaintHandleLogResVo();
+        vo.setId(log.getId());
+        vo.setAction(log.getAction());
+        vo.setFromStatus(log.getFromStatus());
+        vo.setToStatus(log.getToStatus());
+        vo.setOperatorId(log.getOperatorId());
+        vo.setOperatorName(log.getOperatorName());
+        vo.setRemark(log.getRemark());
+        vo.setCreateTime(log.getCreateTime());
+        return vo;
+    }
+
+    private ComplaintAppDetailResVo toAppDetail(ComplaintDetailResVo detail) {
+        ComplaintAppDetailResVo vo = new ComplaintAppDetailResVo();
+        vo.setId(detail.getId());
+        vo.setComplaintNo(detail.getComplaintNo());
+        vo.setTypeCode(detail.getTypeCode());
+        vo.setTypeName(detail.getTypeName());
+        vo.setTargetName(detail.getTargetName());
+        vo.setIsAnonymous(detail.getIsAnonymous());
+        vo.setSubmitterName(detail.getSubmitterName());
+        vo.setContent(detail.getContent());
+        vo.setAttachments(detail.getAttachments());
+        vo.setStatus(detail.getStatus());
+        vo.setStatusName(detail.getStatusName());
+        vo.setOccurTime(detail.getOccurTime());
+        vo.setProcessedAt(detail.getProcessedAt());
+        vo.setProcessReply(detail.getProcessReply());
+        return vo;
+    }
+
+    private String statusName(Integer status) {
+        if (status == null) {
+            return "";
+        }
+        return status == 1 ? "已处理" : "待处理";
+    }
+
+    private String typeName(Integer typeCode) {
+        if (typeCode == null) {
+            return "";
+        }
+        return switch (typeCode) {
+            case 1 -> "违规";
+            case 2 -> "服务态度";
+            case 3 -> "其他";
+            default -> String.valueOf(typeCode);
+        };
+    }
+
+    private String buildTempComplaintNo(LocalDateTime now) {
+        String date = now.toLocalDate().format(DATE_NO_DASH);
+        String suffix = String.valueOf(System.nanoTime());
+        if (suffix.length() > 12) {
+            suffix = suffix.substring(suffix.length() - 12);
+        }
+        return "CT" + date + "TMP" + suffix;
+    }
+
+    private String buildComplaintNo(LocalDateTime now) {
+        String date = now.toLocalDate().format(DATE_NO_DASH);
+        for (int i = 0; i < 5; i++) {
+            String suffix = String.format("%06d", ThreadLocalRandom.current().nextInt(0, 1_000_000));
+            String no = "CT" + date + suffix;
+            boolean exists = tComplaintService.lambdaQuery()
+                    .eq(TComplaint::getComplaintNo, no)
+                    .exists();
+            if (!exists) {
+                return no;
+            }
+        }
+        return buildTempComplaintNo(now);
+    }
+
+
+    private LocalDateTime parseStartDate(String startDate) {
+        if (StrUtil.isBlank(startDate)) {
+            return null;
+        }
+        return LocalDate.parse(startDate.trim()).atStartOfDay();
+    }
+
+    private LocalDateTime parseEndDateExclusive(String endDate) {
+        if (StrUtil.isBlank(endDate)) {
+            return null;
+        }
+        return LocalDate.parse(endDate.trim()).plusDays(1).atStartOfDay();
+    }
+}

+ 16 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/TComplaintAttachmentService.java

@@ -0,0 +1,16 @@
+package com.sckw.system.service;
+
+import org.springframework.stereotype.Service;
+import org.springframework.beans.factory.annotation.Autowired;
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.system.dao.TComplaintAttachmentMapper;
+import com.sckw.system.model.TComplaintAttachment;
+/**
+* @date 2026-06-06 15:14:09
+* @author xucaiqin
+*/
+@Service
+public class TComplaintAttachmentService extends ServiceImpl<TComplaintAttachmentMapper, TComplaintAttachment> {
+
+}

+ 11 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/TComplaintHandleLogService.java

@@ -0,0 +1,11 @@
+package com.sckw.system.service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.system.dao.TComplaintHandleLogMapper;
+import com.sckw.system.model.TComplaintHandleLog;
+import org.springframework.stereotype.Service;
+
+@Service
+public class TComplaintHandleLogService extends ServiceImpl<TComplaintHandleLogMapper, TComplaintHandleLog> {
+}
+

+ 16 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/TComplaintService.java

@@ -0,0 +1,16 @@
+package com.sckw.system.service;
+
+import org.springframework.stereotype.Service;
+import org.springframework.beans.factory.annotation.Autowired;
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.system.dao.TComplaintMapper;
+import com.sckw.system.model.TComplaint;
+/**
+* @date 2026-06-06 15:14:09
+* @author xucaiqin
+*/
+@Service
+public class TComplaintService extends ServiceImpl<TComplaintMapper, TComplaint> {
+
+}

+ 3 - 3
sckw-modules/sckw-system/src/main/resources/bootstrap.yml

@@ -5,8 +5,8 @@ spring:
   application:
     name: sckw-ng-system
   profiles:
-    active: @profiles.active@
-#    active: dev
+#    active: @profiles.active@
+    active: local
 #    active: test
   main:
     allow-bean-definition-overriding: true
@@ -32,4 +32,4 @@ feign:
   httpclient:
     enabled: true
     max-connections: 200
-    max-connections-per-route: 50
+    max-connections-per-route: 50

+ 25 - 0
sckw-modules/sckw-system/src/main/resources/mapper/TComplaintAttachmentMapper.xml

@@ -0,0 +1,25 @@
+<?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="com.sckw.system.dao.TComplaintAttachmentMapper">
+  <resultMap id="BaseResultMap" type="com.sckw.system.model.TComplaintAttachment">
+    <!--@mbg.generated-->
+    <!--@Table t_complaint_attachment-->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="complaint_id" jdbcType="BIGINT" property="complaintId" />
+    <result column="file_url" jdbcType="VARCHAR" property="fileUrl" />
+    <result column="file_name" jdbcType="VARCHAR" property="fileName" />
+    <result column="file_type" jdbcType="VARCHAR" property="fileType" />
+    <result column="file_size" jdbcType="BIGINT" property="fileSize" />
+    <result column="sort_no" jdbcType="INTEGER" property="sortNo" />
+    <result column="create_by" jdbcType="BIGINT" property="createBy" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="update_by" jdbcType="BIGINT" property="updateBy" />
+    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
+    <result column="del_flag" jdbcType="INTEGER" property="delFlag" />
+  </resultMap>
+  <sql id="Base_Column_List">
+    <!--@mbg.generated-->
+    id, complaint_id, file_url, file_name, file_type, file_size, sort_no, create_by, 
+    create_time, update_by, update_time, del_flag
+  </sql>
+</mapper>

+ 30 - 0
sckw-modules/sckw-system/src/main/resources/mapper/TComplaintMapper.xml

@@ -0,0 +1,30 @@
+<?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="com.sckw.system.dao.TComplaintMapper">
+  <resultMap id="BaseResultMap" type="com.sckw.system.model.TComplaint">
+    <!--@mbg.generated-->
+    <!--@Table t_complaint-->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="complaint_no" jdbcType="VARCHAR" property="complaintNo" />
+    <result column="channel" jdbcType="TINYINT" property="channel" />
+    <result column="type_code" jdbcType="VARCHAR" property="typeCode" />
+    <result column="target_name" jdbcType="VARCHAR" property="targetName" />
+    <result column="submitter_type" jdbcType="TINYINT" property="submitterType" />
+    <result column="submitter_id" jdbcType="BIGINT" property="submitterId" />
+    <result column="submitter_name" jdbcType="VARCHAR" property="submitterName" />
+    <result column="is_anonymous" jdbcType="BOOLEAN" property="isAnonymous" />
+    <result column="content" jdbcType="LONGVARCHAR" property="content" />
+    <result column="status" jdbcType="TINYINT" property="status" />
+    <result column="occur_time" jdbcType="TIMESTAMP" property="occurTime" />
+    <result column="processed_at" jdbcType="TIMESTAMP" property="processedAt" />
+    <result column="processor_id" jdbcType="BIGINT" property="processorId" />
+    <result column="processor_name" jdbcType="VARCHAR" property="processorName" />
+    <result column="process_reply" jdbcType="LONGVARCHAR" property="processReply" />
+    <result column="create_by" jdbcType="BIGINT" property="createBy" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="update_by" jdbcType="BIGINT" property="updateBy" />
+    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
+    <result column="del_flag" jdbcType="INTEGER" property="delFlag" />
+  </resultMap>
+
+</mapper>