问卷和警告bug修复
This commit is contained in:
parent
612becc4eb
commit
2e7104c0c6
|
|
@ -168,18 +168,23 @@ public class PsyAssessmentController extends BaseController
|
|||
public AjaxResult start(@Validated @RequestBody AssessmentStartVO startVO)
|
||||
{
|
||||
try {
|
||||
Long userId = null;
|
||||
String username = null;
|
||||
Long currentUserId = null;
|
||||
String currentUsername = null;
|
||||
|
||||
// 尝试获取当前登录用户信息
|
||||
try {
|
||||
userId = SecurityUtils.getUserId();
|
||||
username = SecurityUtils.getUsername();
|
||||
currentUserId = SecurityUtils.getUserId();
|
||||
currentUsername = SecurityUtils.getUsername();
|
||||
} catch (Exception e) {
|
||||
logger.warn("获取用户信息失败,可能是匿名访问: {}", e.getMessage());
|
||||
// 如果是匿名访问,userId 和 username 可以为 null
|
||||
}
|
||||
|
||||
// 确定实际的测评用户ID
|
||||
// 如果传入了targetUserId(管理员代替用户测评),使用targetUserId
|
||||
// 否则使用当前登录用户ID
|
||||
Long assessmentUserId = startVO.getTargetUserId() != null ? startVO.getTargetUserId() : currentUserId;
|
||||
|
||||
PsyAssessment assessment = new PsyAssessment();
|
||||
assessment.setScaleId(startVO.getScaleId());
|
||||
assessment.setAssesseeName(startVO.getAssesseeName());
|
||||
|
|
@ -191,20 +196,20 @@ public class PsyAssessmentController extends BaseController
|
|||
assessment.setIpAddress(IpUtils.getIpAddr());
|
||||
assessment.setUserAgent(ServletUtils.getRequest().getHeader("User-Agent"));
|
||||
|
||||
// 设置用户ID(如果获取到了)
|
||||
if (userId != null) {
|
||||
assessment.setUserId(userId);
|
||||
// 设置用户ID(优先使用targetUserId)
|
||||
if (assessmentUserId != null) {
|
||||
assessment.setUserId(assessmentUserId);
|
||||
}
|
||||
|
||||
// 设置创建者(如果获取到了)
|
||||
if (username != null && !username.isEmpty()) {
|
||||
assessment.setCreateBy(username);
|
||||
// 设置创建者(记录是谁创建的测评)
|
||||
if (currentUsername != null && !currentUsername.isEmpty()) {
|
||||
assessment.setCreateBy(currentUsername);
|
||||
} else {
|
||||
assessment.setCreateBy("system"); // 默认值
|
||||
}
|
||||
|
||||
logger.info("创建测评 - scaleId: {}, userId: {}, assesseeName: {}",
|
||||
startVO.getScaleId(), userId, startVO.getAssesseeName());
|
||||
logger.info("创建测评 - scaleId: {}, targetUserId: {}, assessmentUserId: {}, currentUserId: {}, assesseeName: {}",
|
||||
startVO.getScaleId(), startVO.getTargetUserId(), assessmentUserId, currentUserId, startVO.getAssesseeName());
|
||||
|
||||
int result = assessmentService.insertAssessment(assessment);
|
||||
|
||||
|
|
|
|||
|
|
@ -91,32 +91,25 @@ public class PsyAssessmentReportController extends BaseController
|
|||
*/
|
||||
@PreAuthorize("@ss.hasPermi('psychology:report:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(PsyAssessmentReport report)
|
||||
public TableDataInfo list(PsyAssessmentReport report, Long userId)
|
||||
{
|
||||
System.out.println("开始查询报告列表");
|
||||
System.out.println("开始查询报告列表,sourceType: " + report.getSourceType() + ", userId: " + userId);
|
||||
// 由于需要合并两种报告,需要手动分页,所以先清理分页参数
|
||||
clearPage();
|
||||
|
||||
// 查询测评报告(不使用分页,获取全部数据)
|
||||
List<PsyAssessmentReport> assessmentReports = reportService.selectReportList(report);
|
||||
System.out.println("查询到测评报告数量: " + (assessmentReports != null ? assessmentReports.size() : 0));
|
||||
|
||||
// 查询问卷报告(不使用分页,获取全部数据)
|
||||
PsyQuestionnaireReport questionnaireReport = new PsyQuestionnaireReport();
|
||||
if (report.getReportType() != null && !report.getReportType().isEmpty()) {
|
||||
questionnaireReport.setReportType(report.getReportType());
|
||||
}
|
||||
if (report.getIsGenerated() != null && !report.getIsGenerated().isEmpty()) {
|
||||
questionnaireReport.setIsGenerated(report.getIsGenerated());
|
||||
}
|
||||
List<PsyQuestionnaireReport> questionnaireReports = questionnaireReportMapper.selectReportList(questionnaireReport);
|
||||
System.out.println("查询到问卷报告数量: " + (questionnaireReports != null ? questionnaireReports.size() : 0));
|
||||
// 获取来源类型过滤参数
|
||||
String sourceTypeFilter = report.getSourceType();
|
||||
|
||||
// 合并报告列表(转换为统一的VO格式)
|
||||
List<ReportVO> allReports = new ArrayList<>();
|
||||
|
||||
// 添加测评报告
|
||||
for (PsyAssessmentReport ar : assessmentReports) {
|
||||
// 如果未指定sourceType或指定为"assessment",查询测评报告
|
||||
if (sourceTypeFilter == null || sourceTypeFilter.isEmpty() || "assessment".equals(sourceTypeFilter)) {
|
||||
List<PsyAssessmentReport> assessmentReports = reportService.selectReportList(report);
|
||||
System.out.println("查询到测评报告数量: " + (assessmentReports != null ? assessmentReports.size() : 0));
|
||||
|
||||
// 添加测评报告
|
||||
for (PsyAssessmentReport ar : assessmentReports) {
|
||||
ReportVO vo = new ReportVO();
|
||||
vo.setReportId(ar.getReportId());
|
||||
vo.setSourceType("assessment");
|
||||
|
|
@ -133,17 +126,34 @@ public class PsyAssessmentReportController extends BaseController
|
|||
PsyAssessment assessment = assessmentService.selectAssessmentById(ar.getAssessmentId());
|
||||
if (assessment != null) {
|
||||
vo.setUserId(assessment.getUserId());
|
||||
// 如果指定了userId过滤,跳过不匹配的记录
|
||||
if (userId != null && !userId.equals(assessment.getUserId())) {
|
||||
continue;
|
||||
}
|
||||
// 获取用户档案信息编号
|
||||
com.ddnai.system.domain.psychology.PsyUserProfile profile = profileService.selectProfileByUserId(assessment.getUserId());
|
||||
if (profile != null) {
|
||||
vo.setInfoNumber(profile.getInfoNumber());
|
||||
}
|
||||
}
|
||||
allReports.add(vo);
|
||||
allReports.add(vo);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加问卷报告
|
||||
for (PsyQuestionnaireReport qr : questionnaireReports) {
|
||||
// 如果未指定sourceType或指定为"questionnaire",查询问卷报告
|
||||
if (sourceTypeFilter == null || sourceTypeFilter.isEmpty() || "questionnaire".equals(sourceTypeFilter)) {
|
||||
PsyQuestionnaireReport questionnaireReport = new PsyQuestionnaireReport();
|
||||
if (report.getReportType() != null && !report.getReportType().isEmpty()) {
|
||||
questionnaireReport.setReportType(report.getReportType());
|
||||
}
|
||||
if (report.getIsGenerated() != null && !report.getIsGenerated().isEmpty()) {
|
||||
questionnaireReport.setIsGenerated(report.getIsGenerated());
|
||||
}
|
||||
List<PsyQuestionnaireReport> questionnaireReports = questionnaireReportMapper.selectReportList(questionnaireReport);
|
||||
System.out.println("查询到问卷报告数量: " + (questionnaireReports != null ? questionnaireReports.size() : 0));
|
||||
|
||||
// 添加问卷报告
|
||||
for (PsyQuestionnaireReport qr : questionnaireReports) {
|
||||
ReportVO vo = new ReportVO();
|
||||
vo.setReportId(qr.getReportId());
|
||||
vo.setSourceType("questionnaire");
|
||||
|
|
@ -160,14 +170,19 @@ public class PsyAssessmentReportController extends BaseController
|
|||
com.ddnai.system.domain.psychology.PsyQuestionnaireAnswer answer = questionnaireAnswerService.selectAnswerById(qr.getAnswerId());
|
||||
if (answer != null) {
|
||||
vo.setUserId(answer.getUserId());
|
||||
// 如果指定了userId过滤,跳过不匹配的记录
|
||||
if (userId != null && !userId.equals(answer.getUserId())) {
|
||||
continue;
|
||||
}
|
||||
// 获取用户档案信息编号
|
||||
com.ddnai.system.domain.psychology.PsyUserProfile profile = profileService.selectProfileByUserId(answer.getUserId());
|
||||
if (profile != null) {
|
||||
vo.setInfoNumber(profile.getInfoNumber());
|
||||
}
|
||||
}
|
||||
allReports.add(vo);
|
||||
System.out.println("添加问卷报告: reportId=" + qr.getReportId() + ", answerId=" + qr.getAnswerId() + ", title=" + qr.getReportTitle());
|
||||
allReports.add(vo);
|
||||
System.out.println("添加问卷报告: reportId=" + qr.getReportId() + ", answerId=" + qr.getAnswerId() + ", title=" + qr.getReportTitle());
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("合并后总报告数: " + allReports.size());
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.ddnai.web.controller.psychology;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -80,6 +81,7 @@ public class PsyQuestionnaireAnswerController extends BaseController
|
|||
|
||||
/**
|
||||
* 开始问卷答题
|
||||
* 支持重复答题:每次都创建新的答题记录
|
||||
*/
|
||||
@PostMapping("/start")
|
||||
public AjaxResult start(@RequestBody Map<String, Object> params)
|
||||
|
|
@ -90,6 +92,13 @@ public class PsyQuestionnaireAnswerController extends BaseController
|
|||
// 获取被测试用户ID:如果前端传递了userId(管理员代测),则使用传递的userId;否则使用当前登录用户ID
|
||||
Long userId = params.get("userId") != null ? Long.valueOf(params.get("userId").toString()) : SecurityUtils.getUserId();
|
||||
|
||||
System.out.println("========================================");
|
||||
System.out.println("📝 开始问卷答题");
|
||||
System.out.println("questionnaireId: " + questionnaireId + ", userId: " + userId);
|
||||
System.out.println("允许重复答题,创建新的答题记录");
|
||||
System.out.println("========================================");
|
||||
|
||||
// 每次都创建新的答题记录,支持重复答题
|
||||
PsyQuestionnaireAnswer answer = new PsyQuestionnaireAnswer();
|
||||
answer.setQuestionnaireId(questionnaireId);
|
||||
answer.setUserId(userId); // 使用被测试用户ID
|
||||
|
|
@ -102,8 +111,10 @@ public class PsyQuestionnaireAnswerController extends BaseController
|
|||
|
||||
if (result > 0)
|
||||
{
|
||||
System.out.println("✅ 创建答题记录成功,answerId: " + answer.getAnswerId());
|
||||
return success(answer.getAnswerId());
|
||||
}
|
||||
System.err.println("❌ 创建答题记录失败");
|
||||
return error("开始答题失败");
|
||||
}
|
||||
|
||||
|
|
@ -166,7 +177,10 @@ public class PsyQuestionnaireAnswerController extends BaseController
|
|||
{
|
||||
try
|
||||
{
|
||||
System.out.println("开始提交问卷,answerId: " + answerId + ", 用户: " + SecurityUtils.getUsername());
|
||||
System.out.println("========================================");
|
||||
System.out.println("🔧 [修复版本 2025-11-22] 开始提交问卷");
|
||||
System.out.println("answerId: " + answerId + ", 用户: " + SecurityUtils.getUsername());
|
||||
System.out.println("========================================");
|
||||
|
||||
PsyQuestionnaireAnswer answer = answerService.selectAnswerById(answerId);
|
||||
if (answer == null)
|
||||
|
|
@ -241,8 +255,35 @@ public class PsyQuestionnaireAnswerController extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
// 按总分降序排序,总分相同按提交时间升序
|
||||
filteredList.sort((a, b) -> {
|
||||
// 按用户分组,每个用户只保留最高分的记录
|
||||
Map<Long, PsyQuestionnaireAnswer> userBestScores = new HashMap<>();
|
||||
for (PsyQuestionnaireAnswer answer : filteredList) {
|
||||
Long userId = answer.getUserId();
|
||||
if (userId == null) continue;
|
||||
|
||||
PsyQuestionnaireAnswer existing = userBestScores.get(userId);
|
||||
if (existing == null) {
|
||||
userBestScores.put(userId, answer);
|
||||
} else {
|
||||
// 比较分数,保留更高的
|
||||
BigDecimal existingScore = existing.getTotalScore() != null ? existing.getTotalScore() : BigDecimal.ZERO;
|
||||
BigDecimal currentScore = answer.getTotalScore() != null ? answer.getTotalScore() : BigDecimal.ZERO;
|
||||
|
||||
if (currentScore.compareTo(existingScore) > 0) {
|
||||
userBestScores.put(userId, answer);
|
||||
} else if (currentScore.compareTo(existingScore) == 0) {
|
||||
// 分数相同,保留提交时间更早的
|
||||
if (answer.getSubmitTime() != null && existing.getSubmitTime() != null
|
||||
&& answer.getSubmitTime().before(existing.getSubmitTime())) {
|
||||
userBestScores.put(userId, answer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为列表并按总分降序排序
|
||||
List<PsyQuestionnaireAnswer> uniqueList = new ArrayList<>(userBestScores.values());
|
||||
uniqueList.sort((a, b) -> {
|
||||
if (a.getTotalScore() == null && b.getTotalScore() == null) return 0;
|
||||
if (a.getTotalScore() == null) return 1;
|
||||
if (b.getTotalScore() == null) return -1;
|
||||
|
|
@ -254,31 +295,17 @@ public class PsyQuestionnaireAnswerController extends BaseController
|
|||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
// 设置排名
|
||||
int rank = 1;
|
||||
for (PsyQuestionnaireAnswer answer : uniqueList) {
|
||||
answer.setRank(rank++);
|
||||
}
|
||||
|
||||
// 补充未填写的答题人姓名(默认使用系统用户昵称/账号)
|
||||
enrichRespondentNames(filteredList);
|
||||
enrichRespondentNames(uniqueList);
|
||||
|
||||
return success(filteredList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动生成问卷报告(用于测试和修复)
|
||||
*/
|
||||
@PostMapping("/generateReport/{answerId}")
|
||||
public AjaxResult generateReport(@PathVariable Long answerId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 通过反射调用私有方法,或者将方法改为public
|
||||
// 这里我们直接调用service的公共方法
|
||||
// 由于generateQuestionnaireReport是private,我们需要创建一个公共方法
|
||||
answerService.generateReport(answerId);
|
||||
return success("报告生成成功");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return error("报告生成失败:" + e.getMessage());
|
||||
}
|
||||
return success(uniqueList);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -392,5 +419,43 @@ public class PsyQuestionnaireAnswerController extends BaseController
|
|||
answer.setRespondentName(cachedName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发报告生成(用于补救)
|
||||
*/
|
||||
@PostMapping("/generateReport/{answerId}")
|
||||
public AjaxResult manualGenerateReport(@PathVariable Long answerId)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.out.println("========================================");
|
||||
System.out.println("🔧 手动触发报告生成");
|
||||
System.out.println("answerId: " + answerId);
|
||||
System.out.println("========================================");
|
||||
|
||||
PsyQuestionnaireAnswer answer = answerService.selectAnswerById(answerId);
|
||||
if (answer == null)
|
||||
{
|
||||
return error("答题记录不存在");
|
||||
}
|
||||
|
||||
if (!"1".equals(answer.getStatus()))
|
||||
{
|
||||
return error("问卷未提交,无法生成报告");
|
||||
}
|
||||
|
||||
// 调用报告生成服务
|
||||
answerService.generateReport(answerId);
|
||||
|
||||
System.out.println("✅ 手动报告生成完成");
|
||||
return success("报告生成成功");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.err.println("❌ 手动报告生成失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return error("报告生成失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,5 +84,16 @@ public class PsyQuestionnaireItemController extends BaseController
|
|||
{
|
||||
return toAjax(itemService.deleteItemByIds(itemIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新问卷总分(根据题目分数自动计算)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('psychology:questionnaire:edit')")
|
||||
@Log(title = "更新问卷总分", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/updateTotalScore/{questionnaireId}")
|
||||
public AjaxResult updateTotalScore(@PathVariable Long questionnaireId)
|
||||
{
|
||||
return toAjax(itemService.updateQuestionnaireTotalScore(questionnaireId));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ public class PsyAssessment extends BaseEntity
|
|||
/** 用户代理 */
|
||||
private String userAgent;
|
||||
|
||||
/** 是否有报告(关联查询字段,不存储在表中) */
|
||||
private Boolean hasReport;
|
||||
|
||||
public Long getAssessmentId()
|
||||
{
|
||||
return assessmentId;
|
||||
|
|
@ -279,6 +282,16 @@ public class PsyAssessment extends BaseEntity
|
|||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
public Boolean getHasReport()
|
||||
{
|
||||
return hasReport;
|
||||
}
|
||||
|
||||
public void setHasReport(Boolean hasReport)
|
||||
{
|
||||
this.hasReport = hasReport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ public class PsyAssessmentReport extends BaseEntity
|
|||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private java.util.Date generateTime;
|
||||
|
||||
/** 来源类型(用于查询过滤,不存储到数据库) */
|
||||
private String sourceType;
|
||||
|
||||
public Long getReportId()
|
||||
{
|
||||
return reportId;
|
||||
|
|
@ -148,6 +151,16 @@ public class PsyAssessmentReport extends BaseEntity
|
|||
this.generateTime = generateTime;
|
||||
}
|
||||
|
||||
public String getSourceType()
|
||||
{
|
||||
return sourceType;
|
||||
}
|
||||
|
||||
public void setSourceType(String sourceType)
|
||||
{
|
||||
this.sourceType = sourceType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ public class AssessmentStartVO
|
|||
/** 是否匿名测评 */
|
||||
private Boolean anonymous;
|
||||
|
||||
/** 目标用户ID(管理员代替用户测评时使用) */
|
||||
private Long targetUserId;
|
||||
|
||||
public Long getScaleId()
|
||||
{
|
||||
return scaleId;
|
||||
|
|
@ -87,5 +90,15 @@ public class AssessmentStartVO
|
|||
{
|
||||
this.anonymous = anonymous;
|
||||
}
|
||||
|
||||
public Long getTargetUserId()
|
||||
{
|
||||
return targetUserId;
|
||||
}
|
||||
|
||||
public void setTargetUserId(Long targetUserId)
|
||||
{
|
||||
this.targetUserId = targetUserId;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,18 @@ public class PsyAssessmentServiceImpl implements IPsyAssessmentService
|
|||
@Autowired
|
||||
private IPsyUserProfileService userProfileService;
|
||||
|
||||
@Autowired
|
||||
private com.ddnai.system.mapper.psychology.PsyWarningMapper warningMapper;
|
||||
|
||||
@Autowired
|
||||
private com.ddnai.system.mapper.psychology.PsyFactorScoreMapper factorScoreMapper;
|
||||
|
||||
@Autowired
|
||||
private com.ddnai.system.mapper.psychology.PsyAssessmentAnswerMapper answerMapper;
|
||||
|
||||
@Autowired
|
||||
private com.ddnai.system.mapper.psychology.PsyAssessmentReportMapper reportMapper;
|
||||
|
||||
@Override
|
||||
public PsyAssessment selectAssessmentById(Long assessmentId)
|
||||
{
|
||||
|
|
@ -88,6 +100,27 @@ public class PsyAssessmentServiceImpl implements IPsyAssessmentService
|
|||
@Override
|
||||
public int deleteAssessmentByIds(Long[] assessmentIds)
|
||||
{
|
||||
// 级联删除相关数据
|
||||
for (Long assessmentId : assessmentIds)
|
||||
{
|
||||
// 1. 删除预警记录
|
||||
warningMapper.deleteWarningByAssessmentId(assessmentId);
|
||||
|
||||
// 2. 删除因子得分
|
||||
factorScoreMapper.deleteFactorScoreByAssessmentId(assessmentId);
|
||||
|
||||
// 3. 删除答案记录
|
||||
answerMapper.deleteAnswerByAssessmentId(assessmentId);
|
||||
|
||||
// 4. 删除报告(如果存在)
|
||||
PsyAssessmentReport report = reportMapper.selectReportByAssessmentId(assessmentId);
|
||||
if (report != null)
|
||||
{
|
||||
reportMapper.deleteReportById(report.getReportId());
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 最后删除测评记录
|
||||
return assessmentMapper.deleteAssessmentByIds(assessmentIds);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ import com.ddnai.common.utils.DateUtils;
|
|||
import com.ddnai.system.domain.psychology.PsyQuestionnaire;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.math.RoundingMode;
|
||||
|
|
@ -220,35 +222,22 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
|
|||
|
||||
int result = answerMapper.updateAnswer(answer);
|
||||
|
||||
// 异步更新排名(避免阻塞提交流程)
|
||||
final Long questionnaireIdForRank = answer.getQuestionnaireId();
|
||||
CompletableFuture.runAsync(() -> {
|
||||
// 自动生成报告(和量表一样的简洁方式)
|
||||
if (result > 0) {
|
||||
try {
|
||||
// 延迟50ms确保事务提交完成
|
||||
Thread.sleep(50);
|
||||
updateRank(questionnaireIdForRank);
|
||||
System.out.println("异步排名更新完成,questionnaireId: " + questionnaireIdForRank);
|
||||
System.out.println("========================================");
|
||||
System.out.println("📊 自动生成问卷报告");
|
||||
System.out.println("answerId: " + answerId);
|
||||
System.out.println("========================================");
|
||||
|
||||
generateQuestionnaireReport(answerId);
|
||||
|
||||
System.out.println("✅ 报告生成成功");
|
||||
} catch (Exception e) {
|
||||
System.err.println("异步更新排名失败,questionnaireId: " + questionnaireIdForRank);
|
||||
// 报告生成失败,但不影响问卷提交
|
||||
System.err.println("⚠️ 报告生成失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
// 异步生成问卷报告(避免阻塞提交流程)
|
||||
if (result > 0) {
|
||||
// 使用异步任务生成报告,不阻塞当前事务
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
// 延迟100ms确保事务提交完成
|
||||
Thread.sleep(100);
|
||||
generateQuestionnaireReport(answerId);
|
||||
System.out.println("异步问卷报告生成完成,answerId: " + answerId);
|
||||
} catch (Exception e) {
|
||||
System.err.println("异步生成问卷报告失败,answerId: " + answerId);
|
||||
System.err.println("错误信息: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -269,14 +258,20 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
|
|||
private void generateQuestionnaireReport(Long answerId)
|
||||
{
|
||||
try {
|
||||
System.out.println("开始生成问卷报告,answerId: " + answerId);
|
||||
System.out.println("\n");
|
||||
System.out.println("████████████████████████████████████████");
|
||||
System.out.println("🔧 [修复版本 2025-11-22] 开始生成问卷报告");
|
||||
System.out.println("answerId: " + answerId);
|
||||
System.out.println("时间: " + new java.util.Date());
|
||||
System.out.println("████████████████████████████████████████");
|
||||
|
||||
// 获取答题记录
|
||||
PsyQuestionnaireAnswer answer = answerMapper.selectAnswerById(answerId);
|
||||
if (answer == null) {
|
||||
System.err.println("答题记录不存在,answerId: " + answerId);
|
||||
System.err.println("!!! 错误:答题记录不存在,answerId: " + answerId);
|
||||
return;
|
||||
}
|
||||
System.out.println("✓ 获取答题记录成功,questionnaireId: " + answer.getQuestionnaireId());
|
||||
|
||||
// 获取问卷信息
|
||||
PsyQuestionnaire questionnaire = questionnaireService.selectQuestionnaireById(answer.getQuestionnaireId());
|
||||
|
|
@ -457,16 +452,26 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
|
|||
report.setCreateBy(SecurityUtils.getUsername());
|
||||
report.setCreateTime(DateUtils.getNowDate());
|
||||
int insertResult = reportMapper.insertReport(report);
|
||||
System.out.println("创建新报告结果: " + insertResult + ", reportId: " + report.getReportId() + ", 报告状态: " + isGenerated);
|
||||
System.out.println("✓ 创建新报告结果: " + insertResult + ", reportId: " + report.getReportId() + ", 报告状态: " + isGenerated);
|
||||
if (insertResult <= 0) {
|
||||
System.err.println("警告:报告插入失败,insertResult: " + insertResult);
|
||||
System.err.println("!!! 警告:报告插入失败,insertResult: " + insertResult);
|
||||
} else {
|
||||
System.out.println("✓✓✓ 报告已成功插入数据库 ✓✓✓");
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("报告内容长度: " + reportContent.length());
|
||||
System.out.println("报告摘要: " + summary);
|
||||
|
||||
System.out.println("问卷报告生成成功,answerId: " + answerId + ", reportId: " + report.getReportId());
|
||||
System.out.println("\n");
|
||||
System.out.println("████████████████████████████████████████");
|
||||
System.out.println("✅✅✅ 问卷报告生成成功!✅✅✅");
|
||||
System.out.println("🔧 [修复版本 2025-11-22]");
|
||||
System.out.println("answerId: " + answerId);
|
||||
System.out.println("reportId: " + report.getReportId());
|
||||
System.out.println("时间: " + new java.util.Date());
|
||||
System.out.println("████████████████████████████████████████");
|
||||
System.out.println("\n");
|
||||
} catch (Exception e) {
|
||||
System.err.println("生成问卷报告时发生异常,answerId: " + answerId);
|
||||
e.printStackTrace();
|
||||
|
|
@ -541,7 +546,7 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
|
|||
return calculateInputScore(detail, correctOptions, itemScore);
|
||||
|
||||
case "sort": // 排序
|
||||
return calculateSortScore(detail, correctOptions, itemScore);
|
||||
return calculateSortScore(item, detail, allOptions, itemScore);
|
||||
|
||||
case "calculate": // 计算
|
||||
return calculateCalculateScore(detail, correctOptions, itemScore);
|
||||
|
|
@ -729,15 +734,21 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
|
|||
|
||||
/**
|
||||
* 计算排序题得分
|
||||
* 使用题目的remark字段作为正确答案
|
||||
*/
|
||||
private BigDecimal calculateSortScore(PsyQuestionnaireAnswerDetail detail, List<PsyQuestionnaireOption> correctOptions,
|
||||
BigDecimal itemScore)
|
||||
private BigDecimal calculateSortScore(PsyQuestionnaireItem item, PsyQuestionnaireAnswerDetail detail,
|
||||
List<PsyQuestionnaireOption> allOptions, BigDecimal itemScore)
|
||||
{
|
||||
System.out.println("🔧 [排序题评分] itemId: " + item.getItemId() + ", 题目: " + item.getItemContent());
|
||||
|
||||
if (detail.getOptionIds() == null || detail.getOptionIds().trim().isEmpty())
|
||||
{
|
||||
System.out.println("⚠️ 用户未作答排序题");
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
System.out.println("📝 用户答案: " + detail.getOptionIds());
|
||||
|
||||
// 解析用户排序的选项ID列表
|
||||
List<Long> userOrder = Arrays.stream(detail.getOptionIds().split(","))
|
||||
.map(String::trim)
|
||||
|
|
@ -745,40 +756,82 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
|
|||
.map(Long::valueOf)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 获取正确答案的排序(按sort_order排序)
|
||||
List<Long> correctOrder = correctOptions.stream()
|
||||
.sorted((a, b) -> Integer.compare(
|
||||
a.getSortOrder() != null ? a.getSortOrder() : 0,
|
||||
b.getSortOrder() != null ? b.getSortOrder() : 0))
|
||||
.map(PsyQuestionnaireOption::getOptionId)
|
||||
.collect(Collectors.toList());
|
||||
// 获取正确答案
|
||||
List<String> correctOrder = new ArrayList<>();
|
||||
if (item.getRemark() != null && !item.getRemark().trim().isEmpty())
|
||||
{
|
||||
// 使用题目的remark字段存储正确答案(格式:A,B,C,D 或 1,2,3,4)
|
||||
correctOrder = Arrays.stream(item.getRemark().split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
else
|
||||
{
|
||||
// 兼容旧数据:如果没有remark,使用选项的sortOrder
|
||||
correctOrder = allOptions.stream()
|
||||
.sorted((a, b) -> Integer.compare(
|
||||
a.getSortOrder() != null ? a.getSortOrder() : 0,
|
||||
b.getSortOrder() != null ? b.getSortOrder() : 0))
|
||||
.map(opt -> opt.getOptionCode() != null ? opt.getOptionCode() : String.valueOf(opt.getOptionId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
if (userOrder.size() != correctOrder.size())
|
||||
// 建立选项编码到ID的映射
|
||||
Map<String, Long> codeToIdMap = new HashMap<>();
|
||||
for (PsyQuestionnaireOption option : allOptions)
|
||||
{
|
||||
String code = option.getOptionCode() != null ? option.getOptionCode() : String.valueOf(option.getOptionId());
|
||||
codeToIdMap.put(code.toUpperCase(), option.getOptionId());
|
||||
codeToIdMap.put(String.valueOf(option.getOptionId()), option.getOptionId());
|
||||
}
|
||||
|
||||
// 将正确答案的编码转换为ID列表
|
||||
List<Long> correctOrderIds = new ArrayList<>();
|
||||
for (String code : correctOrder)
|
||||
{
|
||||
Long optionId = codeToIdMap.get(code.toUpperCase());
|
||||
if (optionId != null)
|
||||
{
|
||||
correctOrderIds.add(optionId);
|
||||
}
|
||||
}
|
||||
|
||||
if (correctOrderIds.isEmpty() || userOrder.size() != correctOrderIds.size())
|
||||
{
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
// 检查顺序是否完全正确
|
||||
System.out.println("✓ 正确答案ID: " + correctOrderIds);
|
||||
System.out.println("✓ 用户答案ID: " + userOrder);
|
||||
|
||||
boolean isCorrect = true;
|
||||
for (int i = 0; i < userOrder.size(); i++)
|
||||
{
|
||||
if (!userOrder.get(i).equals(correctOrder.get(i)))
|
||||
if (!userOrder.get(i).equals(correctOrderIds.get(i)))
|
||||
{
|
||||
isCorrect = false;
|
||||
System.out.println("❌ 位置" + i + "不匹配: 用户=" + userOrder.get(i) + ", 正确=" + correctOrderIds.get(i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isCorrect)
|
||||
{
|
||||
System.out.println("✅ 排序完全正确!得分: " + itemScore);
|
||||
return itemScore;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("❌ 排序错误,得分: 0");
|
||||
}
|
||||
|
||||
// 部分正确:计算正确位置的数量
|
||||
int correctCount = 0;
|
||||
for (int i = 0; i < userOrder.size(); i++)
|
||||
{
|
||||
if (userOrder.get(i).equals(correctOrder.get(i)))
|
||||
if (userOrder.get(i).equals(correctOrderIds.get(i)))
|
||||
{
|
||||
correctCount++;
|
||||
}
|
||||
|
|
@ -787,7 +840,7 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
|
|||
// 按比例得分
|
||||
if (correctCount > 0)
|
||||
{
|
||||
BigDecimal ratio = new BigDecimal(correctCount).divide(new BigDecimal(correctOrder.size()), 4, RoundingMode.HALF_UP);
|
||||
BigDecimal ratio = new BigDecimal(correctCount).divide(new BigDecimal(correctOrderIds.size()), 4, RoundingMode.HALF_UP);
|
||||
return itemScore.multiply(ratio);
|
||||
}
|
||||
|
||||
|
|
@ -1095,5 +1148,84 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制生成简化报告 - 备用方案
|
||||
* 当正常报告生成失败时使用此方法
|
||||
*/
|
||||
@Transactional
|
||||
public void forceGenerateSimpleReport(Long answerId) {
|
||||
try {
|
||||
System.out.println("========================================");
|
||||
System.out.println("🚨 使用强制简化报告生成");
|
||||
System.out.println("answerId: " + answerId);
|
||||
System.out.println("========================================");
|
||||
|
||||
// 获取答题记录
|
||||
PsyQuestionnaireAnswer answer = answerMapper.selectAnswerById(answerId);
|
||||
if (answer == null) {
|
||||
System.err.println("❌ 答题记录不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取问卷信息
|
||||
PsyQuestionnaire questionnaire = questionnaireService.selectQuestionnaireById(answer.getQuestionnaireId());
|
||||
if (questionnaire == null) {
|
||||
System.err.println("❌ 问卷不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已有报告
|
||||
PsyQuestionnaireReport existingReport = reportMapper.selectReportByAnswerId(answerId);
|
||||
|
||||
// 生成简化的报告内容
|
||||
StringBuilder content = new StringBuilder();
|
||||
content.append("<div class='report-container'>");
|
||||
content.append("<h1>").append(questionnaire.getQuestionnaireName()).append(" - 答题报告</h1>");
|
||||
content.append("<p>答题时间:").append(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, answer.getStartTime())).append("</p>");
|
||||
content.append("<p>总得分:").append(answer.getTotalScore() != null ? answer.getTotalScore() : "0").append("</p>");
|
||||
content.append("<p>状态:已完成</p>");
|
||||
content.append("</div>");
|
||||
|
||||
String summary = "总分:" + (answer.getTotalScore() != null ? answer.getTotalScore() : "0");
|
||||
|
||||
if (existingReport != null) {
|
||||
// 更新现有报告
|
||||
System.out.println("✓ 更新现有报告,reportId: " + existingReport.getReportId());
|
||||
existingReport.setReportContent(content.toString());
|
||||
existingReport.setSummary(summary);
|
||||
existingReport.setIsGenerated("1");
|
||||
existingReport.setGenerateTime(DateUtils.getNowDate());
|
||||
existingReport.setUpdateBy(SecurityUtils.getUsername());
|
||||
existingReport.setUpdateTime(DateUtils.getNowDate());
|
||||
reportMapper.updateReport(existingReport);
|
||||
} else {
|
||||
// 创建新报告
|
||||
System.out.println("✓ 创建新报告");
|
||||
PsyQuestionnaireReport report = new PsyQuestionnaireReport();
|
||||
report.setAnswerId(answerId);
|
||||
report.setReportContent(content.toString());
|
||||
report.setSummary(summary);
|
||||
report.setIsGenerated("1");
|
||||
report.setGenerateTime(DateUtils.getNowDate());
|
||||
report.setCreateBy(SecurityUtils.getUsername());
|
||||
report.setCreateTime(DateUtils.getNowDate());
|
||||
reportMapper.insertReport(report);
|
||||
}
|
||||
|
||||
System.out.println("========================================");
|
||||
System.out.println("✅ 强制简化报告生成成功!");
|
||||
System.out.println("answerId: " + answerId);
|
||||
System.out.println("========================================");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("========================================");
|
||||
System.err.println("❌ 强制简化报告生成也失败了!");
|
||||
System.err.println("answerId: " + answerId);
|
||||
System.err.println("错误: " + e.getMessage());
|
||||
System.err.println("========================================");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
package com.ddnai.system.service.impl.psychology;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.ddnai.system.domain.psychology.PsyQuestionnaireItem;
|
||||
import com.ddnai.system.domain.psychology.PsyQuestionnaire;
|
||||
import com.ddnai.system.mapper.psychology.PsyQuestionnaireItemMapper;
|
||||
import com.ddnai.system.mapper.psychology.PsyQuestionnaireMapper;
|
||||
import com.ddnai.system.service.psychology.IPsyQuestionnaireItemService;
|
||||
|
||||
/**
|
||||
|
|
@ -17,6 +21,9 @@ public class PsyQuestionnaireItemServiceImpl implements IPsyQuestionnaireItemSer
|
|||
{
|
||||
@Autowired
|
||||
private PsyQuestionnaireItemMapper itemMapper;
|
||||
|
||||
@Autowired
|
||||
private PsyQuestionnaireMapper questionnaireMapper;
|
||||
|
||||
/**
|
||||
* 查询题目信息
|
||||
|
|
@ -61,9 +68,16 @@ public class PsyQuestionnaireItemServiceImpl implements IPsyQuestionnaireItemSer
|
|||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public int insertItem(PsyQuestionnaireItem item)
|
||||
{
|
||||
return itemMapper.insertItem(item);
|
||||
int result = itemMapper.insertItem(item);
|
||||
if (result > 0 && item.getQuestionnaireId() != null)
|
||||
{
|
||||
// 自动更新问卷总分
|
||||
updateQuestionnaireTotalScore(item.getQuestionnaireId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -73,9 +87,16 @@ public class PsyQuestionnaireItemServiceImpl implements IPsyQuestionnaireItemSer
|
|||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public int updateItem(PsyQuestionnaireItem item)
|
||||
{
|
||||
return itemMapper.updateItem(item);
|
||||
int result = itemMapper.updateItem(item);
|
||||
if (result > 0 && item.getQuestionnaireId() != null)
|
||||
{
|
||||
// 自动更新问卷总分
|
||||
updateQuestionnaireTotalScore(item.getQuestionnaireId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -85,9 +106,25 @@ public class PsyQuestionnaireItemServiceImpl implements IPsyQuestionnaireItemSer
|
|||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public int deleteItemByIds(Long[] itemIds)
|
||||
{
|
||||
return itemMapper.deleteItemByIds(itemIds);
|
||||
if (itemIds == null || itemIds.length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 获取第一个题目的问卷ID(假设批量删除的题目属于同一个问卷)
|
||||
PsyQuestionnaireItem firstItem = itemMapper.selectItemById(itemIds[0]);
|
||||
Long questionnaireId = firstItem != null ? firstItem.getQuestionnaireId() : null;
|
||||
|
||||
int result = itemMapper.deleteItemByIds(itemIds);
|
||||
if (result > 0 && questionnaireId != null)
|
||||
{
|
||||
// 自动更新问卷总分
|
||||
updateQuestionnaireTotalScore(questionnaireId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -97,9 +134,72 @@ public class PsyQuestionnaireItemServiceImpl implements IPsyQuestionnaireItemSer
|
|||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public int deleteItemById(Long itemId)
|
||||
{
|
||||
return itemMapper.deleteItemById(itemId);
|
||||
// 先获取题目信息,以便获取问卷ID
|
||||
PsyQuestionnaireItem item = itemMapper.selectItemById(itemId);
|
||||
Long questionnaireId = item != null ? item.getQuestionnaireId() : null;
|
||||
|
||||
int result = itemMapper.deleteItemById(itemId);
|
||||
if (result > 0 && questionnaireId != null)
|
||||
{
|
||||
// 自动更新问卷总分
|
||||
updateQuestionnaireTotalScore(questionnaireId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新问卷总分(根据题目分数自动计算)
|
||||
*
|
||||
* @param questionnaireId 问卷ID
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int updateQuestionnaireTotalScore(Long questionnaireId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 查询该问卷的所有题目
|
||||
List<PsyQuestionnaireItem> items = itemMapper.selectItemListByQuestionnaireId(questionnaireId);
|
||||
|
||||
// 计算总分
|
||||
BigDecimal totalScore = BigDecimal.ZERO;
|
||||
int itemCount = 0;
|
||||
|
||||
if (items != null && !items.isEmpty())
|
||||
{
|
||||
itemCount = items.size();
|
||||
for (PsyQuestionnaireItem item : items)
|
||||
{
|
||||
if (item.getScore() != null)
|
||||
{
|
||||
totalScore = totalScore.add(item.getScore());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新问卷的总分和题目数量
|
||||
PsyQuestionnaire questionnaire = questionnaireMapper.selectQuestionnaireById(questionnaireId);
|
||||
if (questionnaire != null)
|
||||
{
|
||||
questionnaire.setTotalScore(totalScore);
|
||||
questionnaire.setItemCount(itemCount);
|
||||
questionnaireMapper.updateQuestionnaire(questionnaire);
|
||||
|
||||
System.out.println("自动更新问卷总分成功 - questionnaireId: " + questionnaireId +
|
||||
", 题目数量: " + itemCount + ", 总分: " + totalScore);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.err.println("自动更新问卷总分失败 - questionnaireId: " + questionnaireId);
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import com.ddnai.system.service.psychology.IPsyAssessmentService;
|
|||
import com.ddnai.system.service.psychology.IPsyFactorScoreService;
|
||||
import com.ddnai.system.service.psychology.IPsyWarningRuleService;
|
||||
import com.ddnai.system.service.psychology.IPsyWarningService;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* 危机预警 服务层实现
|
||||
|
|
@ -100,6 +102,7 @@ public class PsyWarningServiceImpl implements IPsyWarningService
|
|||
/**
|
||||
* 检查并创建预警(在报告生成后调用)
|
||||
* 根据预警规则匹配因子得分或总分,创建预警记录
|
||||
* 同时检查该用户是否有符合自动解除条件的预警
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
|
|
@ -122,6 +125,9 @@ public class PsyWarningServiceImpl implements IPsyWarningService
|
|||
return 0; // 没有预警规则,不创建预警
|
||||
}
|
||||
|
||||
// 4. 先检查并自动解除该用户的旧预警(基于新的测评分数)
|
||||
checkAndAutoRelieveWarnings(assessment, factorScores, rules);
|
||||
|
||||
int warningCount = 0;
|
||||
|
||||
// 4. 检查因子得分预警
|
||||
|
|
@ -257,4 +263,167 @@ public class PsyWarningServiceImpl implements IPsyWarningService
|
|||
|
||||
return warning;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并自动解除该用户的未处理预警
|
||||
* 当用户做新测评时,检查旧预警是否满足解除条件
|
||||
*/
|
||||
private void checkAndAutoRelieveWarnings(PsyAssessment assessment, List<PsyFactorScore> factorScores, List<PsyWarningRule> rules)
|
||||
{
|
||||
// 1. 查询该用户在该量表下所有未处理的预警(状态为0)
|
||||
PsyWarning queryWarning = new PsyWarning();
|
||||
queryWarning.setUserId(assessment.getUserId());
|
||||
queryWarning.setScaleId(assessment.getScaleId());
|
||||
queryWarning.setStatus("0"); // 只查询未处理的预警
|
||||
|
||||
List<PsyWarning> activeWarnings = warningMapper.selectWarningList(queryWarning);
|
||||
if (activeWarnings == null || activeWarnings.isEmpty())
|
||||
{
|
||||
return; // 没有未处理的预警,直接返回
|
||||
}
|
||||
|
||||
System.out.println("检查自动解除预警:用户ID=" + assessment.getUserId() + ", 量表ID=" + assessment.getScaleId() + ", 未处理预警数=" + activeWarnings.size());
|
||||
|
||||
// 2. 遍历每个未处理的预警,检查是否满足解除条件
|
||||
for (PsyWarning warning : activeWarnings)
|
||||
{
|
||||
// 找到触发该预警的规则
|
||||
PsyWarningRule matchedRule = null;
|
||||
for (PsyWarningRule rule : rules)
|
||||
{
|
||||
// 匹配因子ID(都为null或都相等)
|
||||
boolean factorMatch = (warning.getFactorId() == null && rule.getFactorId() == null) ||
|
||||
(warning.getFactorId() != null && warning.getFactorId().equals(rule.getFactorId()));
|
||||
|
||||
// 匹配预警等级
|
||||
boolean levelMatch = warning.getWarningLevel() != null && warning.getWarningLevel().equals(rule.getWarningLevel());
|
||||
|
||||
if (factorMatch && levelMatch)
|
||||
{
|
||||
matchedRule = rule;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedRule == null)
|
||||
{
|
||||
System.out.println("未找到匹配的预警规则,跳过:预警ID=" + warning.getWarningId());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查规则是否启用了自动解除
|
||||
if (!"1".equals(matchedRule.getAutoRelief()) || matchedRule.getReliefCondition() == null || matchedRule.getReliefCondition().trim().isEmpty())
|
||||
{
|
||||
System.out.println("规则未启用自动解除或无解除条件,跳过:规则ID=" + matchedRule.getRuleId());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取当前的分数(因子分数或总分)
|
||||
BigDecimal currentScore = null;
|
||||
if (warning.getFactorId() != null)
|
||||
{
|
||||
// 因子预警,获取因子分数
|
||||
for (PsyFactorScore factorScore : factorScores)
|
||||
{
|
||||
if (factorScore.getFactorId().equals(warning.getFactorId()))
|
||||
{
|
||||
currentScore = factorScore.getFactorScore();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 总分预警
|
||||
currentScore = assessment.getTotalScore();
|
||||
}
|
||||
|
||||
if (currentScore == null)
|
||||
{
|
||||
System.out.println("无法获取当前分数,跳过:预警ID=" + warning.getWarningId());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查是否满足解除条件
|
||||
if (checkReliefCondition(currentScore, matchedRule.getReliefCondition()))
|
||||
{
|
||||
// 满足解除条件,自动解除预警
|
||||
warning.setStatus("2"); // 已解除
|
||||
warning.setReliefTime(DateUtils.getNowDate());
|
||||
warning.setUpdateBy("system");
|
||||
warning.setUpdateTime(DateUtils.getNowDate());
|
||||
warning.setRemark("自动解除:新测评分数" + currentScore + "满足解除条件");
|
||||
|
||||
warningMapper.updateWarning(warning);
|
||||
System.out.println("自动解除预警成功:预警ID=" + warning.getWarningId() + ", 当前分数=" + currentScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查分数是否满足解除条件
|
||||
* 解除条件JSON格式:
|
||||
* {"operator": "<", "score": 60} - 分数低于60
|
||||
* {"operator": ">", "score": 40} - 分数高于40
|
||||
* {"operator": "between", "scoreMin": 30, "scoreMax": 50} - 分数在30-50之间
|
||||
*/
|
||||
private boolean checkReliefCondition(BigDecimal currentScore, String reliefConditionJson)
|
||||
{
|
||||
try
|
||||
{
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode condition = mapper.readTree(reliefConditionJson);
|
||||
|
||||
String operator = condition.has("operator") ? condition.get("operator").asText() : null;
|
||||
|
||||
if (operator == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (operator)
|
||||
{
|
||||
case "<":
|
||||
// 分数低于指定值
|
||||
if (condition.has("score"))
|
||||
{
|
||||
BigDecimal targetScore = new BigDecimal(condition.get("score").asText());
|
||||
boolean result = currentScore.compareTo(targetScore) < 0;
|
||||
System.out.println("检查解除条件 (<): 当前分数=" + currentScore + ", 目标分数=" + targetScore + ", 结果=" + result);
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case ">":
|
||||
// 分数高于指定值
|
||||
if (condition.has("score"))
|
||||
{
|
||||
BigDecimal targetScore = new BigDecimal(condition.get("score").asText());
|
||||
boolean result = currentScore.compareTo(targetScore) > 0;
|
||||
System.out.println("检查解除条件 (>): 当前分数=" + currentScore + ", 目标分数=" + targetScore + ", 结果=" + result);
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case "between":
|
||||
// 分数在指定范围内
|
||||
if (condition.has("scoreMin") && condition.has("scoreMax"))
|
||||
{
|
||||
BigDecimal minScore = new BigDecimal(condition.get("scoreMin").asText());
|
||||
BigDecimal maxScore = new BigDecimal(condition.get("scoreMax").asText());
|
||||
boolean result = currentScore.compareTo(minScore) >= 0 && currentScore.compareTo(maxScore) <= 0;
|
||||
System.out.println("检查解除条件 (between): 当前分数=" + currentScore + ", 范围=" + minScore + "~" + maxScore + ", 结果=" + result);
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.err.println("解析解除条件失败:" + reliefConditionJson);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,5 +65,13 @@ public interface IPsyQuestionnaireItemService
|
|||
* @return 结果
|
||||
*/
|
||||
public int deleteItemById(Long itemId);
|
||||
|
||||
/**
|
||||
* 更新问卷总分(根据题目分数自动计算)
|
||||
*
|
||||
* @param questionnaireId 问卷ID
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateQuestionnaireTotalScore(Long questionnaireId);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<result property="status" column="status" />
|
||||
<result property="ipAddress" column="ip_address" />
|
||||
<result property="userAgent" column="user_agent" />
|
||||
<result property="hasReport" column="has_report" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
|
|
@ -36,9 +37,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
select a.assessment_id, a.scale_id, s.scale_name, a.user_id, a.assessee_name, a.assessee_gender, a.assessee_age,
|
||||
a.assessee_id_card, a.assessee_phone, a.assessee_email, a.start_time, a.pause_time,
|
||||
a.resume_time, a.pause_count, a.submit_time, a.complete_time, a.total_score, a.status,
|
||||
a.ip_address, a.user_agent, a.create_by, a.create_time, a.update_by, a.update_time, a.remark
|
||||
a.ip_address, a.user_agent,
|
||||
(case when r.report_id is not null then 1 else 0 end) as has_report,
|
||||
a.create_by, a.create_time, a.update_by, a.update_time, a.remark
|
||||
from psy_assessment a
|
||||
left join psy_scale s on a.scale_id = s.scale_id
|
||||
left join psy_assessment_report r on a.assessment_id = r.assessment_id
|
||||
</sql>
|
||||
|
||||
<select id="selectAssessmentById" parameterType="Long" resultMap="PsyAssessmentResult">
|
||||
|
|
|
|||
|
|
@ -71,7 +71,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
AND w.warning_type = #{warningType}
|
||||
</if>
|
||||
<if test="warningLevel != null and warningLevel != ''">
|
||||
AND w.warning_level = #{warningLevel}
|
||||
AND (w.warning_level = #{warningLevel}
|
||||
OR w.warning_level LIKE CONCAT('%', #{warningLevel}, '%')
|
||||
OR (#{warningLevel} = '低' AND (w.warning_level = 'low' OR w.warning_level LIKE '%低%'))
|
||||
OR (#{warningLevel} = '中' AND (w.warning_level = 'medium' OR w.warning_level LIKE '%中%'))
|
||||
OR (#{warningLevel} = '高' AND (w.warning_level = 'high' OR w.warning_level LIKE '%高%'))
|
||||
OR (#{warningLevel} = '严重' AND (w.warning_level = 'critical' OR w.warning_level LIKE '%严重%')))
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND w.status = #{status}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
</select>
|
||||
|
||||
<select id="selectWarningRuleList" parameterType="com.ddnai.system.domain.psychology.PsyWarningRule" resultMap="PsyWarningRuleResult">
|
||||
<include refid="selectWarningRuleVo"/>
|
||||
select r.rule_id, r.scale_id, r.factor_id, r.rule_name, r.warning_level,
|
||||
r.score_min, r.score_max, r.percentile_min, r.percentile_max,
|
||||
r.auto_relief, r.relief_condition, r.status,
|
||||
r.create_by, r.create_time, r.update_by, r.update_time, r.remark,
|
||||
s.scale_name, f.factor_name
|
||||
from psy_warning_rule r
|
||||
left join psy_scale s on r.scale_id = s.scale_id
|
||||
left join psy_factor f on r.factor_id = f.factor_id
|
||||
<where>
|
||||
<if test="scaleId != null">
|
||||
AND r.scale_id = #{scaleId}
|
||||
|
|
@ -52,7 +59,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
AND r.factor_id = #{factorId}
|
||||
</if>
|
||||
<if test="warningLevel != null and warningLevel != ''">
|
||||
AND r.warning_level = #{warningLevel}
|
||||
AND (r.warning_level = #{warningLevel}
|
||||
OR r.warning_level LIKE CONCAT('%', #{warningLevel}, '%')
|
||||
OR (#{warningLevel} = '低' AND (r.warning_level = 'low' OR r.warning_level LIKE '%低%'))
|
||||
OR (#{warningLevel} = '中' AND (r.warning_level = 'medium' OR r.warning_level LIKE '%中%'))
|
||||
OR (#{warningLevel} = '高' AND (r.warning_level = 'high' OR r.warning_level LIKE '%高%'))
|
||||
OR (#{warningLevel} = '严重' AND (r.warning_level = 'critical' OR r.warning_level LIKE '%严重%')))
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND r.status = #{status}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,10 @@ export function pausedAssessmentList() {
|
|||
export function getAssessment(assessmentId) {
|
||||
return request({
|
||||
url: '/psychology/assessment/' + assessmentId,
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
params: {
|
||||
_t: new Date().getTime() // 添加时间戳防止缓存
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +50,10 @@ export function startAssessment(data) {
|
|||
export function getAssessmentItems(assessmentId) {
|
||||
return request({
|
||||
url: '/psychology/assessment/items/' + assessmentId,
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
params: {
|
||||
_t: new Date().getTime() // 添加时间戳防止缓存
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +102,10 @@ export function submitAssessment(assessmentId) {
|
|||
export function getAssessmentAnswers(assessmentId) {
|
||||
return request({
|
||||
url: '/psychology/assessment/answers/' + assessmentId,
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
params: {
|
||||
_t: new Date().getTime() // 添加时间戳防止缓存
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ export function listQuestionnaireAnswer(query) {
|
|||
export function getQuestionnaireAnswer(answerId) {
|
||||
return request({
|
||||
url: '/psychology/questionnaire/answer/' + answerId,
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
params: {
|
||||
_t: new Date().getTime() // 添加时间戳防止缓存
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +33,10 @@ export function startQuestionnaireAnswer(data) {
|
|||
export function getQuestionnaireItems(questionnaireId) {
|
||||
return request({
|
||||
url: '/psychology/questionnaire/answer/items/' + questionnaireId,
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
params: {
|
||||
_t: new Date().getTime() // 添加时间戳防止缓存
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +44,10 @@ export function getQuestionnaireItems(questionnaireId) {
|
|||
export function getAnswerDetails(answerId) {
|
||||
return request({
|
||||
url: '/psychology/questionnaire/answer/details/' + answerId,
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
params: {
|
||||
_t: new Date().getTime() // 添加时间戳防止缓存
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -94,3 +103,11 @@ export function batchSubmitScoring(data) {
|
|||
})
|
||||
}
|
||||
|
||||
// 重新生成问卷报告
|
||||
export function regenerateQuestionnaireReport(answerId) {
|
||||
return request({
|
||||
url: '/psychology/questionnaire/answer/generateReport/' + answerId,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,3 +42,11 @@ export function delQuestionnaireItem(itemIds) {
|
|||
})
|
||||
}
|
||||
|
||||
// 更新问卷总分(根据题目分数自动计算)
|
||||
export function updateQuestionnaireTotalScore(questionnaireId) {
|
||||
return request({
|
||||
url: '/psychology/questionnaire/item/updateTotalScore/' + questionnaireId,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,14 @@ export default {
|
|||
return this.$store.state.tagsView.cachedViews
|
||||
},
|
||||
key() {
|
||||
// 对于答题页面,包含 answerId 参数以确保每次都重新创建组件
|
||||
if (this.$route.path.includes('/taking') && this.$route.query.answerId) {
|
||||
return this.$route.path + '?answerId=' + this.$route.query.answerId
|
||||
}
|
||||
// 对于答题页面,包含 assessmentId 参数以确保每次都重新创建组件
|
||||
if (this.$route.path.includes('/taking') && this.$route.query.assessmentId) {
|
||||
return this.$route.path + '?assessmentId=' + this.$route.query.assessmentId
|
||||
}
|
||||
return this.$route.path
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
<span>精选内容</span>
|
||||
<el-button type="text" @click="loadPortalContents">刷新</el-button>
|
||||
</div>
|
||||
<el-skeleton :loading="portalLoading" animated rows="5">
|
||||
<el-skeleton :loading="portalLoading" animated :rows="5">
|
||||
<div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="24" :md="12" v-for="item in portalContents" :key="item.contentId">
|
||||
|
|
@ -68,7 +68,7 @@
|
|||
destroy-on-close
|
||||
>
|
||||
<div v-if="detailLoading" class="detail-loading">
|
||||
<el-skeleton animated rows="6" />
|
||||
<el-skeleton animated :rows="6" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="detail-meta">
|
||||
|
|
|
|||
|
|
@ -1,13 +1,23 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 🔧 修复标记:2025-11-22 已修复缓存和外键问题 -->
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
|
||||
<el-form-item label="量表名称" prop="scaleId">
|
||||
<el-input
|
||||
<el-form-item label="量表" prop="scaleId">
|
||||
<el-select
|
||||
v-model="queryParams.scaleId"
|
||||
placeholder="请输入量表ID"
|
||||
placeholder="请选择量表"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px;"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
>
|
||||
<el-option
|
||||
v-for="scale in scaleList"
|
||||
:key="scale.scaleId"
|
||||
:label="scale.scaleName"
|
||||
:value="scale.scaleId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="状态" clearable>
|
||||
|
|
@ -73,7 +83,14 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总分" align="center" prop="totalScore" width="100" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="250">
|
||||
<el-table-column label="报告状态" align="center" prop="hasReport" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.status === '1' && scope.row.hasReport" type="success" size="small">已生成</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '1' && !scope.row.hasReport" type="warning" size="small">未生成</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="300">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
|
|
@ -88,8 +105,16 @@
|
|||
type="text"
|
||||
icon="el-icon-view"
|
||||
@click="handleView(scope.row)"
|
||||
v-if="scope.row.status === '1'"
|
||||
v-if="scope.row.status === '1' && scope.row.hasReport"
|
||||
>查看报告</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-document-add"
|
||||
@click="handleGenerateReport(scope.row)"
|
||||
v-if="scope.row.status === '1' && !scope.row.hasReport"
|
||||
v-hasPermi="['psychology:report:add']"
|
||||
>生成报告</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
|
|
@ -113,6 +138,7 @@
|
|||
|
||||
<script>
|
||||
import { listAssessment, getAssessment, delAssessment, pausedAssessmentList } from "@/api/psychology/assessment";
|
||||
import { listScale } from "@/api/psychology/scale";
|
||||
|
||||
export default {
|
||||
name: "Assessment",
|
||||
|
|
@ -133,6 +159,8 @@ export default {
|
|||
total: 0,
|
||||
// 测评表格数据
|
||||
assessmentList: [],
|
||||
// 量表列表
|
||||
scaleList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
|
|
@ -153,8 +181,19 @@ export default {
|
|||
},
|
||||
created() {
|
||||
this.getList();
|
||||
this.loadScales();
|
||||
},
|
||||
methods: {
|
||||
/** 加载量表列表 */
|
||||
loadScales() {
|
||||
listScale({ status: '0', pageNum: 1, pageSize: 1000, includeQuestionnaire: false }).then(response => {
|
||||
// 只显示量表,不包含问卷
|
||||
this.scaleList = (response.rows || [])
|
||||
.filter(scale => !scale.sourceType || scale.sourceType === 'scale');
|
||||
}).catch(error => {
|
||||
console.error('加载量表列表失败:', error);
|
||||
});
|
||||
},
|
||||
/** 查询测评列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
|
|
@ -191,7 +230,23 @@ export default {
|
|||
/** 查看报告按钮操作 */
|
||||
handleView(row) {
|
||||
// 跳转到报告详情页面,使用测评ID查询报告
|
||||
this.$router.push({ path: '/psychology/report/detail', query: { assessmentId: row.assessmentId } });
|
||||
this.$router.push({ path: '/psychology/report/detail', query: { assessmentId: row.assessmentId, sourceType: 'assessment' } });
|
||||
},
|
||||
/** 生成报告按钮操作 */
|
||||
handleGenerateReport(row) {
|
||||
this.$modal.confirm('是否为该测评生成报告?').then(() => {
|
||||
this.$modal.loading("正在生成报告...");
|
||||
return this.$http.post(`/psychology/report/generate/${row.assessmentId}`);
|
||||
}).then(() => {
|
||||
this.$modal.closeLoading();
|
||||
this.$modal.msgSuccess("报告生成成功");
|
||||
this.getList();
|
||||
}).catch((error) => {
|
||||
this.$modal.closeLoading();
|
||||
console.error('生成报告失败:', error);
|
||||
const errorMsg = error.msg || error.message || "生成报告失败";
|
||||
this.$modal.msgError(errorMsg);
|
||||
});
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
|
|
|
|||
|
|
@ -152,7 +152,11 @@ export default {
|
|||
const selectedScale = this.scaleList.find(s => s.scaleId === this.form.scaleId);
|
||||
if (selectedScale && selectedScale.sourceType === 'questionnaire') {
|
||||
// 如果是问卷,直接跳转到问卷开始页面
|
||||
const questionnaireId = selectedScale.originalId || Math.abs(selectedScale.scaleId);
|
||||
if (!selectedScale.originalId) {
|
||||
this.$modal.msgError('问卷数据异常,缺少原始ID');
|
||||
return;
|
||||
}
|
||||
const questionnaireId = selectedScale.originalId;
|
||||
const queryParams = { questionnaireId: questionnaireId };
|
||||
|
||||
// 检查URL中是否有userId参数(管理员代测)
|
||||
|
|
@ -184,7 +188,11 @@ export default {
|
|||
const selectedScale = this.scaleList.find(s => s.scaleId === this.form.scaleId);
|
||||
if (selectedScale && selectedScale.sourceType === 'questionnaire') {
|
||||
// 如果是问卷,直接跳转到问卷开始页面
|
||||
const questionnaireId = selectedScale.originalId || Math.abs(selectedScale.scaleId);
|
||||
if (!selectedScale.originalId) {
|
||||
this.$modal.msgError('问卷数据异常,缺少原始ID');
|
||||
return;
|
||||
}
|
||||
const questionnaireId = selectedScale.originalId;
|
||||
const queryParams = { questionnaireId: questionnaireId };
|
||||
|
||||
// 检查URL中是否有userId参数(管理员代测)
|
||||
|
|
@ -225,7 +233,11 @@ export default {
|
|||
const selectedScale = this.scaleList.find(s => s.scaleId === this.form.scaleId);
|
||||
if (selectedScale && selectedScale.sourceType === 'questionnaire') {
|
||||
// 如果是问卷,直接跳转到问卷开始页面
|
||||
const questionnaireId = selectedScale.originalId || Math.abs(selectedScale.scaleId);
|
||||
if (!selectedScale.originalId) {
|
||||
this.$modal.msgError('问卷数据异常,缺少原始ID');
|
||||
return;
|
||||
}
|
||||
const questionnaireId = selectedScale.originalId;
|
||||
const queryParams = { questionnaireId: questionnaireId };
|
||||
|
||||
// 检查URL中是否有userId参数(管理员代测)
|
||||
|
|
@ -306,29 +318,52 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
// 如果是问卷,直接跳转到问卷答题页面
|
||||
// 如果是问卷,直接调用问卷API并跳转到答题页面(避免重复跳转)
|
||||
if (selectedScale.sourceType === 'questionnaire') {
|
||||
const questionnaireId = selectedScale.originalId || Math.abs(selectedScale.scaleId);
|
||||
if (!selectedScale.originalId) {
|
||||
this.$modal.msgError('问卷数据异常,缺少原始ID');
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
const questionnaireId = selectedScale.originalId;
|
||||
|
||||
// 构建查询参数
|
||||
const queryParams = { questionnaireId: questionnaireId };
|
||||
// 构建问卷答题数据
|
||||
const data = {
|
||||
questionnaireId: questionnaireId,
|
||||
respondentName: null
|
||||
};
|
||||
|
||||
// 优先使用targetUserId(从URL获取),其次使用选择的用户档案
|
||||
if (this.targetUserId) {
|
||||
queryParams.userId = this.targetUserId;
|
||||
data.userId = this.targetUserId;
|
||||
console.log('使用URL参数的userId:', this.targetUserId);
|
||||
} else if (this.form.profileId) {
|
||||
const selectedProfile = this.profileList.find(p => p.profileId === this.form.profileId);
|
||||
if (selectedProfile && selectedProfile.userId) {
|
||||
queryParams.userId = selectedProfile.userId;
|
||||
data.userId = selectedProfile.userId;
|
||||
console.log('管理员代测,传递userId:', selectedProfile.userId);
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到问卷开始页面
|
||||
this.$router.push({
|
||||
path: '/psychology/questionnaire/start',
|
||||
query: queryParams
|
||||
// 直接调用问卷开始答题API
|
||||
this.loading = true;
|
||||
const { startQuestionnaireAnswer } = require("@/api/psychology/questionnaireAnswer");
|
||||
startQuestionnaireAnswer(data).then(response => {
|
||||
if (response.code === 200) {
|
||||
// 直接跳转到答题页面(避免闪现)
|
||||
const answerId = response.data;
|
||||
console.log('🔧 [量表页面] 创建问卷答题记录成功,answerId:', answerId);
|
||||
// 使用 replace 跳转到答题页面
|
||||
this.$router.replace({
|
||||
path: '/psychology/questionnaire/taking',
|
||||
query: { answerId: answerId }
|
||||
});
|
||||
}
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
console.error('Failed to start questionnaire answer:', error);
|
||||
this.$modal.msgError("开始答题失败,请重试");
|
||||
this.loading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -354,7 +389,8 @@ export default {
|
|||
assesseeGender: '0', // 默认性别
|
||||
assesseeAge: 0, // 默认年龄
|
||||
assesseePhone: selectedProfile.phone,
|
||||
anonymous: false // 添加anonymous字段,设置为非匿名
|
||||
anonymous: false, // 添加anonymous字段,设置为非匿名
|
||||
targetUserId: selectedProfile.userId // 传递被测评用户的ID
|
||||
};
|
||||
|
||||
startAssessment(assessmentData).then(response => {
|
||||
|
|
|
|||
|
|
@ -151,6 +151,8 @@ export default {
|
|||
this.$router.push(isStudent ? '/student/tests' : '/psychology/assessment');
|
||||
return;
|
||||
}
|
||||
// 清理旧数据,防止缓存
|
||||
this.clearAllData();
|
||||
this.loadAssessment();
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -344,15 +346,29 @@ export default {
|
|||
}
|
||||
|
||||
this.$modal.confirm('确定要提交测评吗?提交后将不能修改。').then(() => {
|
||||
this.loading = true;
|
||||
submitAssessment(this.assessmentId).then(response => {
|
||||
this.$modal.msgSuccess(response.msg || "测评已提交,报告已生成");
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess("测评已提交,报告正在生成中...");
|
||||
|
||||
// 清理所有数据,防止缓存
|
||||
this.clearAllData();
|
||||
|
||||
// 根据用户角色跳转到相应页面
|
||||
const roles = this.$store.getters.roles || [];
|
||||
const isStudent = roles.some(role => role === 'student' || role.includes('学员'));
|
||||
this.$router.push(isStudent ? '/student/tests' : '/psychology/assessment');
|
||||
|
||||
// 延迟跳转,确保用户看到成功提示
|
||||
setTimeout(() => {
|
||||
this.$router.push(isStudent ? '/student/tests' : '/psychology/assessment');
|
||||
}, 500);
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
console.error('提交测评失败:', error);
|
||||
this.$modal.msgError(error.msg || "提交失败,请重试");
|
||||
});
|
||||
}).catch(() => {
|
||||
// 用户取消提交
|
||||
});
|
||||
},
|
||||
/** 快速填充所有题目并提交(管理员功能) */
|
||||
|
|
@ -503,12 +519,39 @@ export default {
|
|||
console.error('保存答案失败:', error);
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
/** 清理所有数据 */
|
||||
clearAllData() {
|
||||
this.scaleName = '';
|
||||
this.itemList = [];
|
||||
this.optionMap = {};
|
||||
this.currentIndex = 0;
|
||||
this.selectedOption = null;
|
||||
this.selectedOptions = [];
|
||||
this.answersMap = {};
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentIndex() {
|
||||
this.loadCurrentAnswer();
|
||||
},
|
||||
'$route.query.assessmentId': {
|
||||
handler(newAssessmentId, oldAssessmentId) {
|
||||
// 当assessmentId改变时,重新加载数据
|
||||
if (newAssessmentId && newAssessmentId !== oldAssessmentId) {
|
||||
console.log('assessmentId changed from', oldAssessmentId, 'to', newAssessmentId);
|
||||
this.clearAllData();
|
||||
this.assessmentId = newAssessmentId;
|
||||
this.loadAssessment();
|
||||
}
|
||||
},
|
||||
immediate: false
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 组件销毁前清理数据,防止缓存
|
||||
this.clearAllData();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -191,12 +191,14 @@
|
|||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="题目数量" prop="itemCount">
|
||||
<el-input-number v-model="form.itemCount" :min="0" controls-position="right" />
|
||||
<el-input-number v-model="form.itemCount" :min="0" controls-position="right" disabled />
|
||||
<span style="color: #909399; font-size: 12px; margin-left: 10px;">(根据题目自动计算)</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="总分" prop="totalScore">
|
||||
<el-input-number v-model="form.totalScore" :min="0" :precision="2" controls-position="right" />
|
||||
<el-input-number v-model="form.totalScore" :min="0" :precision="2" controls-position="right" disabled />
|
||||
<span style="color: #909399; font-size: 12px; margin-left: 10px;">(根据题目分数自动计算)</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
|
@ -246,12 +248,12 @@
|
|||
<span style="font-size: 16px; font-weight: bold;">问卷名称:{{ currentQuestionnaireName }}</span>
|
||||
</div>
|
||||
<el-table v-loading="rankLoading" :data="rankList" border style="width: 100%">
|
||||
<el-table-column type="index" label="排名" width="100" align="center">
|
||||
<el-table-column prop="rank" label="排名" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.$index + 1 === 1" type="danger" size="medium">第1名</el-tag>
|
||||
<el-tag v-else-if="scope.$index + 1 === 2" type="warning" size="medium">第2名</el-tag>
|
||||
<el-tag v-else-if="scope.$index + 1 === 3" type="success" size="medium">第3名</el-tag>
|
||||
<span v-else style="font-size: 14px;">第{{ scope.$index + 1 }}名</span>
|
||||
<el-tag v-if="scope.row.rank === 1" type="danger" size="medium">第1名</el-tag>
|
||||
<el-tag v-else-if="scope.row.rank === 2" type="warning" size="medium">第2名</el-tag>
|
||||
<el-tag v-else-if="scope.row.rank === 3" type="success" size="medium">第3名</el-tag>
|
||||
<span v-else style="font-size: 14px;">第{{ scope.row.rank }}名</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="respondentName" label="答题人" width="150" />
|
||||
|
|
@ -260,14 +262,6 @@
|
|||
<span style="font-weight: bold; color: #409EFF;">{{ scope.row.totalScore || 0 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="rank" label="排名" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.rank === 1" type="danger">第1名</el-tag>
|
||||
<el-tag v-else-if="scope.row.rank === 2" type="warning">第2名</el-tag>
|
||||
<el-tag v-else-if="scope.row.rank === 3" type="success">第3名</el-tag>
|
||||
<span v-else>第{{ scope.row.rank }}名</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="submitTime" label="提交时间" width="180" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,14 @@
|
|||
@click="handleDelete"
|
||||
v-hasPermi="['psychology:questionnaire:remove']"
|
||||
>批量删除</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-refresh"
|
||||
size="mini"
|
||||
@click="handleUpdateTotalScore"
|
||||
v-hasPermi="['psychology:questionnaire:edit']"
|
||||
>更新总分</el-button>
|
||||
</el-col>
|
||||
<el-col :span="2.5">
|
||||
<el-button
|
||||
|
|
@ -120,8 +128,16 @@
|
|||
<el-form-item label="排序">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
||||
<el-form-item :label="form.itemType === 'sort' ? '正确顺序' : '备注'">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:placeholder="form.itemType === 'sort' ? '请输入正确的选项顺序,如:A,B,C,D 或 1,2,3,4' : '请输入备注'"
|
||||
/>
|
||||
<div v-if="form.itemType === 'sort'" style="color: #909399; font-size: 12px; margin-top: 5px;">
|
||||
提示:输入选项的正确排列顺序,用逗号分隔。例如:A,B,C,D 表示正确顺序是A在第一位,B在第二位,以此类推。
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
|
|
@ -208,7 +224,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { listQuestionnaireItem, getQuestionnaireItem, delQuestionnaireItem, addQuestionnaireItem, updateQuestionnaireItem } from "@/api/psychology/questionnaireItem";
|
||||
import { listQuestionnaireItem, getQuestionnaireItem, delQuestionnaireItem, addQuestionnaireItem, updateQuestionnaireItem, updateQuestionnaireTotalScore } from "@/api/psychology/questionnaireItem";
|
||||
import { listQuestionnaireOption, addQuestionnaireOption, updateQuestionnaireOption, delQuestionnaireOption } from "@/api/psychology/questionnaireOption";
|
||||
|
||||
export default {
|
||||
|
|
@ -266,24 +282,36 @@ export default {
|
|||
};
|
||||
},
|
||||
created() {
|
||||
const questionnaireId = this.$route.query.questionnaireId;
|
||||
const questionnaireName = this.$route.query.questionnaireName;
|
||||
if (questionnaireId) {
|
||||
this.queryParams.questionnaireId = questionnaireId;
|
||||
this.currentQuestionnaireName = questionnaireName || '';
|
||||
this.getList();
|
||||
} else {
|
||||
this.$modal.msgError("问卷ID不能为空");
|
||||
this.$router.push('/psychology/questionnaire');
|
||||
}
|
||||
// 更新页面标题
|
||||
if (questionnaireName) {
|
||||
document.title = questionnaireName + " - 题目管理";
|
||||
} else {
|
||||
document.title = "题目管理";
|
||||
this.initPage();
|
||||
},
|
||||
watch: {
|
||||
// 监听路由变化,解决缓存问题
|
||||
'$route'(to, from) {
|
||||
if (to.path === '/psychology/questionnaire/item') {
|
||||
this.initPage();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 初始化页面 */
|
||||
initPage() {
|
||||
const questionnaireId = this.$route.query.questionnaireId;
|
||||
const questionnaireName = this.$route.query.questionnaireName;
|
||||
if (questionnaireId) {
|
||||
this.queryParams.questionnaireId = questionnaireId;
|
||||
this.currentQuestionnaireName = questionnaireName || '';
|
||||
this.getList();
|
||||
} else {
|
||||
this.$modal.msgError("问卷ID不能为空");
|
||||
this.$router.push('/psychology/questionnaire');
|
||||
}
|
||||
// 更新页面标题
|
||||
if (questionnaireName) {
|
||||
document.title = questionnaireName + " - 题目管理";
|
||||
} else {
|
||||
document.title = "题目管理";
|
||||
}
|
||||
},
|
||||
/** 查询题目列表 */
|
||||
getList() {
|
||||
if (!this.queryParams.questionnaireId) {
|
||||
|
|
@ -399,6 +427,15 @@ export default {
|
|||
handleBack() {
|
||||
this.$router.push('/psychology/questionnaire');
|
||||
},
|
||||
/** 更新问卷总分 */
|
||||
handleUpdateTotalScore() {
|
||||
this.$modal.confirm('是否根据当前题目分数重新计算问卷总分?').then(() => {
|
||||
return updateQuestionnaireTotalScore(this.queryParams.questionnaireId);
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess("更新成功");
|
||||
// 可以选择刷新页面或者显示成功消息
|
||||
}).catch(() => {});
|
||||
},
|
||||
/** 管理选项 */
|
||||
handleManageOptions(row) {
|
||||
this.currentItemId = row.itemId;
|
||||
|
|
|
|||
|
|
@ -101,9 +101,14 @@ export default {
|
|||
|
||||
startQuestionnaireAnswer(data).then(response => {
|
||||
if (response.code === 200) {
|
||||
this.$modal.msgSuccess("答题已开始");
|
||||
// 直接跳转,不显示提示(避免闪现)
|
||||
const answerId = response.data;
|
||||
this.$router.push({ path: '/psychology/questionnaire/taking', query: { answerId: answerId } });
|
||||
console.log('🔧 [问卷页面] 创建答题记录成功,answerId:', answerId);
|
||||
// 使用 replace 跳转到答题页面
|
||||
this.$router.replace({
|
||||
path: '/psychology/questionnaire/taking',
|
||||
query: { answerId: answerId }
|
||||
});
|
||||
}
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
|
|
@ -138,9 +143,14 @@ export default {
|
|||
|
||||
startQuestionnaireAnswer(data).then(response => {
|
||||
if (response.code === 200) {
|
||||
this.$modal.msgSuccess("答题已开始");
|
||||
// 直接跳转,不显示提示(避免闪现)
|
||||
const answerId = response.data;
|
||||
this.$router.push({ path: '/psychology/questionnaire/taking', query: { answerId: answerId } });
|
||||
console.log('🔧 [问卷页面-直接] 创建答题记录成功,answerId:', answerId);
|
||||
// 使用 replace 跳转到答题页面
|
||||
this.$router.replace({
|
||||
path: '/psychology/questionnaire/taking',
|
||||
query: { answerId: answerId }
|
||||
});
|
||||
}
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ export default {
|
|||
selectedOptions: [],
|
||||
answerText: '',
|
||||
sortOptions: [],
|
||||
originalSortOptions: [],
|
||||
originalSortOptions: {}, // 修改为对象,用于存储每个题目的原始排序选项
|
||||
answersMap: {},
|
||||
loading: false
|
||||
};
|
||||
|
|
@ -214,6 +214,7 @@ export default {
|
|||
},
|
||||
created() {
|
||||
this.answerId = this.$route.query.answerId;
|
||||
console.log('🔧 [问卷答题] created() - answerId:', this.answerId);
|
||||
if (!this.answerId) {
|
||||
this.$modal.msgError("答题ID不能为空");
|
||||
// 根据用户角色跳转到相应页面
|
||||
|
|
@ -222,16 +223,26 @@ export default {
|
|||
this.$router.push(isStudent ? '/student/tests' : '/psychology/scale');
|
||||
return;
|
||||
}
|
||||
// 清理旧数据,防止缓存(和量表一样)
|
||||
this.clearAllData();
|
||||
console.log('🔧 [问卷答题] 清理数据后 - answerId:', this.answerId);
|
||||
this.loadAnswer();
|
||||
},
|
||||
// watch 已移除:现在通过 AppMain.vue 的 key 变化自动重新创建组件
|
||||
beforeDestroy() {
|
||||
// 组件销毁前清理数据,防止缓存
|
||||
this.clearAllData();
|
||||
},
|
||||
methods: {
|
||||
/** 加载答题信息 */
|
||||
loadAnswer() {
|
||||
console.log('🔧 [问卷答题] loadAnswer() - answerId:', this.answerId);
|
||||
this.loading = true;
|
||||
Promise.all([
|
||||
getQuestionnaireAnswer(this.answerId),
|
||||
getAnswerDetails(this.answerId)
|
||||
]).then(([answerRes, detailsRes]) => {
|
||||
console.log('🔧 [问卷答题] API响应 - answerRes:', answerRes, 'detailsRes:', detailsRes);
|
||||
const answer = answerRes.data;
|
||||
if (!answer) {
|
||||
this.$modal.msgError("答题记录不存在");
|
||||
|
|
@ -257,10 +268,14 @@ export default {
|
|||
});
|
||||
|
||||
// 加载题目列表
|
||||
console.log('🔧 [问卷答题] 开始加载题目列表 - questionnaireId:', this.questionnaireId);
|
||||
getQuestionnaireItems(this.questionnaireId).then(response => {
|
||||
console.log('🔧 [问卷答题] 题目列表响应:', response);
|
||||
this.itemList = response.data || [];
|
||||
console.log('🔧 [问卷答题] itemList长度:', this.itemList.length);
|
||||
|
||||
if (this.itemList.length === 0) {
|
||||
console.error('❌ [问卷答题] 题目列表为空!');
|
||||
this.$modal.msgWarning("该问卷暂无题目,请联系管理员添加题目");
|
||||
// 根据用户角色跳转到相应页面
|
||||
const roles = this.$store.getters.roles || [];
|
||||
|
|
@ -464,6 +479,21 @@ export default {
|
|||
this.$router.push(isStudent ? '/student/tests' : '/psychology/scale');
|
||||
});
|
||||
},
|
||||
/** 清理所有数据 */
|
||||
clearAllData() {
|
||||
this.questionnaireId = null;
|
||||
this.questionnaireName = '';
|
||||
this.itemList = [];
|
||||
this.optionMap = {};
|
||||
this.currentIndex = 0;
|
||||
this.selectedOption = null;
|
||||
this.selectedOptions = [];
|
||||
this.answerText = '';
|
||||
this.sortOptions = [];
|
||||
this.originalSortOptions = {}; // 修改为对象
|
||||
this.answersMap = {};
|
||||
this.loading = false;
|
||||
},
|
||||
/** 提交问卷 */
|
||||
handleSubmit() {
|
||||
// 检查必答题是否都已作答
|
||||
|
|
@ -489,17 +519,31 @@ export default {
|
|||
|
||||
this.$modal.confirm('确定要提交问卷吗?提交后将不能修改。').then(() => {
|
||||
this.loading = true;
|
||||
const currentAnswerId = this.answerId; // 保存answerId用于后续跳转
|
||||
submitQuestionnaireAnswer(this.answerId).then(response => {
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(response.msg || "问卷已提交");
|
||||
|
||||
// 清理所有数据,防止缓存
|
||||
this.clearAllData();
|
||||
|
||||
// 提交成功,直接跳转到报告列表(和量表完全一样)
|
||||
this.$modal.msgSuccess("问卷已提交,报告已生成");
|
||||
|
||||
// 根据用户角色跳转到相应页面
|
||||
const roles = this.$store.getters.roles || [];
|
||||
const isStudent = roles.some(role => role === 'student' || role.includes('学员'));
|
||||
this.$router.push(isStudent ? '/student/tests' : '/psychology/scale');
|
||||
|
||||
// 延迟跳转,确保用户看到成功提示(和量表完全一样)
|
||||
setTimeout(() => {
|
||||
this.$router.push(isStudent ? '/student/tests' : '/psychology/questionnaire');
|
||||
}, 500);
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
console.error('提交问卷失败:', error);
|
||||
this.$modal.msgError(error.msg || "提交失败,请重试");
|
||||
});
|
||||
}).catch(() => {
|
||||
// 用户取消提交
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -167,6 +167,14 @@ export default {
|
|||
created() {
|
||||
this.loadReport();
|
||||
},
|
||||
watch: {
|
||||
// 监听路由变化,解决缓存问题
|
||||
'$route'(to, from) {
|
||||
if (to.path === '/psychology/report/detail') {
|
||||
this.loadReport();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 加载报告 */
|
||||
loadReport() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,22 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
|
||||
<el-form-item label="用户" prop="userId">
|
||||
<el-select
|
||||
v-model="queryParams.userId"
|
||||
placeholder="请选择用户"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px;"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.userId"
|
||||
:label="user.nickName + ' (' + user.userName + ')'"
|
||||
:value="user.userId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="来源类型" prop="sourceType">
|
||||
<el-select v-model="queryParams.sourceType" placeholder="请选择来源类型" clearable>
|
||||
<el-option label="量表" value="assessment" />
|
||||
|
|
@ -15,9 +31,9 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="生成状态" prop="isGenerated">
|
||||
<el-select v-model="queryParams.isGenerated" placeholder="生成状态" clearable>
|
||||
<el-option label="未生成" value="0" />
|
||||
<el-option label="已生成" value="1" />
|
||||
<el-select v-model="queryParams.isGenerated" placeholder="全部" clearable>
|
||||
<el-option label="待评分" value="0" />
|
||||
<el-option label="已完成" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
|
|
@ -87,8 +103,8 @@
|
|||
</el-table-column>
|
||||
<el-table-column label="生成状态" align="center" prop="isGenerated" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.isGenerated === '0'" type="warning">未生成</el-tag>
|
||||
<el-tag v-else-if="scope.row.isGenerated === '1'" type="success">已生成</el-tag>
|
||||
<el-tag v-if="scope.row.isGenerated === '0'" type="warning">待评分</el-tag>
|
||||
<el-tag v-else-if="scope.row.isGenerated === '1'" type="success">已完成</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="生成时间" align="center" prop="generateTime" width="180">
|
||||
|
|
@ -125,6 +141,14 @@
|
|||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['psychology:report:remove']"
|
||||
>删除</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.sourceType === 'questionnaire'"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-refresh"
|
||||
@click="handleRegenerate(scope.row)"
|
||||
v-hasPermi="['psychology:report:edit']"
|
||||
>重新生成</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
|
@ -164,18 +188,26 @@
|
|||
</el-dialog>
|
||||
|
||||
<!-- SAS 报告导出设置 -->
|
||||
<el-dialog title="报告导出" :visible.sync="sasExportDialog" width="420px" append-to-body @close="resetSasDialog">
|
||||
<el-form :model="sasExportForm" label-width="90px">
|
||||
<el-dialog title="报告导出" :visible.sync="sasExportDialog" width="480px" append-to-body @close="resetSasDialog">
|
||||
<el-form :model="sasExportForm" label-width="110px">
|
||||
<el-form-item label="导出格式">
|
||||
<el-radio-group v-model="sasExportForm.format">
|
||||
<el-radio label="word">Word 文档</el-radio>
|
||||
<el-radio label="print">打印/PDF</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="包含AI分析">
|
||||
<el-switch v-model="sasExportForm.includeAI"></el-switch>
|
||||
<div style="font-size: 12px; color: #909399; margin-top: 5px;">
|
||||
开启后将自动生成AI分析并包含在报告中(需要约30-60秒)
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="sasExportDialog = false">取 消</el-button>
|
||||
<el-button type="primary" :loading="sasExportLoading" @click="confirmSasExport">开始导出</el-button>
|
||||
<el-button type="primary" :loading="sasExportLoading" @click="confirmSasExport">
|
||||
{{ sasExportLoading ? '处理中...' : '开始导出' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
|
@ -186,6 +218,9 @@ import { listReport, getReport, delReport, exportReport, updateReportWithType }
|
|||
import { loadSasReportData } from "@/services/report/ReportDataMapper";
|
||||
import SASReportGenerator from "@/services/report/SASReportGenerator";
|
||||
import Editor from "@/components/Editor";
|
||||
import { listUser } from "@/api/system/user";
|
||||
import { regenerateQuestionnaireReport } from "@/api/psychology/questionnaireAnswer";
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: "Report",
|
||||
|
|
@ -209,10 +244,13 @@ export default {
|
|||
// 报告表格数据
|
||||
reportList: [],
|
||||
currentRow: null,
|
||||
// 用户列表
|
||||
userList: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
userId: undefined,
|
||||
sourceType: undefined,
|
||||
reportType: undefined,
|
||||
isGenerated: undefined
|
||||
|
|
@ -240,8 +278,17 @@ export default {
|
|||
},
|
||||
created() {
|
||||
this.getList();
|
||||
this.loadUsers();
|
||||
},
|
||||
methods: {
|
||||
/** 加载用户列表 */
|
||||
loadUsers() {
|
||||
listUser({ pageNum: 1, pageSize: 1000 }).then(response => {
|
||||
this.userList = response.rows || [];
|
||||
}).catch(error => {
|
||||
console.error('加载用户列表失败:', error);
|
||||
});
|
||||
},
|
||||
/** 查询报告列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
|
|
@ -455,7 +502,7 @@ export default {
|
|||
this.sasExportDialog = false;
|
||||
this.sasExportLoading = false;
|
||||
this.sasTarget = null;
|
||||
this.sasExportForm = { format: "word" };
|
||||
this.sasExportForm = { format: "word", includeAI: false };
|
||||
},
|
||||
async confirmSasExport() {
|
||||
if (!this.sasTarget) {
|
||||
|
|
@ -483,15 +530,92 @@ export default {
|
|||
}
|
||||
},
|
||||
async exportAssessmentReport() {
|
||||
const assessmentId = this.sasTarget.sourceId || this.sasTarget.assessmentId;
|
||||
if (!assessmentId) {
|
||||
throw new Error("无法定位测评ID");
|
||||
// 获取测评报告数据(后端已生成完整的HTML)
|
||||
const response = await getReport(this.sasTarget.reportId, 'assessment');
|
||||
if (!response || !response.data) {
|
||||
throw new Error("获取报告内容失败");
|
||||
}
|
||||
const reportData = await loadSasReportData(assessmentId);
|
||||
|
||||
const report = response.data;
|
||||
console.log('测评报告数据:', report);
|
||||
|
||||
// 如果需要包含AI分析,先生成AI分析
|
||||
let aiAnalysisHtml = '';
|
||||
if (this.sasExportForm.includeAI) {
|
||||
try {
|
||||
this.$message.info('正在生成AI分析,请稍候...');
|
||||
const aiResult = await this.generateAIAnalysis(
|
||||
report.reportContent || '',
|
||||
report.reportTitle || '测评报告',
|
||||
report.reportType || '标准报告'
|
||||
);
|
||||
aiAnalysisHtml = `
|
||||
<div class="ai-analysis" style="margin-top: 30px; padding: 20px; background-color: #f9f9f9; border-radius: 8px; border-left: 4px solid #67C23A;">
|
||||
<h2 style="color: #67C23A; margin-top: 0;">
|
||||
<i style="margin-right: 8px;">🤖</i>AI智能分析
|
||||
</h2>
|
||||
<div class="ai-content">
|
||||
${aiResult}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
console.error('AI分析失败:', error);
|
||||
this.$message.warning('AI分析失败,将继续导出报告(不含AI分析)');
|
||||
}
|
||||
}
|
||||
|
||||
// 直接使用后端生成的报告内容
|
||||
const reportHtml = `
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>${report.reportTitle || '测评报告'}</title>
|
||||
<style>
|
||||
body { font-family: 'Microsoft Yahei', sans-serif; padding: 32px; color: #303133; }
|
||||
h1, h2, h3 { margin: 16px 0; }
|
||||
h1 { text-align: center; font-size: 24px; }
|
||||
h2 { font-size: 20px; color: #1f2d3d; }
|
||||
h3 { font-size: 18px; color: #606266; }
|
||||
.section { margin-top: 24px; }
|
||||
.report-info { margin: 5px 0; color: #606266; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
||||
table td, table th { border: 1px solid #ddd; padding: 10px; font-size: 14px; }
|
||||
table th { background-color: #f5f7fa; font-weight: bold; }
|
||||
.content { margin-top: 24px; line-height: 1.8; }
|
||||
.summary { background-color: #f0f9ff; padding: 15px; border-left: 4px solid #409EFF; margin: 20px 0; }
|
||||
.ai-analysis { margin-top: 30px; padding: 20px; background-color: #f9f9f9; border-radius: 8px; }
|
||||
.ai-content p { line-height: 1.8; margin: 10px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${report.reportTitle || '测评报告'}</h1>
|
||||
${report.summary ? `<div class="summary"><h3>报告摘要</h3><div>${report.summary}</div></div>` : ''}
|
||||
<div class="content">
|
||||
${report.reportContent || '暂无报告内容'}
|
||||
</div>
|
||||
${aiAnalysisHtml}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
if (this.sasExportForm.format === 'word') {
|
||||
await SASReportGenerator.exportWord(reportData, false);
|
||||
// 导出为Word
|
||||
const blob = new Blob(['\ufeff', reportHtml], { type: 'application/msword' });
|
||||
const filename = `${report.reportTitle || '测评报告'}_${Date.now()}.doc`;
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
} else {
|
||||
await SASReportGenerator.print(reportData, false);
|
||||
// 打印
|
||||
const printWindow = window.open('', '_blank');
|
||||
printWindow.document.write(reportHtml);
|
||||
printWindow.document.close();
|
||||
printWindow.focus();
|
||||
printWindow.print();
|
||||
}
|
||||
},
|
||||
async exportQuestionnaireReport() {
|
||||
|
|
@ -504,6 +628,32 @@ export default {
|
|||
const report = response.data;
|
||||
console.log('问卷报告数据:', report);
|
||||
|
||||
// 如果需要包含AI分析,先生成AI分析
|
||||
let aiAnalysisHtml = '';
|
||||
if (this.sasExportForm.includeAI) {
|
||||
try {
|
||||
this.$message.info('正在生成AI分析,请稍候...');
|
||||
const aiResult = await this.generateAIAnalysis(
|
||||
report.reportContent || '',
|
||||
report.reportTitle || '问卷报告',
|
||||
report.reportType || '标准报告'
|
||||
);
|
||||
aiAnalysisHtml = `
|
||||
<div class="ai-analysis" style="margin-top: 30px; padding: 20px; background-color: #f9f9f9; border-radius: 8px; border-left: 4px solid #67C23A;">
|
||||
<h2 style="color: #67C23A; margin-top: 0;">
|
||||
<i style="margin-right: 8px;">🤖</i>AI智能分析
|
||||
</h2>
|
||||
<div class="ai-content">
|
||||
${aiResult}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
console.error('AI分析失败:', error);
|
||||
this.$message.warning('AI分析失败,将继续导出报告(不含AI分析)');
|
||||
}
|
||||
}
|
||||
|
||||
// 直接使用后端生成的报告内容(已包含评语)
|
||||
const reportHtml = `
|
||||
<html>
|
||||
|
|
@ -512,17 +662,20 @@ export default {
|
|||
<title>${report.reportTitle || '问卷报告'}</title>
|
||||
<style>
|
||||
body { font-family: 'Microsoft Yahei', sans-serif; padding: 32px; color: #303133; }
|
||||
h1, h2 { text-align: center; margin: 16px 0; }
|
||||
h1, h2, h3 { text-align: center; margin: 16px 0; }
|
||||
.section { margin-top: 24px; }
|
||||
.report-info { margin: 5px 0; color: #606266; }
|
||||
table.score-table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
||||
table.score-table td, table.score-table th { border: 1px solid #ddd; padding: 10px; font-size: 14px; }
|
||||
table.score-table th { background-color: #f5f7fa; font-weight: bold; }
|
||||
.content { margin-top: 24px; line-height: 1.8; }
|
||||
.ai-analysis { margin-top: 30px; padding: 20px; background-color: #f9f9f9; border-radius: 8px; }
|
||||
.ai-content p { line-height: 1.8; margin: 10px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${report.reportContent || '暂无报告内容'}
|
||||
${aiAnalysisHtml}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
|
@ -546,6 +699,113 @@ export default {
|
|||
printWindow.print();
|
||||
}
|
||||
},
|
||||
/** AI分析报告内容 */
|
||||
async generateAIAnalysis(reportContent, reportTitle, reportType) {
|
||||
// Ollama API配置
|
||||
const OLLAMA_URL = 'http://192.168.0.106:11434/api/generate';
|
||||
const MODEL = 'deepseek-r1:32b';
|
||||
|
||||
// 构建系统提示词
|
||||
const SYSTEM_PROMPT = [
|
||||
'你是专业心理测评报告分析师,请根据用户提供的报告内容进行深度分析。要求:',
|
||||
'1. 提取报告的核心信息和关键指标;',
|
||||
'2. 分析测评结果的含义和可能的影响;',
|
||||
'3. 提供专业、客观、易懂的分析解读(500-800字);',
|
||||
'4. 使用结构化的格式输出,包含:核心结论、详细分析、建议、总体结论四个部分;',
|
||||
'5. 仅输出分析结果,不添加额外建议、问候语或思考过程;',
|
||||
'6. 使用HTML格式输出,使用<h3>标签作为小标题,<p>标签作为段落。'
|
||||
].join('\n');
|
||||
|
||||
// 提取纯文本内容(去除HTML标签)
|
||||
const textContent = reportContent.replace(/<[^>]*>/g, '').substring(0, 3000);
|
||||
|
||||
const prompt = `${SYSTEM_PROMPT}\n\n重要:请直接输出结果,不要包含任何思考过程、<think>标签或</think>标签。\n\n报告标题:${reportTitle}\n报告类型:${reportType}\n报告内容:${textContent}`;
|
||||
|
||||
try {
|
||||
const { data } = await axios.post(OLLAMA_URL, {
|
||||
model: MODEL,
|
||||
prompt: prompt,
|
||||
temperature: 0.2,
|
||||
num_predict: 1000,
|
||||
stream: false
|
||||
}, {
|
||||
timeout: 60000 // 60秒超时
|
||||
});
|
||||
|
||||
let rawResponse = data?.response ?? '';
|
||||
|
||||
// 过滤掉思考过程标签
|
||||
rawResponse = rawResponse
|
||||
.replace(/<think>[\s\S]*?<\/think>/gi, '')
|
||||
.replace(/<think>[\s\S]*?<\/redacted_reasoning>/gi, '')
|
||||
.replace(/<think[\s\S]*?>/gi, '')
|
||||
.replace(/<redacted_reasoning[\s\S]*?>/gi, '')
|
||||
// 移除Markdown代码块标记
|
||||
.replace(/```html\s*/gi, '')
|
||||
.replace(/```\s*/g, '')
|
||||
.replace(/```[a-z]*\s*/gi, '')
|
||||
.trim();
|
||||
|
||||
if (!rawResponse) {
|
||||
throw new Error('AI分析返回结果为空');
|
||||
}
|
||||
|
||||
// 格式化结果
|
||||
return this.formatAIResult(rawResponse);
|
||||
|
||||
} catch (err) {
|
||||
console.error('AI分析失败:', err);
|
||||
throw new Error('AI分析失败:' + (err.message || '未知错误'));
|
||||
}
|
||||
},
|
||||
/** 格式化AI分析结果 */
|
||||
formatAIResult(text) {
|
||||
// 移除Markdown代码块标记
|
||||
let html = text
|
||||
.replace(/```html\s*/gi, '')
|
||||
.replace(/```\s*/g, '')
|
||||
.replace(/```[a-z]*\s*/gi, '')
|
||||
.trim();
|
||||
|
||||
// 如果已经是HTML格式,清理后返回
|
||||
if (html.includes('<h3>') || html.includes('<p>') || html.includes('<div>')) {
|
||||
return html;
|
||||
}
|
||||
|
||||
// 处理标题
|
||||
html = html.replace(/^(\d+[\.、]?\s*[^\n]+)$/gm, '<h3>$1</h3>');
|
||||
html = html.replace(/^([^\n]*(?:结论|分析|建议|总结|概述)[^\n]*)$/gm, '<h3>$1</h3>');
|
||||
|
||||
// 将段落分隔符转换为<p>标签
|
||||
html = html.split('\n\n').map(para => {
|
||||
para = para.trim();
|
||||
if (!para) return '';
|
||||
if (para.startsWith('<h3>')) return para;
|
||||
return '<p>' + para.replace(/\n/g, '<br>') + '</p>';
|
||||
}).join('');
|
||||
|
||||
return html;
|
||||
},
|
||||
/** 重新生成报告 */
|
||||
handleRegenerate(row) {
|
||||
if (!row.sourceId) {
|
||||
this.$modal.msgError("无法获取答题记录ID");
|
||||
return;
|
||||
}
|
||||
this.$modal.confirm('是否确认重新生成该问卷报告?').then(() => {
|
||||
this.loading = true;
|
||||
return regenerateQuestionnaireReport(row.sourceId);
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess("报告生成成功");
|
||||
this.getList();
|
||||
}).catch((error) => {
|
||||
console.error('生成报告失败:', error);
|
||||
const errorMsg = error.msg || error.message || "生成报告失败";
|
||||
this.$modal.msgError(errorMsg);
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
let reportIds;
|
||||
|
|
|
|||
|
|
@ -295,25 +295,37 @@ export default {
|
|||
};
|
||||
},
|
||||
created() {
|
||||
const scaleId = this.$route.query.scaleId;
|
||||
const scaleName = this.$route.query.scaleName;
|
||||
if (scaleId) {
|
||||
this.queryParams.scaleId = scaleId;
|
||||
this.currentScaleName = scaleName || '';
|
||||
this.getList();
|
||||
this.loadItems();
|
||||
} else {
|
||||
// 如果没有scaleId,加载量表列表供选择
|
||||
this.loadScaleList();
|
||||
}
|
||||
// 更新页面标题
|
||||
if (scaleName) {
|
||||
document.title = scaleName + " - 因子管理";
|
||||
} else {
|
||||
document.title = "因子管理";
|
||||
this.initPage();
|
||||
},
|
||||
watch: {
|
||||
// 监听路由变化,解决缓存问题
|
||||
'$route'(to, from) {
|
||||
if (to.path === '/psychology/scale/factor') {
|
||||
this.initPage();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 初始化页面 */
|
||||
initPage() {
|
||||
const scaleId = this.$route.query.scaleId;
|
||||
const scaleName = this.$route.query.scaleName;
|
||||
if (scaleId) {
|
||||
this.queryParams.scaleId = scaleId;
|
||||
this.currentScaleName = scaleName || '';
|
||||
this.getList();
|
||||
this.loadItems();
|
||||
} else {
|
||||
// 如果没有scaleId,加载量表列表供选择
|
||||
this.loadScaleList();
|
||||
}
|
||||
// 更新页面标题
|
||||
if (scaleName) {
|
||||
document.title = scaleName + " - 因子管理";
|
||||
} else {
|
||||
document.title = "因子管理";
|
||||
}
|
||||
},
|
||||
/** 加载量表列表(只显示量表,不包含问卷) */
|
||||
loadScaleList() {
|
||||
this.scaleLoading = true;
|
||||
|
|
|
|||
|
|
@ -250,24 +250,36 @@ export default {
|
|||
};
|
||||
},
|
||||
created() {
|
||||
const scaleId = this.$route.query.scaleId;
|
||||
const scaleName = this.$route.query.scaleName;
|
||||
if (scaleId) {
|
||||
this.queryParams.scaleId = scaleId;
|
||||
this.currentScaleName = scaleName || '';
|
||||
this.getList();
|
||||
} else {
|
||||
// 如果没有scaleId,加载量表列表供选择
|
||||
this.loadScales();
|
||||
}
|
||||
// 更新页面标题
|
||||
if (scaleName) {
|
||||
document.title = scaleName + " - 题目管理";
|
||||
} else {
|
||||
document.title = "题目管理";
|
||||
this.initPage();
|
||||
},
|
||||
watch: {
|
||||
// 监听路由变化,解决缓存问题
|
||||
'$route'(to, from) {
|
||||
if (to.path === '/psychology/scale/item') {
|
||||
this.initPage();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 初始化页面 */
|
||||
initPage() {
|
||||
const scaleId = this.$route.query.scaleId;
|
||||
const scaleName = this.$route.query.scaleName;
|
||||
if (scaleId) {
|
||||
this.queryParams.scaleId = scaleId;
|
||||
this.currentScaleName = scaleName || '';
|
||||
this.getList();
|
||||
} else {
|
||||
// 如果没有scaleId,加载量表列表供选择
|
||||
this.loadScales();
|
||||
}
|
||||
// 更新页面标题
|
||||
if (scaleName) {
|
||||
document.title = scaleName + " - 题目管理";
|
||||
} else {
|
||||
document.title = "题目管理";
|
||||
}
|
||||
},
|
||||
/** 加载量表列表(只显示量表,不包含问卷) */
|
||||
loadScales() {
|
||||
this.scaleLoading = true;
|
||||
|
|
|
|||
|
|
@ -9,20 +9,47 @@
|
|||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="量表ID" prop="scaleId">
|
||||
<el-input
|
||||
v-model="queryParams.scaleId"
|
||||
placeholder="请输入量表ID"
|
||||
<el-form-item label="用户" prop="userId">
|
||||
<el-select
|
||||
v-model="queryParams.userId"
|
||||
placeholder="请选择用户"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px;"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.userId"
|
||||
:label="user.nickName + ' (' + user.userName + ')'"
|
||||
:value="user.userId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="量表" prop="scaleId">
|
||||
<el-select
|
||||
v-model="queryParams.scaleId"
|
||||
placeholder="请选择量表"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px;"
|
||||
@keyup.enter.native="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="scale in scaleList"
|
||||
:key="scale.scaleId"
|
||||
:label="scale.scaleName"
|
||||
:value="scale.scaleId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="预警等级" prop="warningLevel">
|
||||
<el-select v-model="queryParams.warningLevel" placeholder="预警等级" clearable>
|
||||
<el-option label="低" value="low" />
|
||||
<el-option label="中" value="medium" />
|
||||
<el-option label="高" value="high" />
|
||||
<el-option label="严重" value="critical" />
|
||||
<el-select v-model="queryParams.warningLevel" placeholder="预警等级" clearable filterable>
|
||||
<el-option label="低" value="低" />
|
||||
<el-option label="中" value="中" />
|
||||
<el-option label="高" value="高" />
|
||||
<el-option label="紧急" value="紧急" />
|
||||
<el-option label="极高" value="极高" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
|
|
@ -66,10 +93,13 @@
|
|||
</el-table-column>
|
||||
<el-table-column label="预警等级" align="center" prop="warningLevel" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.warningLevel === 'low'" type="info">低</el-tag>
|
||||
<el-tag v-else-if="scope.row.warningLevel === 'medium'" type="warning">中</el-tag>
|
||||
<el-tag v-else-if="scope.row.warningLevel === 'high'" type="danger">高</el-tag>
|
||||
<el-tag v-else-if="scope.row.warningLevel === 'critical'" type="danger">严重</el-tag>
|
||||
<el-tag
|
||||
v-if="getWarningLevelMeta(scope.row.warningLevel)"
|
||||
:type="getWarningLevelMeta(scope.row.warningLevel).type"
|
||||
>
|
||||
{{ getWarningLevelMeta(scope.row.warningLevel).label }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预警得分" align="center" prop="warningScore" width="100" />
|
||||
|
|
@ -161,10 +191,13 @@
|
|||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="预警等级">
|
||||
<el-tag v-if="form.warningLevel === 'low'" type="info">低</el-tag>
|
||||
<el-tag v-else-if="form.warningLevel === 'medium'" type="warning">中</el-tag>
|
||||
<el-tag v-else-if="form.warningLevel === 'high'" type="danger">高</el-tag>
|
||||
<el-tag v-else-if="form.warningLevel === 'critical'" type="danger">严重</el-tag>
|
||||
<el-tag
|
||||
v-if="getWarningLevelMeta(form.warningLevel)"
|
||||
:type="getWarningLevelMeta(form.warningLevel).type"
|
||||
>
|
||||
{{ getWarningLevelMeta(form.warningLevel).label }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
|
@ -203,6 +236,8 @@
|
|||
|
||||
<script>
|
||||
import { listWarning, getWarning, delWarning, handleWarning, relieveWarning } from "@/api/psychology/warning";
|
||||
import { listScale } from "@/api/psychology/scale";
|
||||
import { listUser } from "@/api/system/user";
|
||||
|
||||
export default {
|
||||
name: "Warning",
|
||||
|
|
@ -222,6 +257,10 @@ export default {
|
|||
total: 0,
|
||||
// 预警表格数据
|
||||
warningList: [],
|
||||
// 量表列表
|
||||
scaleList: [],
|
||||
// 用户列表
|
||||
userList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
|
|
@ -231,6 +270,7 @@ export default {
|
|||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
assessmentId: undefined,
|
||||
userId: undefined,
|
||||
scaleId: undefined,
|
||||
warningLevel: undefined,
|
||||
status: undefined
|
||||
|
|
@ -241,8 +281,28 @@ export default {
|
|||
},
|
||||
created() {
|
||||
this.getList();
|
||||
this.loadScales();
|
||||
this.loadUsers();
|
||||
},
|
||||
methods: {
|
||||
/** 加载用户列表 */
|
||||
loadUsers() {
|
||||
listUser({ pageNum: 1, pageSize: 1000 }).then(response => {
|
||||
this.userList = response.rows || [];
|
||||
}).catch(error => {
|
||||
console.error('加载用户列表失败:', error);
|
||||
});
|
||||
},
|
||||
/** 加载量表列表 */
|
||||
loadScales() {
|
||||
listScale({ status: '0', pageNum: 1, pageSize: 1000, includeQuestionnaire: false }).then(response => {
|
||||
// 只显示量表,不包含问卷
|
||||
this.scaleList = (response.rows || [])
|
||||
.filter(scale => !scale.sourceType || scale.sourceType === 'scale');
|
||||
}).catch(error => {
|
||||
console.error('加载量表列表失败:', error);
|
||||
});
|
||||
},
|
||||
/** 查询预警列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
|
|
@ -330,6 +390,38 @@ export default {
|
|||
reset() {
|
||||
this.form = {};
|
||||
this.resetForm("form");
|
||||
},
|
||||
/** 预警等级展示映射 */
|
||||
getWarningLevelMeta(level) {
|
||||
if (!level) {
|
||||
return null;
|
||||
}
|
||||
const normalized = String(level).toLowerCase().trim();
|
||||
|
||||
// 统一的预警等级映射表(低、中、高、紧急、极高)
|
||||
const map = {
|
||||
// 英文
|
||||
'low': { label: "低", type: "info" },
|
||||
'medium': { label: "中", type: "warning" },
|
||||
'high': { label: "高", type: "danger" },
|
||||
'urgent': { label: "紧急", type: "danger" },
|
||||
'critical': { label: "紧急", type: "danger" },
|
||||
// 中文
|
||||
'低': { label: "低", type: "info" },
|
||||
'中': { label: "中", type: "warning" },
|
||||
'高': { label: "高", type: "danger" },
|
||||
'紧急': { label: "紧急", type: "danger" },
|
||||
'极高': { label: "极高", type: "danger" },
|
||||
'严重': { label: "紧急", type: "danger" }
|
||||
};
|
||||
|
||||
// 精确匹配
|
||||
if (map[normalized]) {
|
||||
return map[normalized];
|
||||
}
|
||||
|
||||
// 默认返回低等级
|
||||
return { label: level, type: "info" };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,20 +1,30 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
|
||||
<el-form-item label="量表ID" prop="scaleId">
|
||||
<el-input
|
||||
<el-form-item label="量表" prop="scaleId">
|
||||
<el-select
|
||||
v-model="queryParams.scaleId"
|
||||
placeholder="请输入量表ID"
|
||||
placeholder="请选择量表"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px;"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
>
|
||||
<el-option
|
||||
v-for="scale in scaleList"
|
||||
:key="scale.scaleId"
|
||||
:label="scale.scaleName"
|
||||
:value="scale.scaleId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="预警等级" prop="warningLevel">
|
||||
<el-select v-model="queryParams.warningLevel" placeholder="预警等级" clearable>
|
||||
<el-option label="低" value="low" />
|
||||
<el-option label="中" value="medium" />
|
||||
<el-option label="高" value="high" />
|
||||
<el-option label="严重" value="critical" />
|
||||
<el-select v-model="queryParams.warningLevel" placeholder="预警等级" clearable filterable>
|
||||
<el-option label="低" value="低" />
|
||||
<el-option label="中" value="中" />
|
||||
<el-option label="高" value="高" />
|
||||
<el-option label="紧急" value="紧急" />
|
||||
<el-option label="极高" value="极高" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
|
|
@ -83,18 +93,15 @@
|
|||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="百分位范围" align="center" width="150">
|
||||
<el-table-column label="自动解除" align="center" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.percentileMin != null || scope.row.percentileMax != null">
|
||||
{{ scope.row.percentileMin != null ? scope.row.percentileMin : '无下限' }} ~ {{ scope.row.percentileMax != null ? scope.row.percentileMax : '无上限' }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="自动解除" align="center" prop="autoRelief" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.autoRelief === '1'" type="success">是</el-tag>
|
||||
<el-tag v-else type="info">否</el-tag>
|
||||
<div v-if="scope.row.autoRelief === '1'">
|
||||
<el-tag type="success" size="small">是</el-tag>
|
||||
<div style="font-size: 12px; color: #909399; margin-top: 2px;" v-if="scope.row.reliefCondition">
|
||||
{{ getReliefScoreDisplay(scope.row.reliefCondition) }}
|
||||
</div>
|
||||
</div>
|
||||
<el-tag v-else type="info" size="small">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
|
|
@ -175,10 +182,11 @@
|
|||
<el-col :span="12">
|
||||
<el-form-item label="预警等级" prop="warningLevel">
|
||||
<el-select v-model="form.warningLevel" placeholder="请选择预警等级" style="width: 100%;">
|
||||
<el-option label="低" value="low" />
|
||||
<el-option label="中" value="medium" />
|
||||
<el-option label="高" value="high" />
|
||||
<el-option label="严重" value="critical" />
|
||||
<el-option label="低" value="低" />
|
||||
<el-option label="中" value="中" />
|
||||
<el-option label="高" value="高" />
|
||||
<el-option label="紧急" value="紧急" />
|
||||
<el-option label="极高" value="极高" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
|
@ -195,18 +203,6 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="百分位最小值">
|
||||
<el-input-number v-model="form.percentileMin" :precision="2" :min="0" :max="100" :step="0.1" style="width: 100%;" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="百分位最大值">
|
||||
<el-input-number v-model="form.percentileMax" :precision="2" :min="0" :max="100" :step="0.1" style="width: 100%;" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否自动解除">
|
||||
|
|
@ -216,6 +212,38 @@
|
|||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="form.autoRelief === '1'">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="解除条件">
|
||||
<el-select v-model="form.reliefOperator" placeholder="请选择" style="width: 100%;" @change="handleReliefOperatorChange">
|
||||
<el-option label="分值处于范围内" value="between" />
|
||||
<el-option label="分值低于" value="<" />
|
||||
<el-option label="分值高于" value=">" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-show="form.autoRelief === '1' && form.reliefOperator === 'between'">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="最小分值">
|
||||
<el-input-number v-model="form.reliefScoreMin" :precision="2" :step="0.1" placeholder="请输入最小分值" style="width: 100%;" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="最大分值">
|
||||
<el-input-number v-model="form.reliefScoreMax" :precision="2" :step="0.1" placeholder="请输入最大分值" style="width: 100%;" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-show="form.autoRelief === '1' && (form.reliefOperator === '<' || form.reliefOperator === '>')">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="解除分值">
|
||||
<el-input-number v-model="form.reliefScore" :precision="2" :step="0.1" placeholder="请输入分值" style="width: 100%;" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
|
|
@ -225,9 +253,6 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="解除条件">
|
||||
<el-input v-model="form.reliefCondition" type="textarea" :rows="3" placeholder="请输入解除条件(JSON格式)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
|
|
@ -349,14 +374,64 @@ export default {
|
|||
if (!level) {
|
||||
return null;
|
||||
}
|
||||
const normalized = String(level).toLowerCase();
|
||||
const normalized = String(level).toLowerCase().trim();
|
||||
|
||||
// 统一的预警等级映射表(低、中、高、紧急、极高)
|
||||
const map = {
|
||||
low: { label: "低", type: "info" },
|
||||
medium: { label: "中", type: "warning" },
|
||||
high: { label: "高", type: "danger" },
|
||||
critical: { label: "严重", type: "danger" }
|
||||
// 英文
|
||||
'low': { label: "低", type: "info" },
|
||||
'medium': { label: "中", type: "warning" },
|
||||
'high': { label: "高", type: "danger" },
|
||||
'urgent': { label: "紧急", type: "danger" },
|
||||
'critical': { label: "紧急", type: "danger" },
|
||||
// 中文
|
||||
'低': { label: "低", type: "info" },
|
||||
'中': { label: "中", type: "warning" },
|
||||
'高': { label: "高", type: "danger" },
|
||||
'紧急': { label: "紧急", type: "danger" },
|
||||
'极高': { label: "极高", type: "danger" },
|
||||
'严重': { label: "紧急", type: "danger" }
|
||||
};
|
||||
return map[normalized] || { label: level, type: "info" };
|
||||
|
||||
// 精确匹配
|
||||
if (map[normalized]) {
|
||||
return map[normalized];
|
||||
}
|
||||
|
||||
// 默认返回低等级
|
||||
return { label: level, type: "info" };
|
||||
},
|
||||
/** 解析并显示解除分值 */
|
||||
getReliefScoreDisplay(reliefCondition) {
|
||||
if (!reliefCondition) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const condition = JSON.parse(reliefCondition);
|
||||
if (condition.operator === 'between' && condition.scoreMin !== undefined && condition.scoreMax !== undefined) {
|
||||
return `${condition.scoreMin} ~ ${condition.scoreMax}`;
|
||||
} else if (condition.score !== undefined) {
|
||||
const operator = condition.operator || '<';
|
||||
return `${operator} ${condition.score}`;
|
||||
}
|
||||
} catch (e) {
|
||||
// 如果不是JSON格式,直接返回原值
|
||||
return reliefCondition;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
/** 解除条件类型改变时清空相关字段 */
|
||||
handleReliefOperatorChange(value) {
|
||||
console.log('解除条件改变为:', value);
|
||||
console.log('当前form.reliefOperator:', this.form.reliefOperator);
|
||||
if (value === 'between') {
|
||||
// 切换到范围内,清空单个分值
|
||||
this.$set(this.form, 'reliefScore', null);
|
||||
} else if (value === '<' || value === '>') {
|
||||
// 切换到高于/低于,清空范围分值
|
||||
this.$set(this.form, 'reliefScoreMin', null);
|
||||
this.$set(this.form, 'reliefScoreMax', null);
|
||||
}
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
|
|
@ -376,6 +451,31 @@ export default {
|
|||
const ruleId = row.ruleId || this.ids[0];
|
||||
getWarningRule(ruleId).then(response => {
|
||||
this.form = response.data;
|
||||
// 解析JSON格式的解除条件
|
||||
if (this.form.reliefCondition) {
|
||||
try {
|
||||
const condition = JSON.parse(this.form.reliefCondition);
|
||||
if (condition.operator !== undefined) {
|
||||
this.form.reliefOperator = condition.operator;
|
||||
} else {
|
||||
// 默认为范围内
|
||||
this.form.reliefOperator = 'between';
|
||||
}
|
||||
|
||||
// 根据操作符类型提取不同字段
|
||||
if (condition.operator === 'between') {
|
||||
this.form.reliefScoreMin = condition.scoreMin;
|
||||
this.form.reliefScoreMax = condition.scoreMax;
|
||||
} else if (condition.score !== undefined) {
|
||||
this.form.reliefScore = condition.score;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('无法解析解除条件,使用默认值');
|
||||
this.form.reliefOperator = 'between';
|
||||
}
|
||||
} else {
|
||||
this.form.reliefOperator = 'between';
|
||||
}
|
||||
// 加载因子列表
|
||||
if (this.form.scaleId) {
|
||||
this.loadFactors(this.form.scaleId);
|
||||
|
|
@ -388,14 +488,55 @@ export default {
|
|||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
const submitData = { ...this.form };
|
||||
|
||||
// 根据不同的操作符类型构建JSON
|
||||
if (submitData.autoRelief === '1' && submitData.reliefOperator) {
|
||||
if (submitData.reliefOperator === 'between') {
|
||||
// 范围内解除
|
||||
if (submitData.reliefScoreMin !== undefined && submitData.reliefScoreMax !== undefined) {
|
||||
submitData.reliefCondition = JSON.stringify({
|
||||
type: 'score',
|
||||
operator: 'between',
|
||||
scoreMin: submitData.reliefScoreMin,
|
||||
scoreMax: submitData.reliefScoreMax,
|
||||
description: `分值处于${submitData.reliefScoreMin}~${submitData.reliefScoreMax}范围内时自动解除`
|
||||
});
|
||||
} else {
|
||||
submitData.reliefCondition = null;
|
||||
}
|
||||
} else {
|
||||
// 高于或低于
|
||||
if (submitData.reliefScore !== undefined) {
|
||||
const operatorText = submitData.reliefOperator === '<' ? '低于' : '高于';
|
||||
submitData.reliefCondition = JSON.stringify({
|
||||
type: 'score',
|
||||
score: submitData.reliefScore,
|
||||
operator: submitData.reliefOperator,
|
||||
description: `分值${operatorText}${submitData.reliefScore}时自动解除`
|
||||
});
|
||||
} else {
|
||||
submitData.reliefCondition = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
submitData.reliefCondition = null;
|
||||
}
|
||||
|
||||
// 删除临时字段,不发送到后端
|
||||
delete submitData.reliefScore;
|
||||
delete submitData.reliefScoreMin;
|
||||
delete submitData.reliefScoreMax;
|
||||
delete submitData.reliefOperator;
|
||||
|
||||
if (this.form.ruleId != undefined) {
|
||||
updateWarningRule(this.form).then(response => {
|
||||
updateWarningRule(submitData).then(response => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
});
|
||||
} else {
|
||||
addWarningRule(this.form).then(response => {
|
||||
addWarningRule(submitData).then(response => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
|
|
@ -429,9 +570,11 @@ export default {
|
|||
warningLevel: undefined,
|
||||
scoreMin: undefined,
|
||||
scoreMax: undefined,
|
||||
percentileMin: undefined,
|
||||
percentileMax: undefined,
|
||||
autoRelief: "0",
|
||||
reliefScore: null,
|
||||
reliefScoreMin: null,
|
||||
reliefScoreMax: null,
|
||||
reliefOperator: "between",
|
||||
reliefCondition: undefined,
|
||||
status: "1",
|
||||
remark: undefined
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { axios } from '@/utils/request'
|
||||
import request from '@/utils/request'
|
||||
|
||||
export default {
|
||||
name: 'MenuCleanup',
|
||||
|
|
@ -71,7 +71,7 @@ export default {
|
|||
checkDuplicateMenus() {
|
||||
this.loading = true
|
||||
this.showDuplicates = true
|
||||
axios.post('/system/menu/cleanup/check').then(res => {
|
||||
request.post('/system/menu/cleanup/check').then(res => {
|
||||
if (res.code === 200) {
|
||||
this.tableData = res.data
|
||||
this.hasDuplicates = this.tableData.length > 0
|
||||
|
|
@ -104,7 +104,7 @@ export default {
|
|||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.loading = true
|
||||
axios.post('/system/menu/cleanup/cleanup').then(res => {
|
||||
request.post('/system/menu/cleanup/cleanup').then(res => {
|
||||
if (res.code === 200) {
|
||||
this.message = res.msg || '清理成功'
|
||||
this.messageType = 'success'
|
||||
|
|
@ -126,7 +126,7 @@ export default {
|
|||
listPsychologyMenus() {
|
||||
this.loading = true
|
||||
this.showDuplicates = false
|
||||
axios.post('/system/menu/cleanup/list').then(res => {
|
||||
request.post('/system/menu/cleanup/list').then(res => {
|
||||
if (res.code === 200) {
|
||||
this.tableData = res.data
|
||||
this.message = `共查询到 ${this.tableData.length} 个心理学相关菜单`
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user