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