44 changed files with 3394 additions and 629 deletions
@ -0,0 +1,35 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.constant; |
||||
|
|
||||
|
/** |
||||
|
* 警务评议工单业务常量定义类 |
||||
|
* |
||||
|
* @Author Antigravity |
||||
|
* @Date 2026-06-14 |
||||
|
*/ |
||||
|
public class WtczConst { |
||||
|
|
||||
|
/** |
||||
|
* 工单状态:已录入/暂存,待首访 |
||||
|
*/ |
||||
|
public static final String GDZT_STASH = "0"; |
||||
|
|
||||
|
/** |
||||
|
* 工单状态:办结中/待二次回访反馈 |
||||
|
*/ |
||||
|
public static final String GDZT_PROCESSING = "1"; |
||||
|
|
||||
|
/** |
||||
|
* 工单状态:已办结 |
||||
|
*/ |
||||
|
public static final String GDZT_COMPLETED = "2"; |
||||
|
|
||||
|
/** |
||||
|
* 回访阶段:1首访通话 |
||||
|
*/ |
||||
|
public static final String XFJD_FIRST = "1"; |
||||
|
|
||||
|
/** |
||||
|
* 回访阶段:3二次回访通话 |
||||
|
*/ |
||||
|
public static final String XFJD_SECOND = "3"; |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.controller; |
||||
|
|
||||
|
import io.swagger.v3.oas.annotations.Operation; |
||||
|
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
import jakarta.annotation.Resource; |
||||
|
import jakarta.validation.Valid; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.domain.entity.SurveySampleEntity; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.domain.entity.SurveySampleGroupEntity; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.domain.entity.SurveyTaskEntity; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.domain.form.*; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.service.SurveySampleService; |
||||
|
import net.lab1024.sa.base.common.domain.PageResult; |
||||
|
import net.lab1024.sa.base.common.domain.ResponseDTO; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 调查问卷样本库与评议任务 控制器 |
||||
|
* |
||||
|
* @Author Antigravity |
||||
|
* @Date 2026-06-15 |
||||
|
*/ |
||||
|
@RestController |
||||
|
@Tag(name = "业务功能-调查样本与评议分发") |
||||
|
public class SurveySampleController { |
||||
|
|
||||
|
@Resource |
||||
|
private SurveySampleService sampleService; |
||||
|
|
||||
|
// ================= 分组管理 =================
|
||||
|
|
||||
|
@Operation(summary = "查询样本库分类列表") |
||||
|
@GetMapping("/mydc/sample/group/list") |
||||
|
public ResponseDTO<List<SurveySampleGroupEntity>> groupList() { |
||||
|
return ResponseDTO.ok(sampleService.groupList()); |
||||
|
} |
||||
|
|
||||
|
@Operation(summary = "创建新样本库分类") |
||||
|
@PostMapping("/mydc/sample/group/add") |
||||
|
public ResponseDTO<String> addGroup(@RequestBody @Valid SurveySampleGroupAddForm form) { |
||||
|
return sampleService.addGroup(form); |
||||
|
} |
||||
|
|
||||
|
@Operation(summary = "移除样本库分类及其样本") |
||||
|
@GetMapping("/mydc/sample/group/delete/{id}") |
||||
|
public ResponseDTO<String> deleteGroup(@PathVariable Long id) { |
||||
|
return sampleService.deleteGroup(id); |
||||
|
} |
||||
|
|
||||
|
// ================= 名单管理 =================
|
||||
|
|
||||
|
@Operation(summary = "分页筛选样本人员名单") |
||||
|
@PostMapping("/mydc/sample/queryPage") |
||||
|
public ResponseDTO<PageResult<SurveySampleEntity>> querySamplePage(@RequestBody @Valid SurveySampleQueryForm queryForm) { |
||||
|
return ResponseDTO.ok(sampleService.querySamplePage(queryForm)); |
||||
|
} |
||||
|
|
||||
|
@Operation(summary = "单条录入样本人员") |
||||
|
@PostMapping("/mydc/sample/add") |
||||
|
public ResponseDTO<String> addSample(@RequestBody @Valid SurveySampleAddForm form) { |
||||
|
return sampleService.addSample(form); |
||||
|
} |
||||
|
|
||||
|
@Operation(summary = "粘贴多行文本批量解析导入") |
||||
|
@PostMapping("/mydc/sample/batchAdd") |
||||
|
public ResponseDTO<String> batchAddSamples(@RequestBody @Valid SurveySampleBatchAddForm form) { |
||||
|
return sampleService.batchAddSamples(form); |
||||
|
} |
||||
|
|
||||
|
@Operation(summary = "移除样本库成员") |
||||
|
@GetMapping("/mydc/sample/delete/{id}") |
||||
|
public ResponseDTO<String> deleteSample(@PathVariable Long id) { |
||||
|
return sampleService.deleteSample(id); |
||||
|
} |
||||
|
|
||||
|
// ================= 测评任务管理 =================
|
||||
|
|
||||
|
@Operation(summary = "分页查询测评任务列表") |
||||
|
@PostMapping("/mydc/task/queryPage") |
||||
|
public ResponseDTO<PageResult<SurveyTaskEntity>> queryTaskPage(@RequestBody @Valid SurveyTaskQueryForm queryForm) { |
||||
|
return ResponseDTO.ok(sampleService.queryTaskPage(queryForm)); |
||||
|
} |
||||
|
|
||||
|
@Operation(summary = "查询单个测评任务详情") |
||||
|
@GetMapping("/mydc/task/detail/{id}") |
||||
|
public ResponseDTO<SurveyTaskEntity> getTaskDetail(@PathVariable Long id) { |
||||
|
return ResponseDTO.ok(sampleService.getTaskDetail(id)); |
||||
|
} |
||||
|
|
||||
|
@Operation(summary = "新建调查任务并批量推送生成名单") |
||||
|
@PostMapping("/mydc/task/create") |
||||
|
public ResponseDTO<String> createSurveyTask(@RequestBody @Valid SurveyTaskCreateForm form) { |
||||
|
return sampleService.createSurveyTask(form); |
||||
|
} |
||||
|
|
||||
|
@Operation(summary = "H5填报端提交群众测评答案") |
||||
|
@PostMapping("/mydc/task/submitAnswer") |
||||
|
public ResponseDTO<String> submitH5Answer(@RequestBody @Valid SurveyTaskSubmitAnswerForm form) { |
||||
|
return sampleService.submitH5Answer(form); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.dao; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.domain.entity.SurveySampleEntity; |
||||
|
import org.apache.ibatis.annotations.Mapper; |
||||
|
|
||||
|
/** |
||||
|
* 调查问卷样本人员 Dao 接口 |
||||
|
* |
||||
|
* @Author Antigravity |
||||
|
* @Date 2026-06-15 |
||||
|
*/ |
||||
|
@Mapper |
||||
|
public interface SurveySampleDao extends BaseMapper<SurveySampleEntity> { |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.dao; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.domain.entity.SurveySampleGroupEntity; |
||||
|
import org.apache.ibatis.annotations.Mapper; |
||||
|
|
||||
|
/** |
||||
|
* 调查问卷样本分组 Dao 接口 |
||||
|
* |
||||
|
* @Author Antigravity |
||||
|
* @Date 2026-06-15 |
||||
|
*/ |
||||
|
@Mapper |
||||
|
public interface SurveySampleGroupDao extends BaseMapper<SurveySampleGroupEntity> { |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.dao; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.domain.entity.SurveyTaskEntity; |
||||
|
import org.apache.ibatis.annotations.Mapper; |
||||
|
|
||||
|
/** |
||||
|
* 调查测评任务 Dao 接口 |
||||
|
* |
||||
|
* @Author Antigravity |
||||
|
* @Date 2026-06-15 |
||||
|
*/ |
||||
|
@Mapper |
||||
|
public interface SurveyTaskDao extends BaseMapper<SurveyTaskEntity> { |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.domain.entity; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.IdType; |
||||
|
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; |
||||
|
|
||||
|
/** |
||||
|
* 调查问卷样本人员 实体类 |
||||
|
* |
||||
|
* @Author Antigravity |
||||
|
* @Date 2026-06-15 |
||||
|
*/ |
||||
|
@Data |
||||
|
@TableName("t_survey_sample") |
||||
|
@Schema(description = "调查问卷样本人员实体") |
||||
|
public class SurveySampleEntity { |
||||
|
|
||||
|
@TableId(type = IdType.ASSIGN_ID) |
||||
|
@Schema(description = "样本人ID") |
||||
|
private Long id; |
||||
|
|
||||
|
@Schema(description = "关联分组ID") |
||||
|
private Long groupId; |
||||
|
|
||||
|
@Schema(description = "群众/人才姓名") |
||||
|
private String name; |
||||
|
|
||||
|
@Schema(description = "手机号") |
||||
|
private String phone; |
||||
|
|
||||
|
@Schema(description = "备注说明") |
||||
|
private String remarks; |
||||
|
|
||||
|
@Schema(description = "录入时间") |
||||
|
private LocalDateTime importTime; |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.domain.entity; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.IdType; |
||||
|
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; |
||||
|
|
||||
|
/** |
||||
|
* 调查问卷样本分组 实体类 |
||||
|
* |
||||
|
* @Author Antigravity |
||||
|
* @Date 2026-06-15 |
||||
|
*/ |
||||
|
@Data |
||||
|
@TableName("t_survey_sample_group") |
||||
|
@Schema(description = "调查问卷样本分组实体") |
||||
|
public class SurveySampleGroupEntity { |
||||
|
|
||||
|
@TableId(type = IdType.ASSIGN_ID) |
||||
|
@Schema(description = "分组ID") |
||||
|
private Long id; |
||||
|
|
||||
|
@Schema(description = "分类/分组名称") |
||||
|
private String name; |
||||
|
|
||||
|
@Schema(description = "创建时间") |
||||
|
private LocalDateTime createTime; |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.domain.entity; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.IdType; |
||||
|
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; |
||||
|
|
||||
|
/** |
||||
|
* 调查测评任务 实体类 |
||||
|
* |
||||
|
* @Author Antigravity |
||||
|
* @Date 2026-06-15 |
||||
|
*/ |
||||
|
@Data |
||||
|
@TableName("t_survey_task") |
||||
|
@Schema(description = "调查测评任务实体") |
||||
|
public class SurveyTaskEntity { |
||||
|
|
||||
|
@TableId(type = IdType.ASSIGN_ID) |
||||
|
@Schema(description = "任务ID") |
||||
|
private Long id; |
||||
|
|
||||
|
@Schema(description = "任务名称") |
||||
|
private String title; |
||||
|
|
||||
|
@Schema(description = "受访群众姓名") |
||||
|
private String name; |
||||
|
|
||||
|
@Schema(description = "联系电话") |
||||
|
private String phone; |
||||
|
|
||||
|
@Schema(description = "关联警务分类编码") |
||||
|
private String sjlyNo; |
||||
|
|
||||
|
@Schema(description = "呼叫次数") |
||||
|
private Integer callCount; |
||||
|
|
||||
|
@Schema(description = "状态: 0待调查, 1调查成功, 2无人接听, 3明确拒访, 4已发送短信评议") |
||||
|
private Integer status; |
||||
|
|
||||
|
@Schema(description = "关联问卷模板名称") |
||||
|
private String surveyTitle; |
||||
|
|
||||
|
@Schema(description = "关联的问卷模板ID") |
||||
|
private Long surveyId; |
||||
|
|
||||
|
@Schema(description = "群众填报的答卷JSON详情") |
||||
|
private String answers; |
||||
|
|
||||
|
@Schema(description = "生成/分配时间") |
||||
|
private LocalDateTime assignTime; |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.domain.form; |
||||
|
|
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import jakarta.validation.constraints.NotBlank; |
||||
|
import jakarta.validation.constraints.NotNull; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
@Schema(description = "新增单个样本人员表单") |
||||
|
public class SurveySampleAddForm { |
||||
|
|
||||
|
@Schema(description = "样本组ID") |
||||
|
@NotNull(message = "样本分组不能为空") |
||||
|
private Long groupId; |
||||
|
|
||||
|
@Schema(description = "姓名") |
||||
|
@NotBlank(message = "姓名不能为空") |
||||
|
private String name; |
||||
|
|
||||
|
@Schema(description = "联系电话") |
||||
|
@NotBlank(message = "电话不能为空") |
||||
|
private String phone; |
||||
|
|
||||
|
@Schema(description = "分类备注/描述") |
||||
|
private String remarks; |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.domain.form; |
||||
|
|
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import jakarta.validation.constraints.NotBlank; |
||||
|
import jakarta.validation.constraints.NotNull; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
@Schema(description = "批量粘贴解析导入样本表单") |
||||
|
public class SurveySampleBatchAddForm { |
||||
|
|
||||
|
@Schema(description = "样本组ID") |
||||
|
@NotNull(message = "样本分组不能为空") |
||||
|
private Long groupId; |
||||
|
|
||||
|
@Schema(description = "粘贴的解析文本块") |
||||
|
@NotBlank(message = "粘贴的数据文本不能为空") |
||||
|
private String text; |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.domain.form; |
||||
|
|
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import jakarta.validation.constraints.NotBlank; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
@Schema(description = "新增样本分组表单") |
||||
|
public class SurveySampleGroupAddForm { |
||||
|
|
||||
|
@Schema(description = "分类/分组名称") |
||||
|
@NotBlank(message = "分组名称不能为空") |
||||
|
private String name; |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.domain.form; |
||||
|
|
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import lombok.Data; |
||||
|
import net.lab1024.sa.base.common.domain.PageParam; |
||||
|
|
||||
|
@Data |
||||
|
@Schema(description = "样本名单分页查询表单") |
||||
|
public class SurveySampleQueryForm extends PageParam { |
||||
|
|
||||
|
@Schema(description = "样本组ID") |
||||
|
private Long groupId; |
||||
|
|
||||
|
@Schema(description = "姓名/模糊查询") |
||||
|
private String name; |
||||
|
|
||||
|
@Schema(description = "手机号") |
||||
|
private String phone; |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.domain.form; |
||||
|
|
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import jakarta.validation.constraints.NotBlank; |
||||
|
import jakarta.validation.constraints.NotNull; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
@Schema(description = "新建并推送调查测评任务表单") |
||||
|
public class SurveyTaskCreateForm { |
||||
|
|
||||
|
@Schema(description = "测评任务名称") |
||||
|
@NotBlank(message = "任务名称不能为空") |
||||
|
private String title; |
||||
|
|
||||
|
@Schema(description = "样本分组ID") |
||||
|
@NotNull(message = "请选择目标样本分组") |
||||
|
private Long groupId; |
||||
|
|
||||
|
@Schema(description = "关联的问卷模板ID") |
||||
|
@NotNull(message = "请选择关联的问卷模板") |
||||
|
private Long surveyId; |
||||
|
|
||||
|
@Schema(description = "推送通知方式: sms 短信, wx 微信") |
||||
|
private String pushType; |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.domain.form; |
||||
|
|
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import lombok.Data; |
||||
|
import net.lab1024.sa.base.common.domain.PageParam; |
||||
|
|
||||
|
@Data |
||||
|
@Schema(description = "测评任务分页查询表单") |
||||
|
public class SurveyTaskQueryForm extends PageParam { |
||||
|
|
||||
|
@Schema(description = "受访群众姓名/模糊查询") |
||||
|
private String name; |
||||
|
|
||||
|
@Schema(description = "任务状态: 0待调查, 1调查成功, 2无人接听, 3明确拒访, 4已发送") |
||||
|
private Integer status; |
||||
|
|
||||
|
@Schema(description = "是否只查询待回访的列表") |
||||
|
private Boolean isTodoView; |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.domain.form; |
||||
|
|
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import jakarta.validation.constraints.NotBlank; |
||||
|
import jakarta.validation.constraints.NotNull; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
@Schema(description = "群众自助填报提交表单") |
||||
|
public class SurveyTaskSubmitAnswerForm { |
||||
|
|
||||
|
@Schema(description = "任务明细ID") |
||||
|
@NotNull(message = "任务ID不能为空") |
||||
|
private Long taskId; |
||||
|
|
||||
|
@Schema(description = "答卷反馈选项JSON内容") |
||||
|
@NotBlank(message = "答卷内容不能为空") |
||||
|
private String answers; |
||||
|
} |
||||
@ -0,0 +1,221 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.service; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
||||
|
import jakarta.annotation.Resource; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.dao.SurveyDao; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.dao.SurveySampleDao; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.dao.SurveySampleGroupDao; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.dao.SurveyTaskDao; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.domain.entity.SurveyEntity; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.domain.entity.SurveySampleEntity; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.domain.entity.SurveySampleGroupEntity; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.domain.entity.SurveyTaskEntity; |
||||
|
import net.lab1024.sa.admin.module.business.jwpy.domain.form.*; |
||||
|
import net.lab1024.sa.base.common.domain.PageResult; |
||||
|
import net.lab1024.sa.base.common.domain.ResponseDTO; |
||||
|
import net.lab1024.sa.base.common.util.SmartBeanUtil; |
||||
|
import net.lab1024.sa.base.common.util.SmartPageUtil; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 调查问卷样本库与评议任务 业务服务类 |
||||
|
* |
||||
|
* @Author Antigravity |
||||
|
* @Date 2026-06-15 |
||||
|
*/ |
||||
|
@Service |
||||
|
@Slf4j |
||||
|
public class SurveySampleService { |
||||
|
|
||||
|
@Resource |
||||
|
private SurveySampleGroupDao groupDao; |
||||
|
|
||||
|
@Resource |
||||
|
private SurveySampleDao sampleDao; |
||||
|
|
||||
|
@Resource |
||||
|
private SurveyTaskDao surveyTaskDao; |
||||
|
|
||||
|
@Resource |
||||
|
private SurveyDao surveyDao; |
||||
|
|
||||
|
// ================= 分组管理 =================
|
||||
|
|
||||
|
public List<SurveySampleGroupEntity> groupList() { |
||||
|
return groupDao.selectList(Wrappers.<SurveySampleGroupEntity>lambdaQuery().orderByAsc(SurveySampleGroupEntity::getId)); |
||||
|
} |
||||
|
|
||||
|
public ResponseDTO<String> addGroup(SurveySampleGroupAddForm form) { |
||||
|
SurveySampleGroupEntity entity = new SurveySampleGroupEntity(); |
||||
|
entity.setName(form.getName()); |
||||
|
entity.setCreateTime(LocalDateTime.now()); |
||||
|
groupDao.insert(entity); |
||||
|
return ResponseDTO.ok("分组创建成功"); |
||||
|
} |
||||
|
|
||||
|
public ResponseDTO<String> deleteGroup(Long id) { |
||||
|
groupDao.deleteById(id); |
||||
|
// 联动删除该分组下的所有人员
|
||||
|
sampleDao.delete(Wrappers.<SurveySampleEntity>lambdaQuery().eq(SurveySampleEntity::getGroupId, id)); |
||||
|
return ResponseDTO.ok("分类及下属样本删除成功"); |
||||
|
} |
||||
|
|
||||
|
// ================= 样本名单管理 =================
|
||||
|
|
||||
|
@SuppressWarnings("unchecked") |
||||
|
public PageResult<SurveySampleEntity> querySamplePage(SurveySampleQueryForm queryForm) { |
||||
|
Page<SurveySampleEntity> page = (Page<SurveySampleEntity>) SmartPageUtil.convert2PageQuery(queryForm); |
||||
|
LambdaQueryWrapper<SurveySampleEntity> wrapper = Wrappers.lambdaQuery(); |
||||
|
|
||||
|
if (queryForm.getGroupId() != null) { |
||||
|
wrapper.eq(SurveySampleEntity::getGroupId, queryForm.getGroupId()); |
||||
|
} |
||||
|
if (StringUtils.isNotBlank(queryForm.getName())) { |
||||
|
wrapper.like(SurveySampleEntity::getName, queryForm.getName()); |
||||
|
} |
||||
|
if (StringUtils.isNotBlank(queryForm.getPhone())) { |
||||
|
wrapper.eq(SurveySampleEntity::getPhone, queryForm.getPhone()); |
||||
|
} |
||||
|
|
||||
|
wrapper.orderByDesc(SurveySampleEntity::getImportTime); |
||||
|
sampleDao.selectPage(page, wrapper); |
||||
|
return SmartPageUtil.convert2PageResult(page, page.getRecords(), SurveySampleEntity.class); |
||||
|
} |
||||
|
|
||||
|
public ResponseDTO<String> addSample(SurveySampleAddForm form) { |
||||
|
SurveySampleEntity entity = SmartBeanUtil.copy(form, SurveySampleEntity.class); |
||||
|
entity.setImportTime(LocalDateTime.now()); |
||||
|
sampleDao.insert(entity); |
||||
|
return ResponseDTO.ok("录入样本成功"); |
||||
|
} |
||||
|
|
||||
|
@Transactional(rollbackFor = Exception.class) |
||||
|
public ResponseDTO<String> batchAddSamples(SurveySampleBatchAddForm form) { |
||||
|
String text = form.getText(); |
||||
|
if (StringUtils.isBlank(text)) { |
||||
|
return ResponseDTO.userErrorParam("粘贴内容不能为空"); |
||||
|
} |
||||
|
String[] lines = text.split("\n"); |
||||
|
List<SurveySampleEntity> list = new ArrayList<>(); |
||||
|
|
||||
|
for (String line : lines) { |
||||
|
if (StringUtils.isBlank(line)) continue; |
||||
|
String[] parts = line.split("[,,\\s]+"); |
||||
|
if (parts.length >= 2) { |
||||
|
String name = parts[0].trim(); |
||||
|
String phone = parts[1].trim(); |
||||
|
String remarks = parts.length >= 3 ? parts[2].trim() : ""; |
||||
|
|
||||
|
if (phone.matches("\\d{11}")) { |
||||
|
SurveySampleEntity s = new SurveySampleEntity(); |
||||
|
s.setGroupId(form.getGroupId()); |
||||
|
s.setName(name); |
||||
|
s.setPhone(phone); |
||||
|
s.setRemarks(remarks); |
||||
|
s.setImportTime(LocalDateTime.now()); |
||||
|
list.add(s); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (list.isEmpty()) { |
||||
|
return ResponseDTO.userErrorParam("解析失败,未提取到符合格式要求的人员信息"); |
||||
|
} |
||||
|
|
||||
|
for (SurveySampleEntity s : list) { |
||||
|
sampleDao.insert(s); |
||||
|
} |
||||
|
return ResponseDTO.ok("成功导入 " + list.size() + " 条样本数据"); |
||||
|
} |
||||
|
|
||||
|
public ResponseDTO<String> deleteSample(Long id) { |
||||
|
sampleDao.deleteById(id); |
||||
|
return ResponseDTO.ok("移除样本人员成功"); |
||||
|
} |
||||
|
|
||||
|
// ================= 调查评议任务管理 =================
|
||||
|
|
||||
|
@SuppressWarnings("unchecked") |
||||
|
public PageResult<SurveyTaskEntity> queryTaskPage(SurveyTaskQueryForm queryForm) { |
||||
|
Page<SurveyTaskEntity> page = (Page<SurveyTaskEntity>) SmartPageUtil.convert2PageQuery(queryForm); |
||||
|
LambdaQueryWrapper<SurveyTaskEntity> wrapper = Wrappers.lambdaQuery(); |
||||
|
|
||||
|
if (StringUtils.isNotBlank(queryForm.getName())) { |
||||
|
wrapper.like(SurveyTaskEntity::getName, queryForm.getName()); |
||||
|
} |
||||
|
|
||||
|
if (queryForm.getStatus() != null) { |
||||
|
wrapper.eq(SurveyTaskEntity::getStatus, queryForm.getStatus()); |
||||
|
} else { |
||||
|
// 待办/已发推送等自适应过滤
|
||||
|
if (queryForm.getIsTodoView() != null && queryForm.getIsTodoView()) { |
||||
|
wrapper.in(SurveyTaskEntity::getStatus, 0, 2, 4); // 待调查、未接听、已发送推送
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
wrapper.orderByDesc(SurveyTaskEntity::getAssignTime); |
||||
|
surveyTaskDao.selectPage(page, wrapper); |
||||
|
return SmartPageUtil.convert2PageResult(page, page.getRecords(), SurveyTaskEntity.class); |
||||
|
} |
||||
|
|
||||
|
public SurveyTaskEntity getTaskDetail(Long id) { |
||||
|
return surveyTaskDao.selectById(id); |
||||
|
} |
||||
|
|
||||
|
@Transactional(rollbackFor = Exception.class) |
||||
|
public ResponseDTO<String> createSurveyTask(SurveyTaskCreateForm form) { |
||||
|
// 1. 获取分组样本人员
|
||||
|
List<SurveySampleEntity> samples = sampleDao.selectList( |
||||
|
Wrappers.<SurveySampleEntity>lambdaQuery().eq(SurveySampleEntity::getGroupId, form.getGroupId()) |
||||
|
); |
||||
|
if (samples.isEmpty()) { |
||||
|
return ResponseDTO.userErrorParam("所选的分组下没有任何样本人员,请先录入名单"); |
||||
|
} |
||||
|
|
||||
|
// 2. 获取关联问卷详情
|
||||
|
SurveyEntity survey = surveyDao.selectById(form.getSurveyId()); |
||||
|
if (survey == null) { |
||||
|
return ResponseDTO.userErrorParam("绑定的问卷模板不存在"); |
||||
|
} |
||||
|
if (survey.getStatus() == null || survey.getStatus() != 1) { |
||||
|
return ResponseDTO.userErrorParam("该问卷模板尚未启用,无法创建测评任务!"); |
||||
|
} |
||||
|
|
||||
|
// 3. 循环转换生成评议任务
|
||||
|
for (SurveySampleEntity s : samples) { |
||||
|
SurveyTaskEntity task = new SurveyTaskEntity(); |
||||
|
task.setTitle(form.getTitle()); |
||||
|
task.setName(s.getName()); |
||||
|
task.setPhone(s.getPhone()); |
||||
|
task.setSjlyNo(form.getGroupId() == 1001L ? "crj" : (form.getGroupId() == 1002L ? "hz" : "110")); // 业务分类标签颜色映射
|
||||
|
task.setCallCount(0); |
||||
|
task.setStatus(4); // 直接设置为已发推送评议状态
|
||||
|
task.setSurveyTitle(survey.getTitle()); |
||||
|
task.setSurveyId(survey.getSurveyId()); |
||||
|
task.setAssignTime(LocalDateTime.now()); |
||||
|
surveyTaskDao.insert(task); |
||||
|
} |
||||
|
|
||||
|
return ResponseDTO.ok("成功为 " + samples.size() + " 位群众生成评议任务并下发通知!"); |
||||
|
} |
||||
|
|
||||
|
public ResponseDTO<String> submitH5Answer(SurveyTaskSubmitAnswerForm form) { |
||||
|
SurveyTaskEntity task = surveyTaskDao.selectById(form.getTaskId()); |
||||
|
if (task == null) { |
||||
|
return ResponseDTO.userErrorParam("测评任务明细不存在"); |
||||
|
} |
||||
|
task.setStatus(1); // 状态更新为“调查成功/已评议”
|
||||
|
task.setAnswers(form.getAnswers()); |
||||
|
surveyTaskDao.updateById(task); |
||||
|
return ResponseDTO.ok("提交成功,感谢您的测评反馈!"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
package net.lab1024.sa.admin.module.business.jwpy.util; |
||||
|
|
||||
|
import net.lab1024.sa.admin.module.business.jwpy.constant.WtczConst; |
||||
|
import java.time.LocalDateTime; |
||||
|
import java.time.format.DateTimeFormatter; |
||||
|
|
||||
|
/** |
||||
|
* 警务评议工单业务工具类 |
||||
|
* |
||||
|
* @Author Antigravity |
||||
|
* @Date 2026-06-14 |
||||
|
*/ |
||||
|
public class WtczUtil { |
||||
|
|
||||
|
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); |
||||
|
|
||||
|
/** |
||||
|
* 根据工单当前处理状态,智能推导当前对应的回访阶段(首访或二访) |
||||
|
* |
||||
|
* @param gdzt 工单处理状态 |
||||
|
* @return 对应的回访阶段标识 |
||||
|
*/ |
||||
|
public static String determineXfjdByGdzt(String gdzt) { |
||||
|
if (WtczConst.GDZT_STASH.equals(gdzt)) { |
||||
|
return WtczConst.XFJD_FIRST; |
||||
|
} else if (WtczConst.GDZT_PROCESSING.equals(gdzt)) { |
||||
|
return WtczConst.XFJD_SECOND; |
||||
|
} |
||||
|
// 默认归入首访
|
||||
|
return WtczConst.XFJD_FIRST; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 生成唯一的工单受理编号 (slbh) |
||||
|
* 规则: SL + 毫秒时间戳 + 4位随机数 |
||||
|
* |
||||
|
* @return 唯一的工单受理编号 |
||||
|
*/ |
||||
|
public static String generateSlbh() { |
||||
|
long timestamp = System.currentTimeMillis(); |
||||
|
int random = (int) ((Math.random() * 9 + 1) * 1000); // 1000 ~ 9999
|
||||
|
return "SL" + timestamp + random; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据前端菜单传递的 tabType,向 Mybatis-Plus QueryWrapper 统一织入对应的状态过滤条件。 |
||||
|
* |
||||
|
* @param wrapper Mybatis-Plus 实体查询 Wrapper 容器 |
||||
|
* @param tabType 前端左侧三级菜单路由类型 |
||||
|
*/ |
||||
|
public static void buildTabTypeFilter(com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<net.lab1024.sa.admin.module.business.jwpy.domain.entity.WtczEntity> wrapper, String tabType) { |
||||
|
if (org.apache.commons.lang3.StringUtils.isBlank(tabType)) { |
||||
|
return; |
||||
|
} |
||||
|
switch (tabType) { |
||||
|
case "to_handle": |
||||
|
// 待办工单:包含暂存待首访(0)和办理中(1)
|
||||
|
wrapper.in(net.lab1024.sa.admin.module.business.jwpy.domain.entity.WtczEntity::getGdzt, java.util.List.of(WtczConst.GDZT_STASH, WtczConst.GDZT_PROCESSING)); |
||||
|
break; |
||||
|
case "transferred": |
||||
|
// 已流转工单:已经在流程办理核查中的工单(1)
|
||||
|
wrapper.eq(net.lab1024.sa.admin.module.business.jwpy.domain.entity.WtczEntity::getGdzt, WtczConst.GDZT_PROCESSING); |
||||
|
break; |
||||
|
case "completed": |
||||
|
// 已办结工单:(2)
|
||||
|
wrapper.eq(net.lab1024.sa.admin.module.business.jwpy.domain.entity.WtczEntity::getGdzt, WtczConst.GDZT_COMPLETED); |
||||
|
break; |
||||
|
case "about_to_expire": |
||||
|
// 即将超期 (未办结 且 创建时间在 2天前 到 3天前 之间)
|
||||
|
String twoDaysAgo = LocalDateTime.now().minusDays(2).format(DATE_TIME_FORMATTER); |
||||
|
String threeDaysAgoFor临期 = LocalDateTime.now().minusDays(3).format(DATE_TIME_FORMATTER); |
||||
|
wrapper.ne(net.lab1024.sa.admin.module.business.jwpy.domain.entity.WtczEntity::getGdzt, WtczConst.GDZT_COMPLETED) |
||||
|
.between(net.lab1024.sa.admin.module.business.jwpy.domain.entity.WtczEntity::getCreateTime, threeDaysAgoFor临期, twoDaysAgo); |
||||
|
break; |
||||
|
case "expired": |
||||
|
// 已超期 (未办结 且 创建时间早于 3天前,即 72 小时前)
|
||||
|
String threeDaysAgo = LocalDateTime.now().minusDays(3).format(DATE_TIME_FORMATTER); |
||||
|
wrapper.ne(net.lab1024.sa.admin.module.business.jwpy.domain.entity.WtczEntity::getGdzt, WtczConst.GDZT_COMPLETED) |
||||
|
.le(net.lab1024.sa.admin.module.business.jwpy.domain.entity.WtczEntity::getCreateTime, threeDaysAgo); |
||||
|
break; |
||||
|
case "invalid": |
||||
|
// 已作废 (占位状态,避免正常查出数据)
|
||||
|
wrapper.eq(net.lab1024.sa.admin.module.business.jwpy.domain.entity.WtczEntity::getGdzt, "9"); |
||||
|
break; |
||||
|
case "all": |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
/** |
||||
|
* 民意调查样本库与评议任务 api 封装 |
||||
|
* |
||||
|
* @Author: Antigravity |
||||
|
* @Date: 2026-06-15 |
||||
|
*/ |
||||
|
import { postRequest, getRequest } from '/@/lib/axios'; |
||||
|
|
||||
|
export const surveySampleApi = { |
||||
|
|
||||
|
/** |
||||
|
* 获取样本库分组列表 |
||||
|
*/ |
||||
|
queryGroupList: () => { |
||||
|
return getRequest('/mydc/sample/group/list'); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 新增样本分组 |
||||
|
*/ |
||||
|
addGroup: (param) => { |
||||
|
return postRequest('/mydc/sample/group/add', param); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 删除样本分组及下属所有样本 |
||||
|
*/ |
||||
|
deleteGroup: (id) => { |
||||
|
return getRequest(`/mydc/sample/group/delete/${id}`); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 分页查询当前分组样本人员名单 |
||||
|
*/ |
||||
|
querySamplePage: (param) => { |
||||
|
return postRequest('/mydc/sample/queryPage', param); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 新增单条样本人员 |
||||
|
*/ |
||||
|
addSample: (param) => { |
||||
|
return postRequest('/mydc/sample/add', param); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 粘贴文本批量解析导入样本人员 |
||||
|
*/ |
||||
|
batchAddSamples: (param) => { |
||||
|
return postRequest('/mydc/sample/batchAdd', param); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 删除样本人员 |
||||
|
*/ |
||||
|
deleteSample: (id) => { |
||||
|
return getRequest(`/mydc/sample/delete/${id}`); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 分页查询测评任务列表 |
||||
|
*/ |
||||
|
queryTaskPage: (param) => { |
||||
|
return postRequest('/mydc/task/queryPage', param); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 获取测评任务详情 |
||||
|
*/ |
||||
|
getTaskDetail: (id) => { |
||||
|
return getRequest(`/mydc/task/detail/${id}`); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 新建调查评议任务并批量向分组推送 |
||||
|
*/ |
||||
|
createSurveyTask: (param) => { |
||||
|
return postRequest('/mydc/task/create', param); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* H5填报端提交群众测评答案 |
||||
|
*/ |
||||
|
submitH5Answer: (param) => { |
||||
|
return postRequest('/mydc/task/submitAnswer', param); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 查询已配置的问卷模板列表 |
||||
|
*/ |
||||
|
querySurveys: (param) => { |
||||
|
return postRequest('/mydc/survey/queryPage', param); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 添加新问卷模板 |
||||
|
*/ |
||||
|
addSurvey: (param) => { |
||||
|
return postRequest('/mydc/survey/add', param); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 修改/保存问卷设计及状态 |
||||
|
*/ |
||||
|
updateSurvey: (param) => { |
||||
|
return postRequest('/mydc/survey/update', param); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 删除问卷模板 |
||||
|
*/ |
||||
|
deleteSurvey: (id) => { |
||||
|
return getRequest(`/mydc/survey/delete/${id}`); |
||||
|
} |
||||
|
}; |
||||
@ -0,0 +1,66 @@ |
|||||
|
// 级联静态数据(被反映单位字典选项)
|
||||
|
export const departmentOptions = [ |
||||
|
{ |
||||
|
value: 'hefei_gx', |
||||
|
label: '合肥市公安局高新分局', |
||||
|
children: [ |
||||
|
{ value: 'gx_hfpcs', label: '蜀麓派出所' }, |
||||
|
{ value: 'gx_gxdd', label: '高新交警大队' } |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
value: 'hefei_bh', |
||||
|
label: '合肥市公安局包河分局', |
||||
|
children: [ |
||||
|
{ value: 'bh_wnpcs', label: '万年埠派出所' }, |
||||
|
{ value: 'bh_bhyzd', label: '交警支队包河大队一中队' } |
||||
|
] |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
/** |
||||
|
* 查找级联部门路径的辅助函数 |
||||
|
* @param {string} code - 被反映单位最后一级编码 |
||||
|
* @param {Array} options - 部门级联静态字典数据 |
||||
|
* @returns {Array} 级联路径数组 |
||||
|
*/ |
||||
|
export function findDeptPath(code, options = departmentOptions) { |
||||
|
if (!code) return []; |
||||
|
for (const parent of options) { |
||||
|
if (parent.children) { |
||||
|
const child = parent.children.find(c => c.value === code); |
||||
|
if (child) { |
||||
|
return [parent.value, child.value]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return [code]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 统一将表单或详情中的字典、下拉字段的值转化为 String 字符串,避免 Ant Design 组件因强类型不匹配(如 Number 1 无法匹配 String "1")回显为原始数字的问题。 |
||||
|
* @param {Object} data - 工单数据对象 |
||||
|
*/ |
||||
|
export function formatDictFields(data) { |
||||
|
if (!data) return; |
||||
|
const dictFields = [ |
||||
|
'sfnm', |
||||
|
'sqrXb', |
||||
|
'sqfl', |
||||
|
'sjlyNo', |
||||
|
'sfsq', |
||||
|
'sfyyq', |
||||
|
'sjjzNo', |
||||
|
'ssqkNo', |
||||
|
'wzqkNo', |
||||
|
'secondSatisfaction', |
||||
|
'gdbq', |
||||
|
'sfsjfj', |
||||
|
'sqdj' // 诉求等级
|
||||
|
]; |
||||
|
Object.keys(data).forEach(key => { |
||||
|
if (dictFields.includes(key) && data[key] !== undefined && data[key] !== null) { |
||||
|
data[key] = String(data[key]); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,277 @@ |
|||||
|
<!-- |
||||
|
* 警务评议与问题处置 - 通用动态问卷渲染器组件 |
||||
|
* |
||||
|
* @Author: Antigravity |
||||
|
* @Date: 2026-06-14 |
||||
|
--> |
||||
|
<template> |
||||
|
<div class="survey-renderer"> |
||||
|
<div v-for="(q, idx) in questions" :key="q.id" class="q-card-item" :class="{ 'is-readonly': readOnly }"> |
||||
|
<!-- 题目头部 --> |
||||
|
<div class="q-title-row"> |
||||
|
<span class="q-req-star" v-if="q.required && !readOnly">*</span> |
||||
|
<span class="q-seq-num">Q{{ idx + 1 }}.</span> |
||||
|
<span class="q-title-text">{{ q.title || '(未命名题目)' }}</span> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 题目答案渲染区 --> |
||||
|
<div class="q-answer-body"> |
||||
|
<!-- 1. 星级评分题 --> |
||||
|
<div v-if="q.type === 'rate'" class="q-body-rate"> |
||||
|
<a-rate v-model:value="localAnswers[q.id]" :disabled="readOnly" class="renderer-rate" /> |
||||
|
<span class="rate-label-text" v-if="localAnswers[q.id]"> |
||||
|
{{ localAnswers[q.id] }} 星 |
||||
|
</span> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 2. 单选题 --> |
||||
|
<div v-else-if="q.type === 'radio'" class="q-body-radio"> |
||||
|
<!-- 编辑模式 --> |
||||
|
<a-radio-group v-if="!readOnly" v-model:value="localAnswers[q.id]" class="renderer-radio-group"> |
||||
|
<a-radio v-for="opt in q.options" :key="opt.label" :value="opt.label" class="renderer-radio-item"> |
||||
|
<span class="opt-label-text">{{ opt.label }}</span> |
||||
|
<span class="opt-score-badge" v-if="opt.score !== undefined && opt.score !== null"> |
||||
|
(分值: {{ opt.score }}) |
||||
|
</span> |
||||
|
</a-radio> |
||||
|
</a-radio-group> |
||||
|
<!-- 只读模式 --> |
||||
|
<div v-else class="readonly-value"> |
||||
|
<a-tag color="blue" class="val-tag">{{ localAnswers[q.id] || '未填写' }}</a-tag> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 3. 多选题 --> |
||||
|
<div v-else-if="q.type === 'checkbox'" class="q-body-checkbox"> |
||||
|
<!-- 编辑模式 --> |
||||
|
<a-checkbox-group v-if="!readOnly" v-model:value="localAnswers[q.id]" class="renderer-checkbox-group"> |
||||
|
<a-checkbox v-for="opt in q.options" :key="opt.label" :value="opt.label" class="renderer-checkbox-item"> |
||||
|
<span class="opt-label-text">{{ opt.label }}</span> |
||||
|
</a-checkbox> |
||||
|
</a-checkbox-group> |
||||
|
<!-- 只读模式 --> |
||||
|
<div v-else class="readonly-value"> |
||||
|
<template v-if="localAnswers[q.id] && localAnswers[q.id].length > 0"> |
||||
|
<a-tag v-for="tag in localAnswers[q.id]" :key="tag" color="blue" class="val-tag"> |
||||
|
{{ tag }} |
||||
|
</a-tag> |
||||
|
</template> |
||||
|
<span v-else class="no-val-text">未选择</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 4. 多行意见框 --> |
||||
|
<div v-else-if="q.type === 'textarea'" class="q-body-text"> |
||||
|
<a-textarea |
||||
|
v-if="!readOnly" |
||||
|
v-model:value="localAnswers[q.id]" |
||||
|
placeholder="请在此填写具体的意见与建议描述..." |
||||
|
:rows="3" |
||||
|
class="renderer-textarea" |
||||
|
maxLength="200" |
||||
|
showCount |
||||
|
/> |
||||
|
<div v-else class="readonly-textarea-box"> |
||||
|
{{ localAnswers[q.id] || '无建议或意见' }} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { ref, watch } from 'vue'; |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
questions: { |
||||
|
type: Array, |
||||
|
required: true, |
||||
|
default: () => [] |
||||
|
}, |
||||
|
value: { |
||||
|
type: Object, |
||||
|
required: true, |
||||
|
default: () => ({}) |
||||
|
}, |
||||
|
readOnly: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
const emit = defineEmits(['update:value']); |
||||
|
|
||||
|
// 同步本地数据 |
||||
|
const localAnswers = ref({ ...props.value }); |
||||
|
|
||||
|
watch( |
||||
|
() => props.value, |
||||
|
(newVal) => { |
||||
|
localAnswers.value = { ...newVal }; |
||||
|
}, |
||||
|
{ deep: true } |
||||
|
); |
||||
|
|
||||
|
watch( |
||||
|
localAnswers, |
||||
|
(newVal) => { |
||||
|
emit('update:value', newVal); |
||||
|
}, |
||||
|
{ deep: true } |
||||
|
); |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.survey-renderer { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 16px; |
||||
|
} |
||||
|
|
||||
|
.q-card-item { |
||||
|
background: #ffffff; |
||||
|
border: 1px solid #f0f0f0; |
||||
|
border-radius: 12px; |
||||
|
padding: 16px; |
||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.01); |
||||
|
transition: all 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
.q-card-item:hover { |
||||
|
border-color: #e6f7ff; |
||||
|
} |
||||
|
|
||||
|
.q-card-item.is-readonly { |
||||
|
padding: 12px 16px; |
||||
|
border-color: #f0f0f0; |
||||
|
background: #fafafa; |
||||
|
} |
||||
|
|
||||
|
.q-title-row { |
||||
|
margin-bottom: 12px; |
||||
|
line-height: 1.4; |
||||
|
display: flex; |
||||
|
align-items: flex-start; |
||||
|
} |
||||
|
|
||||
|
.q-req-star { |
||||
|
color: #ff4d4f; |
||||
|
margin-right: 4px; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.q-seq-num { |
||||
|
font-size: 14px; |
||||
|
font-weight: 700; |
||||
|
color: #1890ff; |
||||
|
margin-right: 6px; |
||||
|
} |
||||
|
|
||||
|
.q-title-text { |
||||
|
font-size: 14px; |
||||
|
font-weight: 700; |
||||
|
color: #262626; |
||||
|
} |
||||
|
|
||||
|
.q-answer-body { |
||||
|
padding-left: 4px; |
||||
|
} |
||||
|
|
||||
|
/* 打分类样式 */ |
||||
|
.q-body-rate { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 12px; |
||||
|
} |
||||
|
|
||||
|
.renderer-rate { |
||||
|
font-size: 24px; |
||||
|
} |
||||
|
|
||||
|
.rate-label-text { |
||||
|
font-size: 13px; |
||||
|
color: #fa8c16; |
||||
|
font-weight: 700; |
||||
|
} |
||||
|
|
||||
|
/* 按钮、单选多选样式 */ |
||||
|
.renderer-radio-group, |
||||
|
.renderer-checkbox-group { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 8px; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.renderer-radio-item, |
||||
|
.renderer-checkbox-item { |
||||
|
margin: 0 !important; |
||||
|
background: #fafafa; |
||||
|
border: 1px solid #f0f0f0; |
||||
|
border-radius: 8px; |
||||
|
padding: 10px 12px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
transition: all 0.2s ease; |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.renderer-radio-item:hover, |
||||
|
.renderer-checkbox-item:hover { |
||||
|
border-color: #1890ff; |
||||
|
background: #f0f7ff; |
||||
|
} |
||||
|
|
||||
|
.opt-label-text { |
||||
|
font-size: 13px; |
||||
|
color: #595959; |
||||
|
} |
||||
|
|
||||
|
.opt-score-badge { |
||||
|
color: #bfbfbf; |
||||
|
font-size: 11px; |
||||
|
margin-left: 6px; |
||||
|
} |
||||
|
|
||||
|
/* 多行文本框样式 */ |
||||
|
.renderer-textarea { |
||||
|
border-radius: 8px; |
||||
|
background: #fafafa; |
||||
|
border: 1px solid #d9d9d9; |
||||
|
} |
||||
|
|
||||
|
/* 只读状态细节渲染 */ |
||||
|
.readonly-value { |
||||
|
padding: 4px 0; |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 6px; |
||||
|
} |
||||
|
|
||||
|
.val-tag { |
||||
|
font-size: 13px; |
||||
|
padding: 4px 10px; |
||||
|
border-radius: 6px; |
||||
|
margin: 0 !important; |
||||
|
} |
||||
|
|
||||
|
.no-val-text { |
||||
|
color: #bfbfbf; |
||||
|
font-size: 13px; |
||||
|
font-style: italic; |
||||
|
} |
||||
|
|
||||
|
.readonly-textarea-box { |
||||
|
background: #f5f5f5; |
||||
|
border: 1px solid #e8e8e8; |
||||
|
border-radius: 8px; |
||||
|
padding: 10px 14px; |
||||
|
color: #595959; |
||||
|
font-size: 13px; |
||||
|
line-height: 1.5; |
||||
|
min-height: 48px; |
||||
|
word-break: break-all; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,491 @@ |
|||||
|
<!-- |
||||
|
* 警务评议与问题处置 - 群众自助评议手机端 H5 填报页面 |
||||
|
* |
||||
|
* @Author: Antigravity |
||||
|
* @Date: 2026-06-14 |
||||
|
--> |
||||
|
<template> |
||||
|
<div class="h5-container"> |
||||
|
<div class="h5-phone-mockup"> |
||||
|
<!-- 手机顶部状态栏模拟 --> |
||||
|
<div class="phone-status-bar"> |
||||
|
<span class="time">10:24</span> |
||||
|
<div class="icons"> |
||||
|
<span class="signal">📶</span> |
||||
|
<span class="wifi">📶</span> |
||||
|
<span class="battery">🔋</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 手机页面头部 --> |
||||
|
<div class="phone-navbar"> |
||||
|
<span class="navbar-title">{{ surveyInfo.title || '群众意见自主测评' }}</span> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 手机主内容区 --> |
||||
|
<div class="phone-body"> |
||||
|
<!-- 答卷成功感谢状态 --> |
||||
|
<div v-if="submitted" class="success-state"> |
||||
|
<div class="success-icon-wrap"> |
||||
|
<div class="success-checkmark">✓</div> |
||||
|
</div> |
||||
|
<h2>评议提交成功</h2> |
||||
|
<p class="success-desc">感谢您对我们的支持与配合!您的宝贵意见是推动我们不断改进服务的动力。</p> |
||||
|
<div class="divider"></div> |
||||
|
<p class="success-footer">XX市公安局民意感知中心</p> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 正常填报状态 --> |
||||
|
<div v-else-if="loading" class="loading-state"> |
||||
|
<a-spin size="large" tip="正在加载问卷内容..." /> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else-if="errorMsg" class="error-state"> |
||||
|
<div class="error-icon">⚠️</div> |
||||
|
<h2>加载失败</h2> |
||||
|
<p>{{ errorMsg }}</p> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else class="survey-form"> |
||||
|
<!-- 问卷寄语 --> |
||||
|
<div class="intro-card"> |
||||
|
<div class="intro-title">尊敬的市民朋友:</div> |
||||
|
<p class="intro-body"> |
||||
|
您好!为持续改进我局政务办事效率与窗口服务质量,诚邀您参与本次针对您近期办理业务的满意度评议。整个问卷约需1分钟,感谢您的参与! |
||||
|
</p> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 问题列表 --> |
||||
|
<SurveyFormRenderer :questions="questions" v-model:value="answers" /> |
||||
|
|
||||
|
<!-- 提交按钮 --> |
||||
|
<div class="submit-action"> |
||||
|
<a-button type="primary" block size="large" class="h5-submit-btn" @click="handleSubmit"> |
||||
|
提交评议反馈 |
||||
|
</a-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { ref, reactive, onMounted } from 'vue'; |
||||
|
import { useRoute } from 'vue-router'; |
||||
|
import { message } from 'ant-design-vue'; |
||||
|
import SurveyFormRenderer from './components/SurveyFormRenderer.vue'; |
||||
|
import { surveySampleApi } from '/@/api/business/jwpy/survey-sample-api'; |
||||
|
|
||||
|
const route = useRoute(); |
||||
|
|
||||
|
const loading = ref(true); |
||||
|
const submitted = ref(false); |
||||
|
const errorMsg = ref(''); |
||||
|
|
||||
|
const taskInfo = ref({}); |
||||
|
const surveyInfo = ref({}); |
||||
|
const questions = ref([]); |
||||
|
const answers = reactive({}); |
||||
|
|
||||
|
onMounted(() => { |
||||
|
initSurvey(); |
||||
|
}); |
||||
|
|
||||
|
const initSurvey = async () => { |
||||
|
const taskId = route.query.taskId; |
||||
|
if (!taskId) { |
||||
|
errorMsg.value = '无效的评议参数(缺少 taskId)'; |
||||
|
loading.value = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 1. 读取对应任务详情 |
||||
|
const res = await surveySampleApi.getTaskDetail(taskId); |
||||
|
const task = res.data; |
||||
|
if (!task) { |
||||
|
errorMsg.value = '未找到对应的评议任务记录'; |
||||
|
loading.value = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (task.status === 1) { |
||||
|
submitted.value = true; |
||||
|
loading.value = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
taskInfo.value = task; |
||||
|
|
||||
|
// 2. 读取对应问卷模板 |
||||
|
const surveyRes = await surveySampleApi.querySurveys({ pageNum: 1, pageSize: 1000 }); |
||||
|
const templates = surveyRes.data ? (surveyRes.data.list || []) : []; |
||||
|
|
||||
|
let template = null; |
||||
|
if (task.surveyId) { |
||||
|
template = templates.find(t => (t.id || t.surveyId) === task.surveyId); |
||||
|
} |
||||
|
if (!template) { |
||||
|
template = templates.find(t => t.sjlyNo === task.sjlyNo); |
||||
|
} |
||||
|
if (!template) { |
||||
|
template = templates[0]; // 默认兜底 |
||||
|
} |
||||
|
|
||||
|
if (!template) { |
||||
|
errorMsg.value = '暂无可用的评议问卷模板,请联系管理员配置。'; |
||||
|
loading.value = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
surveyInfo.value = template; |
||||
|
try { |
||||
|
questions.value = JSON.parse(template.content); |
||||
|
// 初始化回答数据结构 |
||||
|
questions.value.forEach(q => { |
||||
|
if (q.type === 'checkbox') { |
||||
|
answers[q.id] = []; |
||||
|
} else { |
||||
|
answers[q.id] = undefined; |
||||
|
} |
||||
|
}); |
||||
|
} catch (e) { |
||||
|
questions.value = []; |
||||
|
} |
||||
|
} catch (err) { |
||||
|
errorMsg.value = '加载测评内容失败,请确保后端服务正常且表结构已同步'; |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const handleSubmit = async () => { |
||||
|
// 核心校验必填项 |
||||
|
for (const q of questions.value) { |
||||
|
const ans = answers[q.id]; |
||||
|
if (q.required) { |
||||
|
if (q.type === 'checkbox' && (!ans || ans.length === 0)) { |
||||
|
message.warning(`请填写必填项: ${q.title}`); |
||||
|
return; |
||||
|
} |
||||
|
if (q.type !== 'checkbox' && (ans === undefined || ans === null || String(ans).trim() === '')) { |
||||
|
message.warning(`请填写必填项: ${q.title}`); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
const res = await surveySampleApi.submitH5Answer({ |
||||
|
taskId: taskInfo.value.id, |
||||
|
answers: JSON.stringify(answers) |
||||
|
}); |
||||
|
|
||||
|
// 如果是 110 等工单,我们也去模拟关联回流更新工单列表里的满意度数据 |
||||
|
updateWorkOrderSatisfaction(taskInfo.value); |
||||
|
|
||||
|
submitted.value = true; |
||||
|
message.success(res.msg || '评议提交成功'); |
||||
|
} catch (err) { |
||||
|
message.error('提交答卷失败,请检查您的网络连接'); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 满意度智能回流至工单主表(模拟真实的分布式服务对齐) |
||||
|
const updateWorkOrderSatisfaction = (task) => { |
||||
|
// 查找是否有对应的满意度评价分 |
||||
|
let score = 5; |
||||
|
for (const key in answers) { |
||||
|
if (typeof answers[key] === 'number') { |
||||
|
score = answers[key]; // 获取评分题分值 |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 模拟判定:满意度 1 满意,2 不满意 |
||||
|
const sfjg = score >= 3 ? '1' : '2'; |
||||
|
const qzmyqk = score >= 3 ? '1' : '2'; |
||||
|
|
||||
|
// 这里可以关联真实的业务数据表或本地工单缓存(如果存在),让工单页也立刻展示最新群众自填结果 |
||||
|
// 模拟自填回流通知 |
||||
|
console.log(`群众 H5 自填回流成功:任务ID=${task.id}, 评分=${score}星, 满意度归类=${qzmyqk}`); |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
/* 群众手机端外围仿真背景 */ |
||||
|
.h5-container { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
min-height: 100vh; |
||||
|
background-color: #f1f3f6; |
||||
|
padding: 24px 0; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
/* 手机外观容器 */ |
||||
|
.h5-phone-mockup { |
||||
|
width: 375px; |
||||
|
height: 720px; |
||||
|
background: #000000; |
||||
|
border-radius: 40px; |
||||
|
padding: 12px; |
||||
|
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.15); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
box-sizing: border-box; |
||||
|
border: 4px solid #1f2937; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
/* 手机状态栏 */ |
||||
|
.phone-status-bar { |
||||
|
height: 20px; |
||||
|
color: #ffffff; |
||||
|
font-size: 11px; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 0 16px; |
||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; |
||||
|
} |
||||
|
.phone-status-bar .icons { |
||||
|
display: flex; |
||||
|
gap: 4px; |
||||
|
} |
||||
|
|
||||
|
/* 页面顶部导航栏 */ |
||||
|
.phone-navbar { |
||||
|
height: 44px; |
||||
|
background: #1890ff; |
||||
|
color: #ffffff; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
font-size: 15px; |
||||
|
font-weight: 700; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.navbar-title { |
||||
|
padding: 0 16px; |
||||
|
white-space: nowrap; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
max-width: 280px; |
||||
|
} |
||||
|
|
||||
|
/* 手机屏幕主内容区 */ |
||||
|
.phone-body { |
||||
|
flex: 1; |
||||
|
background: #f5f6fa; |
||||
|
border-bottom-left-radius: 30px; |
||||
|
border-bottom-right-radius: 30px; |
||||
|
overflow-y: auto; |
||||
|
padding: 16px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.phone-body::-webkit-scrollbar { |
||||
|
width: 4px; |
||||
|
} |
||||
|
.phone-body::-webkit-scrollbar-thumb { |
||||
|
background: rgba(0, 0, 0, 0.05); |
||||
|
border-radius: 2px; |
||||
|
} |
||||
|
|
||||
|
/* 问卷寄语 */ |
||||
|
.intro-card { |
||||
|
background: #ffffff; |
||||
|
border-radius: 12px; |
||||
|
padding: 14px; |
||||
|
margin-bottom: 16px; |
||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02); |
||||
|
border: 1px solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.intro-title { |
||||
|
font-size: 14px; |
||||
|
font-weight: 700; |
||||
|
color: #2c3e50; |
||||
|
margin-bottom: 6px; |
||||
|
} |
||||
|
|
||||
|
.intro-body { |
||||
|
font-size: 12px; |
||||
|
color: #7f8c8d; |
||||
|
line-height: 1.6; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
/* 问题卡片 */ |
||||
|
.question-card { |
||||
|
background: #ffffff; |
||||
|
border-radius: 12px; |
||||
|
padding: 16px; |
||||
|
margin-bottom: 14px; |
||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02); |
||||
|
border: 1px solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.q-header { |
||||
|
margin-bottom: 12px; |
||||
|
line-height: 1.4; |
||||
|
} |
||||
|
|
||||
|
.q-req { |
||||
|
color: #ff4d4f; |
||||
|
margin-right: 4px; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.q-index { |
||||
|
font-size: 14px; |
||||
|
font-weight: 700; |
||||
|
color: #1890ff; |
||||
|
margin-right: 4px; |
||||
|
} |
||||
|
|
||||
|
.q-title { |
||||
|
font-size: 14px; |
||||
|
font-weight: 700; |
||||
|
color: #262626; |
||||
|
} |
||||
|
|
||||
|
.q-body { |
||||
|
padding-top: 4px; |
||||
|
} |
||||
|
|
||||
|
/* 星级评价 */ |
||||
|
.rate-body { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
padding: 8px 0; |
||||
|
} |
||||
|
|
||||
|
.rate-label { |
||||
|
margin-top: 8px; |
||||
|
font-size: 12px; |
||||
|
color: #8c8c8c; |
||||
|
} |
||||
|
|
||||
|
.score-num { |
||||
|
color: #f1c40f; |
||||
|
font-weight: 700; |
||||
|
} |
||||
|
|
||||
|
/* 单选多选移动端样式 */ |
||||
|
.mobile-radio-group, |
||||
|
.mobile-checkbox-group { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
.mobile-radio-item, |
||||
|
.mobile-checkbox-item { |
||||
|
margin: 0 !important; |
||||
|
background: #fafafa; |
||||
|
border: 1px solid #f0f0f0; |
||||
|
border-radius: 8px; |
||||
|
padding: 10px 12px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
transition: all 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
.mobile-radio-item:hover, |
||||
|
.mobile-checkbox-item:hover { |
||||
|
border-color: #1890ff; |
||||
|
background: #f0f7ff; |
||||
|
} |
||||
|
|
||||
|
.opt-text { |
||||
|
font-size: 13px; |
||||
|
color: #595959; |
||||
|
} |
||||
|
|
||||
|
/* 多行文本框 */ |
||||
|
.mobile-textarea { |
||||
|
border-radius: 8px; |
||||
|
background: #fafafa; |
||||
|
} |
||||
|
|
||||
|
/* 提交按钮 */ |
||||
|
.submit-action { |
||||
|
margin: 16px 0 24px; |
||||
|
} |
||||
|
|
||||
|
.h5-submit-btn { |
||||
|
height: 48px; |
||||
|
border-radius: 12px; |
||||
|
font-size: 16px; |
||||
|
font-weight: 700; |
||||
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.2); |
||||
|
} |
||||
|
|
||||
|
/* 加载、错误、成功页面状态 */ |
||||
|
.loading-state, |
||||
|
.error-state, |
||||
|
.success-state { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
padding: 24px; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.error-icon { |
||||
|
font-size: 48px; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.success-state h2 { |
||||
|
font-size: 20px; |
||||
|
font-weight: 700; |
||||
|
color: #2c3e50; |
||||
|
margin: 16px 0 8px; |
||||
|
} |
||||
|
|
||||
|
.success-desc { |
||||
|
font-size: 13px; |
||||
|
color: #7f8c8d; |
||||
|
line-height: 1.6; |
||||
|
} |
||||
|
|
||||
|
.success-icon-wrap { |
||||
|
width: 72px; |
||||
|
height: 72px; |
||||
|
background: #f6ffed; |
||||
|
border: 2px solid #b7eb8f; |
||||
|
border-radius: 50%; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
margin-bottom: 8px; |
||||
|
} |
||||
|
|
||||
|
.success-checkmark { |
||||
|
font-size: 36px; |
||||
|
color: #52c41a; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.success-state .divider { |
||||
|
width: 60px; |
||||
|
height: 1px; |
||||
|
background: #d9d9d9; |
||||
|
margin: 24px 0; |
||||
|
} |
||||
|
|
||||
|
.success-footer { |
||||
|
font-size: 11px; |
||||
|
color: #bfbfbf; |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue