问卷和警告bug修复

This commit is contained in:
xiao12feng8 2025-11-22 21:26:49 +08:00
parent 612becc4eb
commit 2e7104c0c6
34 changed files with 1687 additions and 301 deletions

View File

@ -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);

View File

@ -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());

View File

@ -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());
}
}
}

View File

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

View File

@ -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)

View File

@ -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)

View File

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

View File

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

View File

@ -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();
}
}
}

View File

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

View File

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

View File

@ -65,5 +65,13 @@ public interface IPsyQuestionnaireItemService
* @return 结果
*/
public int deleteItemById(Long itemId);
/**
* 更新问卷总分根据题目分数自动计算
*
* @param questionnaireId 问卷ID
* @return 结果
*/
public int updateQuestionnaireTotalScore(Long questionnaireId);
}

View File

@ -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">

View File

@ -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}

View File

@ -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}

View File

@ -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() // 添加时间戳防止缓存
}
})
}

View File

@ -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'
})
}

View File

@ -42,3 +42,11 @@ export function delQuestionnaireItem(itemIds) {
})
}
// 更新问卷总分(根据题目分数自动计算)
export function updateQuestionnaireTotalScore(questionnaireId) {
return request({
url: '/psychology/questionnaire/item/updateTotalScore/' + questionnaireId,
method: 'put'
})
}

View File

@ -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
}
},

View File

@ -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">

View File

@ -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) {

View File

@ -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 };
// URLuserId
@ -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 };
// URLuserId
@ -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 };
// URLuserId
@ -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
};
// 使targetUserIdURL使
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 => {

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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 => {

View File

@ -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(() => {
//
});
}
},

View File

@ -167,6 +167,14 @@ export default {
created() {
this.loadReport();
},
watch: {
//
'$route'(to, from) {
if (to.path === '/psychology/report/detail') {
this.loadReport();
}
}
},
methods: {
/** 加载报告 */
loadReport() {

View File

@ -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);
// AIAI
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);
// AIAI
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;

View File

@ -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;

View File

@ -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;

View File

@ -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" };
}
}
};

View File

@ -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

View File

@ -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} 个心理学相关菜单`