修改bug,优化程序。
This commit is contained in:
commit
c014532706
|
|
@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.PutMapping;
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import com.ddnai.common.annotation.Log;
|
import com.ddnai.common.annotation.Log;
|
||||||
import com.ddnai.common.core.controller.BaseController;
|
import com.ddnai.common.core.controller.BaseController;
|
||||||
|
|
@ -36,7 +37,6 @@ import com.ddnai.system.service.psychology.IPsyAssessmentService;
|
||||||
import com.ddnai.system.service.psychology.IPsyAssessmentAnswerService;
|
import com.ddnai.system.service.psychology.IPsyAssessmentAnswerService;
|
||||||
import com.ddnai.system.service.psychology.IPsyScaleItemService;
|
import com.ddnai.system.service.psychology.IPsyScaleItemService;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 测评记录 信息操作处理
|
* 测评记录 信息操作处理
|
||||||
|
|
@ -87,10 +87,33 @@ public class PsyAssessmentController extends BaseController
|
||||||
* 获取暂停的测评列表
|
* 获取暂停的测评列表
|
||||||
*/
|
*/
|
||||||
@GetMapping("/pausedList")
|
@GetMapping("/pausedList")
|
||||||
public AjaxResult pausedList()
|
public AjaxResult pausedList(@RequestParam(value = "userId", required = false) Long userId)
|
||||||
{
|
{
|
||||||
Long userId = SecurityUtils.getUserId();
|
Long currentUserId = SecurityUtils.getUserId();
|
||||||
List<PsyAssessment> list = assessmentService.selectPausedAssessmentList(userId);
|
boolean hasManagePerm = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
hasManagePerm = SecurityUtils.hasPermi("psychology:assessment:list");
|
||||||
|
}
|
||||||
|
catch (Exception ignored)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Long targetUserId = null;
|
||||||
|
if (userId != null)
|
||||||
|
{
|
||||||
|
if (!userId.equals(currentUserId) && !hasManagePerm)
|
||||||
|
{
|
||||||
|
return error("无权查看其他用户的暂停测评记录");
|
||||||
|
}
|
||||||
|
targetUserId = userId;
|
||||||
|
}
|
||||||
|
else if (!hasManagePerm)
|
||||||
|
{
|
||||||
|
targetUserId = currentUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PsyAssessment> list = assessmentService.selectPausedAssessmentList(targetUserId);
|
||||||
return success(list);
|
return success(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.ddnai.web.controller.psychology;
|
package com.ddnai.web.controller.psychology;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -74,6 +73,7 @@ public class PsyScaleController extends BaseController
|
||||||
public TableDataInfo list(PsyScale scale, @RequestParam(required = false, defaultValue = "true") Boolean includeQuestionnaire)
|
public TableDataInfo list(PsyScale scale, @RequestParam(required = false, defaultValue = "true") Boolean includeQuestionnaire)
|
||||||
{
|
{
|
||||||
Set<Long> allowedScaleIds = resolveAllowedScaleIdsForCurrentUser();
|
Set<Long> allowedScaleIds = resolveAllowedScaleIdsForCurrentUser();
|
||||||
|
Set<Long> restrictedScaleIds = resolveRestrictedScaleIds();
|
||||||
boolean needPermissionFilter = allowedScaleIds != null;
|
boolean needPermissionFilter = allowedScaleIds != null;
|
||||||
|
|
||||||
// 如果需要包含问卷,需要先查询所有数据,合并后再分页
|
// 如果需要包含问卷,需要先查询所有数据,合并后再分页
|
||||||
|
|
@ -91,7 +91,7 @@ public class PsyScaleController extends BaseController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scaleList = filterScaleListByPermission(scaleList, allowedScaleIds);
|
scaleList = filterScaleListByPermission(scaleList, allowedScaleIds, restrictedScaleIds);
|
||||||
|
|
||||||
// 查询问卷列表(不分页,因为需要合并后再分页)
|
// 查询问卷列表(不分页,因为需要合并后再分页)
|
||||||
PsyQuestionnaire questionnaireQuery = new PsyQuestionnaire();
|
PsyQuestionnaire questionnaireQuery = new PsyQuestionnaire();
|
||||||
|
|
@ -119,7 +119,7 @@ public class PsyScaleController extends BaseController
|
||||||
scaleList.add(scaleItem);
|
scaleList.add(scaleItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
scaleList = filterScaleListByPermission(scaleList, allowedScaleIds);
|
scaleList = filterScaleListByPermission(scaleList, allowedScaleIds, restrictedScaleIds);
|
||||||
|
|
||||||
// 按排序顺序和创建时间排序
|
// 按排序顺序和创建时间排序
|
||||||
scaleList.sort((a, b) -> {
|
scaleList.sort((a, b) -> {
|
||||||
|
|
@ -157,7 +157,7 @@ public class PsyScaleController extends BaseController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scaleList = filterScaleListByPermission(scaleList, allowedScaleIds);
|
scaleList = filterScaleListByPermission(scaleList, allowedScaleIds, restrictedScaleIds);
|
||||||
|
|
||||||
if (needPermissionFilter)
|
if (needPermissionFilter)
|
||||||
{
|
{
|
||||||
|
|
@ -193,7 +193,6 @@ public class PsyScaleController extends BaseController
|
||||||
scale.setRemark(questionnaire.getRemark());
|
scale.setRemark(questionnaire.getRemark());
|
||||||
return scale;
|
return scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建手动分页结果,适用于自定义过滤后的列表
|
* 构建手动分页结果,适用于自定义过滤后的列表
|
||||||
*/
|
*/
|
||||||
|
|
@ -224,7 +223,7 @@ public class PsyScaleController extends BaseController
|
||||||
* @param scaleList 原始列表
|
* @param scaleList 原始列表
|
||||||
* @param allowedScaleIds null表示无需过滤;非null表示仅可访问允许集合中的量表
|
* @param allowedScaleIds null表示无需过滤;非null表示仅可访问允许集合中的量表
|
||||||
*/
|
*/
|
||||||
private List<PsyScale> filterScaleListByPermission(List<PsyScale> scaleList, Set<Long> allowedScaleIds)
|
private List<PsyScale> filterScaleListByPermission(List<PsyScale> scaleList, Set<Long> allowedScaleIds, Set<Long> restrictedScaleIds)
|
||||||
{
|
{
|
||||||
if (allowedScaleIds == null || scaleList == null)
|
if (allowedScaleIds == null || scaleList == null)
|
||||||
{
|
{
|
||||||
|
|
@ -238,13 +237,16 @@ public class PsyScaleController extends BaseController
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String sourceType = scale.getSourceType();
|
String sourceType = scale.getSourceType();
|
||||||
|
Long scaleId = scale.getScaleId();
|
||||||
if ("questionnaire".equalsIgnoreCase(sourceType))
|
if ("questionnaire".equalsIgnoreCase(sourceType))
|
||||||
{
|
{
|
||||||
// 问卷类型默认对所有学员开放
|
boolean restricted = restrictedScaleIds != null && restrictedScaleIds.contains(scaleId);
|
||||||
filtered.add(scale);
|
if (!restricted)
|
||||||
continue;
|
{
|
||||||
|
filtered.add(scale);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Long scaleId = scale.getScaleId();
|
|
||||||
if (scaleId != null && allowedScaleIds.contains(scaleId))
|
if (scaleId != null && allowedScaleIds.contains(scaleId))
|
||||||
{
|
{
|
||||||
filtered.add(scale);
|
filtered.add(scale);
|
||||||
|
|
@ -585,6 +587,24 @@ public class PsyScaleController extends BaseController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Set<Long> resolveRestrictedScaleIds()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<Long> ids = scalePermissionService.selectAllScaleIdsWithPermission();
|
||||||
|
if (ids == null || ids.isEmpty())
|
||||||
|
{
|
||||||
|
return java.util.Collections.emptySet();
|
||||||
|
}
|
||||||
|
return new java.util.HashSet<>(ids);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.warn("解析量表权限限制列表失败: {}", e.getMessage());
|
||||||
|
return java.util.Collections.emptySet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private Map<String, Object> normalizeScaleImportMap(Map<String, Object> root)
|
private Map<String, Object> normalizeScaleImportMap(Map<String, Object> root)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import com.ddnai.common.enums.BusinessType;
|
||||||
import com.ddnai.common.utils.SecurityUtils;
|
import com.ddnai.common.utils.SecurityUtils;
|
||||||
import com.ddnai.common.utils.StringUtils;
|
import com.ddnai.common.utils.StringUtils;
|
||||||
import com.ddnai.common.utils.poi.ExcelUtil;
|
import com.ddnai.common.utils.poi.ExcelUtil;
|
||||||
|
import com.ddnai.system.domain.dto.ImportProgress;
|
||||||
import com.ddnai.system.domain.psychology.PsyUserProfile;
|
import com.ddnai.system.domain.psychology.PsyUserProfile;
|
||||||
import com.ddnai.system.service.ISysDeptService;
|
import com.ddnai.system.service.ISysDeptService;
|
||||||
import com.ddnai.system.service.ISysPostService;
|
import com.ddnai.system.service.ISysPostService;
|
||||||
|
|
@ -292,4 +293,15 @@ public class PsyUserProfileController extends BaseController
|
||||||
String message = profileService.importProfile(profileList, updateSupport, operName);
|
String message = profileService.importProfile(profileList, updateSupport, operName);
|
||||||
return success(message);
|
return success(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询导入进度
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('psychology:profile:import')")
|
||||||
|
@GetMapping("/importProgress")
|
||||||
|
public AjaxResult getImportProgress()
|
||||||
|
{
|
||||||
|
ImportProgress progress = profileService.getImportProgress(getUsername());
|
||||||
|
return success(progress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
package com.ddnai.system.domain.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用导入进度信息
|
||||||
|
*/
|
||||||
|
public class ImportProgress implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 总数量 */
|
||||||
|
private int total;
|
||||||
|
|
||||||
|
/** 已处理数量 */
|
||||||
|
private int processed;
|
||||||
|
|
||||||
|
/** 成功数量 */
|
||||||
|
private int success;
|
||||||
|
|
||||||
|
/** 失败数量 */
|
||||||
|
private int failure;
|
||||||
|
|
||||||
|
/** 状态:processing、success、failed */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 状态描述/提示 */
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/** 开始时间 */
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
|
/** 最近更新时间 */
|
||||||
|
private LocalDateTime lastUpdateTime;
|
||||||
|
|
||||||
|
/** 过期时间戳(毫秒) */
|
||||||
|
private long expireAt;
|
||||||
|
|
||||||
|
public int getTotal()
|
||||||
|
{
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotal(int total)
|
||||||
|
{
|
||||||
|
this.total = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProcessed()
|
||||||
|
{
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProcessed(int processed)
|
||||||
|
{
|
||||||
|
this.processed = processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSuccess()
|
||||||
|
{
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuccess(int success)
|
||||||
|
{
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFailure()
|
||||||
|
{
|
||||||
|
return failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFailure(int failure)
|
||||||
|
{
|
||||||
|
this.failure = failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus()
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status)
|
||||||
|
{
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message)
|
||||||
|
{
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getStartTime()
|
||||||
|
{
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartTime(LocalDateTime startTime)
|
||||||
|
{
|
||||||
|
this.startTime = startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getLastUpdateTime()
|
||||||
|
{
|
||||||
|
return lastUpdateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastUpdateTime(LocalDateTime lastUpdateTime)
|
||||||
|
{
|
||||||
|
this.lastUpdateTime = lastUpdateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getExpireAt()
|
||||||
|
{
|
||||||
|
return expireAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpireAt(long expireAt)
|
||||||
|
{
|
||||||
|
this.expireAt = expireAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExpired(long now)
|
||||||
|
{
|
||||||
|
return expireAt > 0 && now > expireAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImportProgress copy()
|
||||||
|
{
|
||||||
|
ImportProgress copy = new ImportProgress();
|
||||||
|
copy.setTotal(this.total);
|
||||||
|
copy.setProcessed(this.processed);
|
||||||
|
copy.setSuccess(this.success);
|
||||||
|
copy.setFailure(this.failure);
|
||||||
|
copy.setStatus(this.status);
|
||||||
|
copy.setMessage(this.message);
|
||||||
|
copy.setStartTime(this.startTime);
|
||||||
|
copy.setLastUpdateTime(this.lastUpdateTime);
|
||||||
|
copy.setExpireAt(this.expireAt);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -46,6 +46,9 @@ public class PsyScalePermission extends BaseEntity
|
||||||
/** 量表名称(关联查询字段,不存储在表中) */
|
/** 量表名称(关联查询字段,不存储在表中) */
|
||||||
private String scaleName;
|
private String scaleName;
|
||||||
|
|
||||||
|
/** 来源类型(scale/questionnaire) */
|
||||||
|
private String sourceType;
|
||||||
|
|
||||||
/** 部门名称(关联查询字段,不存储在表中) */
|
/** 部门名称(关联查询字段,不存储在表中) */
|
||||||
private String deptName;
|
private String deptName;
|
||||||
|
|
||||||
|
|
@ -155,6 +158,16 @@ public class PsyScalePermission extends BaseEntity
|
||||||
this.scaleName = scaleName;
|
this.scaleName = scaleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSourceType()
|
||||||
|
{
|
||||||
|
return sourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceType(String sourceType)
|
||||||
|
{
|
||||||
|
this.sourceType = sourceType;
|
||||||
|
}
|
||||||
|
|
||||||
public String getDeptName()
|
public String getDeptName()
|
||||||
{
|
{
|
||||||
return deptName;
|
return deptName;
|
||||||
|
|
@ -202,6 +215,8 @@ public class PsyScalePermission extends BaseEntity
|
||||||
.append("updateBy", getUpdateBy())
|
.append("updateBy", getUpdateBy())
|
||||||
.append("updateTime", getUpdateTime())
|
.append("updateTime", getUpdateTime())
|
||||||
.append("remark", getRemark())
|
.append("remark", getRemark())
|
||||||
|
.append("scaleName", getScaleName())
|
||||||
|
.append("sourceType", getSourceType())
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ public class PsyUserProfile extends BaseEntity
|
||||||
private String profileData;
|
private String profileData;
|
||||||
|
|
||||||
/** 姓名 */
|
/** 姓名 */
|
||||||
@Excel(name = "罪犯姓名", sort = 2)
|
@Excel(name = "罪犯姓名(必填)", sort = 2)
|
||||||
private String userName;
|
private String userName;
|
||||||
|
|
||||||
/** 电话 */
|
/** 电话 */
|
||||||
|
|
@ -42,14 +42,15 @@ public class PsyUserProfile extends BaseEntity
|
||||||
|
|
||||||
/** 生日 */
|
/** 生日 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
@Excel(name = "出生日期", sort = 6, width = 20, dateFormat = "yyyy-MM-dd")
|
||||||
private java.util.Date birthday;
|
private java.util.Date birthday;
|
||||||
|
|
||||||
/** 监狱 */
|
/** 监狱 */
|
||||||
@Excel(name = "监狱", sort = 3)
|
@Excel(name = "监狱(必填)", sort = 3)
|
||||||
private String prison;
|
private String prison;
|
||||||
|
|
||||||
/** 监区 */
|
/** 监区 */
|
||||||
@Excel(name = "监区", sort = 4)
|
@Excel(name = "监区(必填)", sort = 4)
|
||||||
private String prisonArea;
|
private String prisonArea;
|
||||||
|
|
||||||
/** 性别 */
|
/** 性别 */
|
||||||
|
|
@ -57,38 +58,38 @@ public class PsyUserProfile extends BaseEntity
|
||||||
private String gender;
|
private String gender;
|
||||||
|
|
||||||
/** 民族 */
|
/** 民族 */
|
||||||
@Excel(name = "民族", sort = 6)
|
@Excel(name = "民族", sort = 7)
|
||||||
private String nation;
|
private String nation;
|
||||||
|
|
||||||
/** 文化程度 */
|
/** 文化程度 */
|
||||||
@Excel(name = "文化程度", sort = 7)
|
@Excel(name = "文化程度", sort = 8)
|
||||||
private String educationLevel;
|
private String educationLevel;
|
||||||
|
|
||||||
/** 罪名 */
|
/** 罪名 */
|
||||||
@Excel(name = "罪名", sort = 8)
|
@Excel(name = "罪名", sort = 9)
|
||||||
private String crimeName;
|
private String crimeName;
|
||||||
|
|
||||||
/** 刑期 */
|
/** 刑期 */
|
||||||
@Excel(name = "刑期", sort = 9)
|
@Excel(name = "刑期", sort = 10)
|
||||||
private String sentenceTerm;
|
private String sentenceTerm;
|
||||||
|
|
||||||
/** 刑期起日 */
|
/** 刑期起日 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
@Excel(name = "刑期起日", sort = 10, width = 30, dateFormat = "yyyy-MM-dd")
|
@Excel(name = "刑期起日", sort = 11, width = 30, dateFormat = "yyyy-MM-dd")
|
||||||
private java.util.Date sentenceStartDate;
|
private java.util.Date sentenceStartDate;
|
||||||
|
|
||||||
/** 刑期止日 */
|
/** 刑期止日 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
@Excel(name = "刑期止日", sort = 11, width = 30, dateFormat = "yyyy-MM-dd")
|
@Excel(name = "刑期止日", sort = 12, width = 30, dateFormat = "yyyy-MM-dd")
|
||||||
private java.util.Date sentenceEndDate;
|
private java.util.Date sentenceEndDate;
|
||||||
|
|
||||||
/** 入监时间 */
|
/** 入监时间 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
@Excel(name = "入监时间", sort = 12, width = 30, dateFormat = "yyyy-MM-dd")
|
@Excel(name = "入监时间", sort = 13, width = 30, dateFormat = "yyyy-MM-dd")
|
||||||
private java.util.Date entryDate;
|
private java.util.Date entryDate;
|
||||||
|
|
||||||
/** 用户状态(0在押 1释放 2外出 3假释) */
|
/** 用户状态(0在押 1释放 2外出 3假释) */
|
||||||
@Excel(name = "状态", sort = 13, readConverterExp = "0=在押,1=释放,2=外出,3=假释")
|
@Excel(name = "状态", sort = 14, readConverterExp = "0=在押,1=释放,2=外出,3=假释")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
/** 部门ID */
|
/** 部门ID */
|
||||||
|
|
@ -98,7 +99,7 @@ public class PsyUserProfile extends BaseEntity
|
||||||
private String deptName;
|
private String deptName;
|
||||||
|
|
||||||
/** 信息编号 */
|
/** 信息编号 */
|
||||||
@Excel(name = "信息编号", sort = 1)
|
@Excel(name = "信息编号(必填)", sort = 1)
|
||||||
private String infoNumber;
|
private String infoNumber;
|
||||||
|
|
||||||
public Long getProfileId()
|
public Long getProfileId()
|
||||||
|
|
|
||||||
|
|
@ -91,5 +91,12 @@ public interface PsyScalePermissionMapper
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
public int deletePermissionByScaleId(Long scaleId);
|
public int deletePermissionByScaleId(Long scaleId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有已配置权限的量表ID
|
||||||
|
*
|
||||||
|
* @return 量表ID集合
|
||||||
|
*/
|
||||||
|
public List<Long> selectAllScaleIdsWithPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||||
import com.ddnai.common.utils.SecurityUtils;
|
import com.ddnai.common.utils.SecurityUtils;
|
||||||
import com.ddnai.system.domain.psychology.PsyScalePermission;
|
import com.ddnai.system.domain.psychology.PsyScalePermission;
|
||||||
import com.ddnai.system.mapper.psychology.PsyScalePermissionMapper;
|
import com.ddnai.system.mapper.psychology.PsyScalePermissionMapper;
|
||||||
|
import com.ddnai.system.domain.psychology.PsyQuestionnaire;
|
||||||
import com.ddnai.system.service.psychology.IPsyScalePermissionService;
|
import com.ddnai.system.service.psychology.IPsyScalePermissionService;
|
||||||
import com.ddnai.system.service.psychology.IPsyScaleService;
|
import com.ddnai.system.service.psychology.IPsyScaleService;
|
||||||
|
import com.ddnai.system.service.psychology.IPsyQuestionnaireService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 量表权限 服务层实现
|
* 量表权限 服务层实现
|
||||||
|
|
@ -27,6 +29,9 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IPsyScaleService scaleService;
|
private IPsyScaleService scaleService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IPsyQuestionnaireService questionnaireService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询量表权限信息
|
* 查询量表权限信息
|
||||||
|
|
@ -92,15 +97,7 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
|
||||||
@Override
|
@Override
|
||||||
public int insertPermission(PsyScalePermission permission)
|
public int insertPermission(PsyScalePermission permission)
|
||||||
{
|
{
|
||||||
// 验证 scale_id 是否存在
|
validateScaleExists(permission.getScaleId());
|
||||||
if (permission.getScaleId() != null)
|
|
||||||
{
|
|
||||||
com.ddnai.system.domain.psychology.PsyScale scale = scaleService.selectScaleById(permission.getScaleId());
|
|
||||||
if (scale == null)
|
|
||||||
{
|
|
||||||
throw new RuntimeException("量表不存在,scaleId: " + permission.getScaleId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (permission.getStatus() == null)
|
if (permission.getStatus() == null)
|
||||||
{
|
{
|
||||||
|
|
@ -150,15 +147,7 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
|
||||||
@Override
|
@Override
|
||||||
public int updatePermission(PsyScalePermission permission)
|
public int updatePermission(PsyScalePermission permission)
|
||||||
{
|
{
|
||||||
// 验证 scale_id 是否存在
|
validateScaleExists(permission.getScaleId());
|
||||||
if (permission.getScaleId() != null)
|
|
||||||
{
|
|
||||||
com.ddnai.system.domain.psychology.PsyScale scale = scaleService.selectScaleById(permission.getScaleId());
|
|
||||||
if (scale == null)
|
|
||||||
{
|
|
||||||
throw new RuntimeException("量表不存在,scaleId: " + permission.getScaleId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return permissionMapper.updatePermission(permission);
|
return permissionMapper.updatePermission(permission);
|
||||||
}
|
}
|
||||||
|
|
@ -239,12 +228,10 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 验证 scale_id 是否存在
|
if (!validateScaleExistsSilent(scaleId))
|
||||||
com.ddnai.system.domain.psychology.PsyScale scale = scaleService.selectScaleById(scaleId);
|
|
||||||
if (scale == null)
|
|
||||||
{
|
{
|
||||||
log.warn("量表不存在,跳过该权限分配,scaleId: {}", scaleId);
|
log.warn("量表/问卷不存在,跳过该权限分配,scaleId: {}", scaleId);
|
||||||
continue; // 跳过不存在的量表
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsyScalePermission permission = new PsyScalePermission();
|
PsyScalePermission permission = new PsyScalePermission();
|
||||||
|
|
@ -267,5 +254,35 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Long> selectAllScaleIdsWithPermission()
|
||||||
|
{
|
||||||
|
return permissionMapper.selectAllScaleIdsWithPermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateScaleExists(Long scaleId)
|
||||||
|
{
|
||||||
|
if (!validateScaleExistsSilent(scaleId))
|
||||||
|
{
|
||||||
|
throw new RuntimeException("量表或问卷不存在,scaleId: " + scaleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateScaleExistsSilent(Long scaleId)
|
||||||
|
{
|
||||||
|
if (scaleId == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (scaleId < 0)
|
||||||
|
{
|
||||||
|
Long questionnaireId = Math.abs(scaleId);
|
||||||
|
PsyQuestionnaire questionnaire = questionnaireService.selectQuestionnaireById(questionnaireId);
|
||||||
|
return questionnaire != null;
|
||||||
|
}
|
||||||
|
com.ddnai.system.domain.psychology.PsyScale scale = scaleService.selectScaleById(scaleId);
|
||||||
|
return scale != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,12 @@ import com.ddnai.common.utils.SecurityUtils;
|
||||||
import com.ddnai.common.utils.StringUtils;
|
import com.ddnai.common.utils.StringUtils;
|
||||||
import com.ddnai.system.domain.psychology.PsyUserProfile;
|
import com.ddnai.system.domain.psychology.PsyUserProfile;
|
||||||
import com.ddnai.system.mapper.psychology.PsyUserProfileMapper;
|
import com.ddnai.system.mapper.psychology.PsyUserProfileMapper;
|
||||||
|
import com.ddnai.system.domain.dto.ImportProgress;
|
||||||
import com.ddnai.system.service.ISysConfigService;
|
import com.ddnai.system.service.ISysConfigService;
|
||||||
import com.ddnai.system.service.ISysRoleService;
|
import com.ddnai.system.service.ISysRoleService;
|
||||||
import com.ddnai.system.service.ISysUserService;
|
import com.ddnai.system.service.ISysUserService;
|
||||||
import com.ddnai.system.service.psychology.IPsyUserProfileService;
|
import com.ddnai.system.service.psychology.IPsyUserProfileService;
|
||||||
|
import com.ddnai.system.service.psychology.support.ImportProgressManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户档案扩展表 服务层实现
|
* 用户档案扩展表 服务层实现
|
||||||
|
|
@ -41,6 +43,9 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISysRoleService roleService;
|
private ISysRoleService roleService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ImportProgressManager importProgressManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询档案信息
|
* 查询档案信息
|
||||||
*
|
*
|
||||||
|
|
@ -134,14 +139,14 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService
|
||||||
profile.setInfoNumber(infoNumber);
|
profile.setInfoNumber(infoNumber);
|
||||||
validateInfoNumberUnique(infoNumber, null);
|
validateInfoNumberUnique(infoNumber, null);
|
||||||
|
|
||||||
// 验证姓名:如果提供,只能包含汉字
|
// 验证姓名:如果提供,只能包含汉字和数字
|
||||||
if (StringUtils.isNotEmpty(profile.getUserName()))
|
if (StringUtils.isNotEmpty(profile.getUserName()))
|
||||||
{
|
{
|
||||||
String userName = profile.getUserName().trim();
|
String userName = profile.getUserName().trim();
|
||||||
if (!userName.matches("^[\\u4e00-\\u9fa5]+$"))
|
if (!userName.matches("^[\\u4e00-\\u9fa5\\d]+$"))
|
||||||
{
|
{
|
||||||
log.error("创建用户档案失败:姓名格式错误,只能输入汉字,userName: {}", userName);
|
log.error("创建用户档案失败:姓名格式错误,只能输入汉字和数字,userName: {}", userName);
|
||||||
throw new ServiceException("姓名只能输入汉字");
|
throw new ServiceException("姓名只能输入汉字和数字");
|
||||||
}
|
}
|
||||||
profile.setUserName(userName);
|
profile.setUserName(userName);
|
||||||
}
|
}
|
||||||
|
|
@ -302,14 +307,14 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService
|
||||||
profile.setInfoNumber(infoNumber);
|
profile.setInfoNumber(infoNumber);
|
||||||
validateInfoNumberUnique(infoNumber, profile.getProfileId());
|
validateInfoNumberUnique(infoNumber, profile.getProfileId());
|
||||||
|
|
||||||
// 验证姓名:如果提供,只能包含汉字
|
// 验证姓名:如果提供,只能包含汉字和数字
|
||||||
if (StringUtils.isNotEmpty(profile.getUserName()))
|
if (StringUtils.isNotEmpty(profile.getUserName()))
|
||||||
{
|
{
|
||||||
String userName = profile.getUserName().trim();
|
String userName = profile.getUserName().trim();
|
||||||
if (!userName.matches("^[\\u4e00-\\u9fa5]+$"))
|
if (!userName.matches("^[\\u4e00-\\u9fa5\\d]+$"))
|
||||||
{
|
{
|
||||||
log.error("修改用户档案失败:姓名格式错误,只能输入汉字,userName: {}", userName);
|
log.error("修改用户档案失败:姓名格式错误,只能输入汉字和数字,userName: {}", userName);
|
||||||
throw new ServiceException("姓名只能输入汉字");
|
throw new ServiceException("姓名只能输入汉字和数字");
|
||||||
}
|
}
|
||||||
profile.setUserName(userName);
|
profile.setUserName(userName);
|
||||||
syncUserName(profile.getUserId(), userName);
|
syncUserName(profile.getUserId(), userName);
|
||||||
|
|
@ -488,92 +493,133 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService
|
||||||
{
|
{
|
||||||
throw new ServiceException("导入用户档案数据不能为空!");
|
throw new ServiceException("导入用户档案数据不能为空!");
|
||||||
}
|
}
|
||||||
|
final String progressKey = importProgressManager.buildProfileKey(operName);
|
||||||
|
importProgressManager.start(progressKey, profileList.size());
|
||||||
int successNum = 0;
|
int successNum = 0;
|
||||||
int failureNum = 0;
|
int failureNum = 0;
|
||||||
StringBuilder successMsg = new StringBuilder();
|
StringBuilder successMsg = new StringBuilder();
|
||||||
StringBuilder failureMsg = new StringBuilder();
|
StringBuilder failureMsg = new StringBuilder();
|
||||||
|
try
|
||||||
for (PsyUserProfile profile : profileList)
|
|
||||||
{
|
{
|
||||||
try
|
for (PsyUserProfile profile : profileList)
|
||||||
{
|
{
|
||||||
// 设置创建者
|
try
|
||||||
profile.setCreateBy(operName);
|
{
|
||||||
|
// 设置创建者
|
||||||
// 验证信息编号
|
profile.setCreateBy(operName);
|
||||||
if (StringUtils.isEmpty(profile.getInfoNumber()))
|
|
||||||
|
// 验证信息编号
|
||||||
|
if (StringUtils.isEmpty(profile.getInfoNumber()))
|
||||||
|
{
|
||||||
|
failureNum++;
|
||||||
|
failureMsg.append("<br/>").append(failureNum).append("、档案信息编号为空");
|
||||||
|
importProgressManager.recordFailure(progressKey);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据信息编号查询是否存在
|
||||||
|
PsyUserProfile existProfile = profileMapper.selectProfileByInfoNumber(profile.getInfoNumber());
|
||||||
|
if (StringUtils.isNull(existProfile))
|
||||||
|
{
|
||||||
|
// 新增档案
|
||||||
|
this.insertProfile(profile);
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、信息编号 ").append(profile.getInfoNumber()).append(" 导入成功");
|
||||||
|
importProgressManager.recordSuccess(progressKey);
|
||||||
|
}
|
||||||
|
else if (isUpdateSupport)
|
||||||
|
{
|
||||||
|
// 更新档案
|
||||||
|
profile.setProfileId(existProfile.getProfileId());
|
||||||
|
profile.setUserId(existProfile.getUserId());
|
||||||
|
profile.setUpdateBy(operName);
|
||||||
|
this.updateProfile(profile);
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、信息编号 ").append(profile.getInfoNumber()).append(" 更新成功");
|
||||||
|
importProgressManager.recordSuccess(progressKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
failureNum++;
|
||||||
|
failureMsg.append("<br/>").append(failureNum).append("、信息编号 ").append(profile.getInfoNumber()).append(" 已存在");
|
||||||
|
importProgressManager.recordFailure(progressKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
failureNum++;
|
failureNum++;
|
||||||
failureMsg.append("<br/>").append(failureNum).append("、档案信息编号为空");
|
String msg = "<br/>" + failureNum + "、信息编号 " + profile.getInfoNumber() + " 导入失败:";
|
||||||
continue;
|
failureMsg.append(msg).append(e.getMessage());
|
||||||
|
importProgressManager.recordFailure(progressKey);
|
||||||
|
log.error(msg, e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 根据信息编号查询是否存在
|
|
||||||
PsyUserProfile existProfile = profileMapper.selectProfileByInfoNumber(profile.getInfoNumber());
|
// 根据成功/失败情况生成结果
|
||||||
if (StringUtils.isNull(existProfile))
|
if (failureNum > 0)
|
||||||
|
{
|
||||||
|
if (successNum == 0)
|
||||||
{
|
{
|
||||||
// 新增档案
|
// 全部失败
|
||||||
this.insertProfile(profile);
|
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
|
||||||
successNum++;
|
String finalMsg = failureMsg.toString();
|
||||||
successMsg.append("<br/>").append(successNum).append("、信息编号 ").append(profile.getInfoNumber()).append(" 导入成功");
|
importProgressManager.finishFailure(progressKey, finalMsg);
|
||||||
}
|
throw new ServiceException(finalMsg);
|
||||||
else if (isUpdateSupport)
|
|
||||||
{
|
|
||||||
// 更新档案
|
|
||||||
profile.setProfileId(existProfile.getProfileId());
|
|
||||||
profile.setUserId(existProfile.getUserId());
|
|
||||||
profile.setUpdateBy(operName);
|
|
||||||
this.updateProfile(profile);
|
|
||||||
successNum++;
|
|
||||||
successMsg.append("<br/>").append(successNum).append("、信息编号 ").append(profile.getInfoNumber()).append(" 更新成功");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
failureNum++;
|
// 部分成功、部分失败:同时返回成功和失败统计
|
||||||
failureMsg.append("<br/>").append(failureNum).append("、信息编号 ").append(profile.getInfoNumber()).append(" 已存在");
|
StringBuilder resultMsg = new StringBuilder();
|
||||||
|
resultMsg.append("本次导入完成:共 ")
|
||||||
|
.append(successNum + failureNum)
|
||||||
|
.append(" 条,其中成功 ")
|
||||||
|
.append(successNum)
|
||||||
|
.append(" 条,失败 ")
|
||||||
|
.append(failureNum)
|
||||||
|
.append(" 条。");
|
||||||
|
|
||||||
|
if (successNum > 0)
|
||||||
|
{
|
||||||
|
resultMsg.append("<br/>成功明细如下:");
|
||||||
|
resultMsg.append(successMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
failureMsg.insert(0, "<br/><br/>失败明细如下:");
|
||||||
|
resultMsg.append(failureMsg);
|
||||||
|
|
||||||
|
String finalMsg = resultMsg.toString();
|
||||||
|
importProgressManager.finishFailure(progressKey, finalMsg);
|
||||||
|
return finalMsg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
failureNum++;
|
|
||||||
String msg = "<br/>" + failureNum + "、信息编号 " + profile.getInfoNumber() + " 导入失败:";
|
|
||||||
failureMsg.append(msg).append(e.getMessage());
|
|
||||||
log.error(msg, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failureNum > 0)
|
|
||||||
{
|
|
||||||
if (successNum == 0)
|
|
||||||
{
|
|
||||||
// 全部失败,保持原有行为:抛出异常
|
|
||||||
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
|
|
||||||
throw new ServiceException(failureMsg.toString());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 部分成功、部分失败:同时返回成功和失败统计,前端可一次性展示
|
|
||||||
StringBuilder resultMsg = new StringBuilder();
|
|
||||||
resultMsg.append("本次导入完成:共 ")
|
|
||||||
.append(successNum + failureNum)
|
|
||||||
.append(" 条,其中成功 ")
|
|
||||||
.append(successNum)
|
|
||||||
.append(" 条,失败 ")
|
|
||||||
.append(failureNum)
|
|
||||||
.append(" 条。成功明细如下:");
|
|
||||||
|
|
||||||
resultMsg.append(successMsg);
|
// 没有失败
|
||||||
failureMsg.insert(0, "<br/><br/>失败明细如下:");
|
if (successNum > 0)
|
||||||
resultMsg.append(failureMsg);
|
{
|
||||||
return resultMsg.toString();
|
String successMessage = "恭喜您,数据已全部导入成功!<br/>" + successMsg.toString();
|
||||||
|
importProgressManager.finishSuccess(progressKey, successMessage);
|
||||||
|
return successMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 理论上不会到这里(既没有成功也没有失败),但为安全起见保留兜底逻辑
|
||||||
|
importProgressManager.finishSuccess(progressKey, "导入完成,但未检测到需要处理的数据。");
|
||||||
|
return "导入完成,但未检测到需要处理的数据。";
|
||||||
}
|
}
|
||||||
else
|
catch (RuntimeException ex)
|
||||||
{
|
{
|
||||||
// 全部成功
|
if (!(ex instanceof ServiceException))
|
||||||
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
|
{
|
||||||
return successMsg.toString();
|
importProgressManager.finishFailure(progressKey, ex.getMessage());
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImportProgress getImportProgress(String operName)
|
||||||
|
{
|
||||||
|
String progressKey = importProgressManager.buildProfileKey(operName);
|
||||||
|
return importProgressManager.snapshot(progressKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,13 @@ public interface IPsyScalePermissionService
|
||||||
*/
|
*/
|
||||||
public int deletePermissionByScaleId(Long scaleId);
|
public int deletePermissionByScaleId(Long scaleId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有已配置权限的量表ID
|
||||||
|
*
|
||||||
|
* @return 量表ID集合
|
||||||
|
*/
|
||||||
|
public List<Long> selectAllScaleIdsWithPermission();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量分配用户量表权限
|
* 批量分配用户量表权限
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.ddnai.system.service.psychology;
|
package com.ddnai.system.service.psychology;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import com.ddnai.system.domain.dto.ImportProgress;
|
||||||
import com.ddnai.system.domain.psychology.PsyUserProfile;
|
import com.ddnai.system.domain.psychology.PsyUserProfile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -83,5 +84,13 @@ public interface IPsyUserProfileService
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
public String importProfile(List<PsyUserProfile> profileList, Boolean isUpdateSupport, String operName);
|
public String importProfile(List<PsyUserProfile> profileList, Boolean isUpdateSupport, String operName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询导入进度
|
||||||
|
*
|
||||||
|
* @param operName 操作人
|
||||||
|
* @return 进度信息
|
||||||
|
*/
|
||||||
|
public ImportProgress getImportProgress(String operName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
package com.ddnai.system.service.psychology.support;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.ddnai.system.domain.dto.ImportProgress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入进度管理
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ImportProgressManager
|
||||||
|
{
|
||||||
|
private static final long EXPIRE_MILLIS = TimeUnit.MINUTES.toMillis(30);
|
||||||
|
|
||||||
|
private final Map<String, ImportProgress> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public String buildProfileKey(String username)
|
||||||
|
{
|
||||||
|
return "psychology_profile:" + username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start(String key, int total)
|
||||||
|
{
|
||||||
|
if (Objects.isNull(key))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ImportProgress progress = new ImportProgress();
|
||||||
|
progress.setTotal(total);
|
||||||
|
progress.setProcessed(0);
|
||||||
|
progress.setSuccess(0);
|
||||||
|
progress.setFailure(0);
|
||||||
|
progress.setStatus("processing");
|
||||||
|
progress.setStartTime(LocalDateTime.now());
|
||||||
|
progress.setLastUpdateTime(LocalDateTime.now());
|
||||||
|
progress.setExpireAt(System.currentTimeMillis() + EXPIRE_MILLIS);
|
||||||
|
cache.put(key, progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recordSuccess(String key)
|
||||||
|
{
|
||||||
|
adjustProgress(key, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recordFailure(String key)
|
||||||
|
{
|
||||||
|
adjustProgress(key, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void adjustProgress(String key, boolean success)
|
||||||
|
{
|
||||||
|
ImportProgress progress = cache.get(key);
|
||||||
|
if (progress == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
progress.setProcessed(progress.getProcessed() + 1);
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
progress.setSuccess(progress.getSuccess() + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
progress.setFailure(progress.getFailure() + 1);
|
||||||
|
}
|
||||||
|
refreshMeta(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finishSuccess(String key, String message)
|
||||||
|
{
|
||||||
|
ImportProgress progress = cache.get(key);
|
||||||
|
if (progress == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
progress.setStatus("success");
|
||||||
|
progress.setMessage(message);
|
||||||
|
progress.setProcessed(progress.getTotal());
|
||||||
|
refreshMeta(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finishFailure(String key, String message)
|
||||||
|
{
|
||||||
|
ImportProgress progress = cache.get(key);
|
||||||
|
if (progress == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
progress.setStatus("failed");
|
||||||
|
progress.setMessage(message);
|
||||||
|
refreshMeta(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshMeta(ImportProgress progress)
|
||||||
|
{
|
||||||
|
progress.setLastUpdateTime(LocalDateTime.now());
|
||||||
|
progress.setExpireAt(System.currentTimeMillis() + EXPIRE_MILLIS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImportProgress snapshot(String key)
|
||||||
|
{
|
||||||
|
ImportProgress progress = cache.get(key);
|
||||||
|
if (progress == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (progress.isExpired(now))
|
||||||
|
{
|
||||||
|
cache.remove(key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return progress.copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear(String key)
|
||||||
|
{
|
||||||
|
if (key != null)
|
||||||
|
{
|
||||||
|
cache.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -77,7 +77,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
|
||||||
<select id="selectPausedAssessmentList" parameterType="Long" resultMap="PsyAssessmentResult">
|
<select id="selectPausedAssessmentList" parameterType="Long" resultMap="PsyAssessmentResult">
|
||||||
<include refid="selectAssessmentVo"/>
|
<include refid="selectAssessmentVo"/>
|
||||||
where a.user_id = #{userId} and a.status = '3'
|
<where>
|
||||||
|
a.status = '3'
|
||||||
|
<if test="userId != null">
|
||||||
|
and a.user_id = #{userId}
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
order by a.pause_time desc
|
order by a.pause_time desc
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
@ -349,4 +354,3 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
<result property="updateBy" column="update_by" />
|
<result property="updateBy" column="update_by" />
|
||||||
<result property="updateTime" column="update_time" />
|
<result property="updateTime" column="update_time" />
|
||||||
<result property="remark" column="remark" />
|
<result property="remark" column="remark" />
|
||||||
|
<result property="sourceType" column="source_type" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectScaleVo">
|
<sql id="selectScaleVo">
|
||||||
|
|
@ -34,7 +35,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
COALESCE(COUNT(i.item_id), 0) as item_count,
|
COALESCE(COUNT(i.item_id), 0) as item_count,
|
||||||
s.estimated_time, s.target_population,
|
s.estimated_time, s.target_population,
|
||||||
s.author, s.source, s.reference, s.status, s.sort_order, s.create_by, s.create_time,
|
s.author, s.source, s.reference, s.status, s.sort_order, s.create_by, s.create_time,
|
||||||
s.update_by, s.update_time, s.remark
|
s.update_by, s.update_time, s.remark,
|
||||||
|
'scale' as source_type
|
||||||
from psy_scale s
|
from psy_scale s
|
||||||
left join psy_scale_item i on s.scale_id = i.scale_id
|
left join psy_scale_item i on s.scale_id = i.scale_id
|
||||||
</sql>
|
</sql>
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
<result property="updateTime" column="update_time" />
|
<result property="updateTime" column="update_time" />
|
||||||
<result property="remark" column="remark" />
|
<result property="remark" column="remark" />
|
||||||
<result property="scaleName" column="scale_name" />
|
<result property="scaleName" column="scale_name" />
|
||||||
|
<result property="sourceType" column="source_type" />
|
||||||
<result property="deptName" column="dept_name" />
|
<result property="deptName" column="dept_name" />
|
||||||
<result property="roleName" column="role_name" />
|
<result property="roleName" column="role_name" />
|
||||||
<result property="userName" column="user_name" />
|
<result property="userName" column="user_name" />
|
||||||
|
|
@ -28,9 +29,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
<sql id="selectPermissionVo">
|
<sql id="selectPermissionVo">
|
||||||
select p.permission_id, p.scale_id, p.dept_id, p.role_id, p.user_id, p.class_name,
|
select p.permission_id, p.scale_id, p.dept_id, p.role_id, p.user_id, p.class_name,
|
||||||
p.start_time, p.end_time, p.status, p.create_by, p.create_time, p.update_by, p.update_time, p.remark,
|
p.start_time, p.end_time, p.status, p.create_by, p.create_time, p.update_by, p.update_time, p.remark,
|
||||||
s.scale_name, d.dept_name, r.role_name, u.user_name
|
case when p.scale_id < 0 then q.questionnaire_name else s.scale_name end as scale_name,
|
||||||
|
case when p.scale_id < 0 then 'questionnaire' else 'scale' end as source_type,
|
||||||
|
d.dept_name, r.role_name, u.user_name
|
||||||
from psy_scale_permission p
|
from psy_scale_permission p
|
||||||
left join psy_scale s on p.scale_id = s.scale_id
|
left join psy_scale s on p.scale_id = s.scale_id
|
||||||
|
left join psy_questionnaire q on q.questionnaire_id = -p.scale_id
|
||||||
left join sys_dept d on p.dept_id = d.dept_id
|
left join sys_dept d on p.dept_id = d.dept_id
|
||||||
left join sys_role r on p.role_id = r.role_id
|
left join sys_role r on p.role_id = r.role_id
|
||||||
left join sys_user u on p.user_id = u.user_id
|
left join sys_user u on p.user_id = u.user_id
|
||||||
|
|
@ -175,4 +179,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
delete from psy_scale_permission where scale_id = #{scaleId}
|
delete from psy_scale_permission where scale_id = #{scaleId}
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
|
<select id="selectAllScaleIdsWithPermission" resultType="Long">
|
||||||
|
select distinct scale_id
|
||||||
|
from psy_scale_permission
|
||||||
|
where status = '0'
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
<if test="crimeName != null and crimeName != ''">
|
<if test="crimeName != null and crimeName != ''">
|
||||||
AND p.crime_name like concat('%', #{crimeName}, '%')
|
AND p.crime_name like concat('%', #{crimeName}, '%')
|
||||||
</if>
|
</if>
|
||||||
|
<if test="status != null and status != ''">
|
||||||
|
AND u.status = #{status}
|
||||||
|
</if>
|
||||||
<if test="params.beginTime != null and params.beginTime != ''">
|
<if test="params.beginTime != null and params.beginTime != ''">
|
||||||
AND date_format(u.create_time,'%Y%m%d') >= date_format(#{params.beginTime},'%Y%m%d')
|
AND date_format(u.create_time,'%Y%m%d') >= date_format(#{params.beginTime},'%Y%m%d')
|
||||||
</if>
|
</if>
|
||||||
|
|
@ -211,8 +214,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
<select id="selectStudentProfileList" parameterType="com.ddnai.system.domain.psychology.PsyUserProfile" resultMap="PsyUserProfileResult">
|
<select id="selectStudentProfileList" parameterType="com.ddnai.system.domain.psychology.PsyUserProfile" resultMap="PsyUserProfileResult">
|
||||||
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar as user_avatar, u.phonenumber as phone, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by as user_create_by, u.create_time as user_create_time, u.remark as user_remark,
|
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar as user_avatar, u.phonenumber as phone, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by as user_create_by, u.create_time as user_create_time, u.remark as user_remark,
|
||||||
d.dept_name, d.leader,
|
d.dept_name, d.leader,
|
||||||
p.profile_id, p.profile_type, p.profile_data, p.avatar, p.id_card, p.birthday,
|
p.profile_id, p.profile_type, p.profile_data, p.avatar, p.id_card, p.birthday,
|
||||||
p.prison, p.prison_area, p.gender, p.nation, p.education_level, p.crime_name,
|
p.prison, p.prison_area, p.gender, p.nation, p.education_level, p.crime_name,
|
||||||
p.sentence_term, p.sentence_start_date, p.sentence_end_date, p.entry_date,
|
p.sentence_term, p.sentence_start_date, p.sentence_end_date, p.entry_date,
|
||||||
p.info_number, p.create_by, p.create_time, p.update_by, p.update_time, p.remark
|
p.info_number, p.create_by, p.create_time, p.update_by, p.update_time, p.remark
|
||||||
from sys_user u
|
from sys_user u
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,11 @@ export function myAssessmentList(query) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询暂停的测评列表
|
// 查询暂停的测评列表
|
||||||
export function pausedAssessmentList() {
|
export function pausedAssessmentList(params) {
|
||||||
return request({
|
return request({
|
||||||
url: '/psychology/assessment/pausedList',
|
url: '/psychology/assessment/pausedList',
|
||||||
method: 'get'
|
method: 'get',
|
||||||
|
params: params
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,3 +102,11 @@ export function delUserInProfile(userIds) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询档案导入进度
|
||||||
|
export function getProfileImportProgress() {
|
||||||
|
return request({
|
||||||
|
url: '/psychology/profile/importProgress',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,7 @@ export const dynamicRoutes = [
|
||||||
meta: {
|
meta: {
|
||||||
title: '心理测评管理',
|
title: '心理测评管理',
|
||||||
icon: 'chart',
|
icon: 'chart',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
// 量表管理
|
// 量表管理
|
||||||
|
|
@ -246,7 +246,7 @@ export const dynamicRoutes = [
|
||||||
meta: {
|
meta: {
|
||||||
title: '量表管理',
|
title: '量表管理',
|
||||||
icon: 'table',
|
icon: 'table',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 题目管理(隐藏菜单,通过量表管理页面进入)
|
// 题目管理(隐藏菜单,通过量表管理页面进入)
|
||||||
|
|
@ -257,7 +257,7 @@ export const dynamicRoutes = [
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
title: '题目管理',
|
title: '题目管理',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 因子管理(隐藏菜单,通过量表管理页面进入)
|
// 因子管理(隐藏菜单,通过量表管理页面进入)
|
||||||
|
|
@ -268,7 +268,7 @@ export const dynamicRoutes = [
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
title: '因子管理',
|
title: '因子管理',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 测评管理
|
// 测评管理
|
||||||
|
|
@ -279,7 +279,7 @@ export const dynamicRoutes = [
|
||||||
meta: {
|
meta: {
|
||||||
title: '测评管理',
|
title: '测评管理',
|
||||||
icon: 'edit',
|
icon: 'edit',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -289,7 +289,7 @@ export const dynamicRoutes = [
|
||||||
meta: {
|
meta: {
|
||||||
title: '全员测评分析',
|
title: '全员测评分析',
|
||||||
icon: 'chart',
|
icon: 'chart',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 开始测评
|
// 开始测评
|
||||||
|
|
@ -300,7 +300,7 @@ export const dynamicRoutes = [
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
title: '开始测评',
|
title: '开始测评',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 测评进行中
|
// 测评进行中
|
||||||
|
|
@ -311,7 +311,7 @@ export const dynamicRoutes = [
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
title: '测评中',
|
title: '测评中',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 测评报告
|
// 测评报告
|
||||||
|
|
@ -322,7 +322,7 @@ export const dynamicRoutes = [
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
title: '测评报告',
|
title: '测评报告',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 报告管理
|
// 报告管理
|
||||||
|
|
@ -333,7 +333,7 @@ export const dynamicRoutes = [
|
||||||
meta: {
|
meta: {
|
||||||
title: '报告管理',
|
title: '报告管理',
|
||||||
icon: 'document',
|
icon: 'document',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 报告详情
|
// 报告详情
|
||||||
|
|
@ -344,7 +344,7 @@ export const dynamicRoutes = [
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
title: '报告详情',
|
title: '报告详情',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 综合评估
|
// 综合评估
|
||||||
|
|
@ -355,7 +355,7 @@ export const dynamicRoutes = [
|
||||||
meta: {
|
meta: {
|
||||||
title: '综合评估',
|
title: '综合评估',
|
||||||
icon: 'chart',
|
icon: 'chart',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 量表权限管理
|
// 量表权限管理
|
||||||
|
|
@ -366,7 +366,7 @@ export const dynamicRoutes = [
|
||||||
meta: {
|
meta: {
|
||||||
title: '量表权限管理',
|
title: '量表权限管理',
|
||||||
icon: 'lock',
|
icon: 'lock',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 用户量表权限分配
|
// 用户量表权限分配
|
||||||
|
|
@ -377,7 +377,7 @@ export const dynamicRoutes = [
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
title: '分配量表权限',
|
title: '分配量表权限',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 解释配置
|
// 解释配置
|
||||||
|
|
@ -388,7 +388,7 @@ export const dynamicRoutes = [
|
||||||
meta: {
|
meta: {
|
||||||
title: '解释配置',
|
title: '解释配置',
|
||||||
icon: 'config',
|
icon: 'config',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 用户档案
|
// 用户档案
|
||||||
|
|
@ -399,7 +399,7 @@ export const dynamicRoutes = [
|
||||||
meta: {
|
meta: {
|
||||||
title: '用户档案',
|
title: '用户档案',
|
||||||
icon: 'user',
|
icon: 'user',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 自定义问卷
|
// 自定义问卷
|
||||||
|
|
@ -410,7 +410,7 @@ export const dynamicRoutes = [
|
||||||
meta: {
|
meta: {
|
||||||
title: '自定义问卷',
|
title: '自定义问卷',
|
||||||
icon: 'edit',
|
icon: 'edit',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 问卷开始答题
|
// 问卷开始答题
|
||||||
|
|
@ -443,7 +443,7 @@ export const dynamicRoutes = [
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
title: '问卷题目管理',
|
title: '问卷题目管理',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 主观题评分
|
// 主观题评分
|
||||||
|
|
@ -454,7 +454,7 @@ export const dynamicRoutes = [
|
||||||
meta: {
|
meta: {
|
||||||
title: '主观题评分',
|
title: '主观题评分',
|
||||||
icon: 'edit',
|
icon: 'edit',
|
||||||
roles: ['admin']
|
roles: ['admin', 'teacher']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,9 @@ service.interceptors.response.use(res => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
|
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
|
||||||
|
} else if (code === 403) {
|
||||||
|
// 403权限不足错误,静默处理,不显示提示
|
||||||
|
return Promise.reject('no_permission')
|
||||||
} else if (code === 500) {
|
} else if (code === 500) {
|
||||||
Message({ message: msg, type: 'error' })
|
Message({ message: msg, type: 'error' })
|
||||||
return Promise.reject(new Error(msg))
|
return Promise.reject(new Error(msg))
|
||||||
|
|
@ -116,6 +119,12 @@ service.interceptors.response.use(res => {
|
||||||
async error => {
|
async error => {
|
||||||
console.log('err' + error)
|
console.log('err' + error)
|
||||||
let { message } = error
|
let { message } = error
|
||||||
|
|
||||||
|
// 403权限错误,静默处理
|
||||||
|
if (error.response && error.response.status === 403) {
|
||||||
|
return Promise.reject('no_permission')
|
||||||
|
}
|
||||||
|
|
||||||
// 对于blob类型的错误响应,尝试解析错误信息
|
// 对于blob类型的错误响应,尝试解析错误信息
|
||||||
if (error.response && error.response.config &&
|
if (error.response && error.response.config &&
|
||||||
(error.response.config.responseType === 'blob' || error.response.config.responseType === 'arraybuffer') &&
|
(error.response.config.responseType === 'blob' || error.response.config.responseType === 'arraybuffer') &&
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
icon="el-icon-plus"
|
icon="el-icon-plus"
|
||||||
size="mini"
|
size="mini"
|
||||||
@click="handleStartAssessment"
|
@click="handleStartAssessment"
|
||||||
v-hasPermi="['psychology:assessment:add']"
|
v-hasPermi="['psychology:assessment:start']"
|
||||||
>开始测评</el-button>
|
>开始测评</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
|
|
@ -61,7 +61,11 @@
|
||||||
<el-table v-loading="loading" :data="assessmentList" @selection-change="handleSelectionChange">
|
<el-table v-loading="loading" :data="assessmentList" @selection-change="handleSelectionChange">
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<el-table-column label="序号" align="center" prop="assessmentId" width="80" />
|
<el-table-column label="序号" align="center" prop="assessmentId" width="80" />
|
||||||
<el-table-column label="量表ID" align="center" prop="scaleId" width="100" />
|
<el-table-column label="量表名称" align="center" prop="scaleName" min-width="150" :show-overflow-tooltip="true">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span>{{ scope.row.scaleName || (scope.row.scaleId ? ('量表#' + scope.row.scaleId) : '-') }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="被测评人" align="center" prop="assesseeName" width="120" />
|
<el-table-column label="被测评人" align="center" prop="assesseeName" width="120" />
|
||||||
<el-table-column label="开始时间" align="center" prop="startTime" width="180">
|
<el-table-column label="开始时间" align="center" prop="startTime" width="180">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
|
|
@ -147,7 +151,9 @@
|
||||||
<el-descriptions :column="2" border style="margin-bottom: 20px;">
|
<el-descriptions :column="2" border style="margin-bottom: 20px;">
|
||||||
<el-descriptions-item label="测评ID">{{ currentAssessment.assessmentId }}</el-descriptions-item>
|
<el-descriptions-item label="测评ID">{{ currentAssessment.assessmentId }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="被测评人">{{ currentAssessment.assesseeName }}</el-descriptions-item>
|
<el-descriptions-item label="被测评人">{{ currentAssessment.assesseeName }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="量表ID">{{ currentAssessment.scaleId }}</el-descriptions-item>
|
<el-descriptions-item label="量表名称">
|
||||||
|
{{ currentAssessment.scaleName || (currentAssessment.scaleId ? ('量表#' + currentAssessment.scaleId) : '-') }}
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="提交时间">
|
<el-descriptions-item label="提交时间">
|
||||||
<span v-if="currentAssessment.submitTime">{{ parseTime(currentAssessment.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
<span v-if="currentAssessment.submitTime">{{ parseTime(currentAssessment.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
|
|
@ -158,7 +164,6 @@
|
||||||
|
|
||||||
<el-divider content-position="left">答题详情</el-divider>
|
<el-divider content-position="left">答题详情</el-divider>
|
||||||
<el-table :data="answerDetailList" border style="width: 100%">
|
<el-table :data="answerDetailList" border style="width: 100%">
|
||||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
|
||||||
<el-table-column label="题目序号" prop="itemNumber" width="100" align="center" />
|
<el-table-column label="题目序号" prop="itemNumber" width="100" align="center" />
|
||||||
<el-table-column label="题目内容" prop="itemContent" min-width="200" :show-overflow-tooltip="true" />
|
<el-table-column label="题目内容" prop="itemContent" min-width="200" :show-overflow-tooltip="true" />
|
||||||
<el-table-column label="题目类型" prop="itemType" width="100" align="center">
|
<el-table-column label="题目类型" prop="itemType" width="100" align="center">
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,11 @@ export default {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
'form.profileId'(newVal) {
|
||||||
|
this.refreshPausedListByProfile(newVal)
|
||||||
|
}
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
// 检查URL参数中是否有scaleId和profileId/userId
|
// 检查URL参数中是否有scaleId和profileId/userId
|
||||||
const scaleId = this.$route.query.scaleId;
|
const scaleId = this.$route.query.scaleId;
|
||||||
|
|
@ -130,7 +135,8 @@ export default {
|
||||||
|
|
||||||
this.loadScales();
|
this.loadScales();
|
||||||
this.loadProfiles();
|
this.loadProfiles();
|
||||||
this.loadPaused();
|
const initialPausedUserId = this.targetUserId ? parseInt(this.targetUserId) : this.$store.getters.id;
|
||||||
|
this.loadPaused(initialPausedUserId);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 加载量表列表 */
|
/** 加载量表列表 */
|
||||||
|
|
@ -139,8 +145,9 @@ export default {
|
||||||
const userId = this.$store.getters.id;
|
const userId = this.$store.getters.id;
|
||||||
const roles = this.$store.getters.roles || [];
|
const roles = this.$store.getters.roles || [];
|
||||||
|
|
||||||
// 判断是否是管理员:userId === 1 或者 roles 中包含 'admin'
|
// 判断是否是管理员:只有admin角色才能看到所有量表
|
||||||
const isAdmin = userId === 1 || (roles && roles.includes('admin'));
|
// 其他角色(包括教师)需要通过权限表检查
|
||||||
|
const isAdmin = roles && roles.includes('admin');
|
||||||
|
|
||||||
// 如果是管理员,显示所有量表和问卷;否则只显示有权限的量表
|
// 如果是管理员,显示所有量表和问卷;否则只显示有权限的量表
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
|
|
@ -284,11 +291,30 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/** 加载暂停的测评 */
|
/** 加载暂停的测评 */
|
||||||
loadPaused() {
|
loadPaused(targetUserId) {
|
||||||
pausedAssessmentList().then(response => {
|
const loginUserId = this.$store.getters.id;
|
||||||
|
const params = {};
|
||||||
|
if (targetUserId && !isNaN(targetUserId) && Number(targetUserId) !== Number(loginUserId)) {
|
||||||
|
params.userId = targetUserId;
|
||||||
|
}
|
||||||
|
pausedAssessmentList(params).then(response => {
|
||||||
this.pausedList = response.data || [];
|
this.pausedList = response.data || [];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
refreshPausedListByProfile(profileId) {
|
||||||
|
if (!profileId) {
|
||||||
|
const fallbackUserId = this.targetUserId ? parseInt(this.targetUserId) : this.$store.getters.id;
|
||||||
|
this.loadPaused(fallbackUserId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const profile = this.profileList.find(p => p.profileId === profileId);
|
||||||
|
if (profile && profile.userId) {
|
||||||
|
this.loadPaused(profile.userId);
|
||||||
|
} else {
|
||||||
|
const fallbackUserId = this.targetUserId ? parseInt(this.targetUserId) : this.$store.getters.id;
|
||||||
|
this.loadPaused(fallbackUserId);
|
||||||
|
}
|
||||||
|
},
|
||||||
/** 获取用户档案验证规则(动态) */
|
/** 获取用户档案验证规则(动态) */
|
||||||
getProfileRules() {
|
getProfileRules() {
|
||||||
// 检查选中的是否是问卷
|
// 检查选中的是否是问卷
|
||||||
|
|
@ -374,6 +400,15 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否存在暂停记录
|
||||||
|
const pausedRecord = this.pausedList.find(item => item.scaleId === this.form.scaleId && item.status === '3');
|
||||||
|
if (pausedRecord) {
|
||||||
|
this.$modal.confirm('检测到该用户在该量表有暂停的测评,是否继续上一条记录?').then(() => {
|
||||||
|
this.handleContinue(pausedRecord);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 验证通过,创建测评记录
|
// 验证通过,创建测评记录
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
// 找到选中的用户档案
|
// 找到选中的用户档案
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,11 @@
|
||||||
size="small"
|
size="small"
|
||||||
@click="speakText(currentItem.itemContent)"
|
@click="speakText(currentItem.itemContent)"
|
||||||
:disabled="!isTtsSupported"
|
:disabled="!isTtsSupported"
|
||||||
class="tts-btn"
|
:class="['tts-btn', isSpeaking ? 'speaking' : '']"
|
||||||
title="朗读题干"
|
title="朗读题干"
|
||||||
>
|
>
|
||||||
<img :src="voiceIcon" alt="朗读题干" class="tts-icon" />
|
<i :class="isSpeaking ? 'el-icon-video-pause' : 'el-icon-service'"
|
||||||
|
style="font-size: 18px; color: #409EFF;"></i>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -55,10 +56,11 @@
|
||||||
size="mini"
|
size="mini"
|
||||||
@click="speakText(option.optionContent)"
|
@click="speakText(option.optionContent)"
|
||||||
:disabled="!isTtsSupported"
|
:disabled="!isTtsSupported"
|
||||||
class="option-tts-btn"
|
:class="['option-tts-btn', isSpeaking ? 'speaking' : '']"
|
||||||
title="朗读选项"
|
title="朗读选项"
|
||||||
>
|
>
|
||||||
<img :src="voiceIcon" alt="朗读选项" class="tts-icon" />
|
<i :class="isSpeaking ? 'el-icon-video-pause' : 'el-icon-service'"
|
||||||
|
style="font-size: 16px; color: #409EFF;"></i>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
|
|
@ -74,10 +76,11 @@
|
||||||
size="mini"
|
size="mini"
|
||||||
@click="speakText(option.optionContent)"
|
@click="speakText(option.optionContent)"
|
||||||
:disabled="!isTtsSupported"
|
:disabled="!isTtsSupported"
|
||||||
class="option-tts-btn"
|
:class="['option-tts-btn', isSpeaking ? 'speaking' : '']"
|
||||||
title="朗读选项"
|
title="朗读选项"
|
||||||
>
|
>
|
||||||
<img :src="voiceIcon" alt="朗读选项" class="tts-icon" />
|
<i :class="isSpeaking ? 'el-icon-video-pause' : 'el-icon-service'"
|
||||||
|
style="font-size: 16px; color: #409EFF;"></i>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-checkbox-group>
|
</el-checkbox-group>
|
||||||
|
|
@ -134,7 +137,8 @@ export default {
|
||||||
isTtsSupported: false,
|
isTtsSupported: false,
|
||||||
synth: null,
|
synth: null,
|
||||||
currentUtterance: null,
|
currentUtterance: null,
|
||||||
voiceIcon
|
voiceIcon,
|
||||||
|
isSpeaking: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -208,45 +212,62 @@ export default {
|
||||||
/** 朗读文本 */
|
/** 朗读文本 */
|
||||||
speakText(text) {
|
speakText(text) {
|
||||||
if (!this.isTtsSupported || !text || !text.trim()) {
|
if (!this.isTtsSupported || !text || !text.trim()) {
|
||||||
|
this.$message.warning('浏览器不支持语音播放功能');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果正在播放,则停止
|
||||||
|
if (this.isSpeaking) {
|
||||||
|
this.stopSpeaking();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.stopSpeaking();
|
this.stopSpeaking();
|
||||||
this.currentUtterance = new SpeechSynthesisUtterance(text.trim());
|
this.currentUtterance = new SpeechSynthesisUtterance(text.trim());
|
||||||
this.currentUtterance.lang = 'zh-CN';
|
this.currentUtterance.lang = 'zh-CN';
|
||||||
this.currentUtterance.volume = 1.0;
|
this.currentUtterance.volume = 1.0; // 最大音量
|
||||||
this.currentUtterance.rate = 1.0;
|
this.currentUtterance.rate = 0.9; // 稍慢一点,更清晰
|
||||||
this.currentUtterance.pitch = 1.0;
|
this.currentUtterance.pitch = 1.0;
|
||||||
|
|
||||||
|
// 获取可用的语音列表
|
||||||
const voices = this.synth.getVoices();
|
const voices = this.synth.getVoices();
|
||||||
const chineseVoice = voices.find(voice => voice.lang.includes('zh') || voice.lang.includes('CN'));
|
// 优先选择中文语音
|
||||||
|
const chineseVoice = voices.find(voice =>
|
||||||
|
voice.lang.includes('zh') ||
|
||||||
|
voice.lang.includes('CN') ||
|
||||||
|
voice.name.includes('中文') ||
|
||||||
|
voice.name.includes('Chinese')
|
||||||
|
);
|
||||||
if (chineseVoice) {
|
if (chineseVoice) {
|
||||||
this.currentUtterance.voice = chineseVoice;
|
this.currentUtterance.voice = chineseVoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasStarted = false;
|
|
||||||
this.currentUtterance.onstart = () => {
|
this.currentUtterance.onstart = () => {
|
||||||
hasStarted = true;
|
this.isSpeaking = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.currentUtterance.onend = () => {
|
||||||
|
this.isSpeaking = false;
|
||||||
|
};
|
||||||
|
|
||||||
this.currentUtterance.onerror = (event) => {
|
this.currentUtterance.onerror = (event) => {
|
||||||
console.error('TTS 错误:', event);
|
this.isSpeaking = false;
|
||||||
if (hasStarted) {
|
const errorType = event.error || '';
|
||||||
|
// 忽略正常的中断
|
||||||
|
const ignoredErrors = ['interrupted', 'canceled'];
|
||||||
|
if (ignoredErrors.includes(errorType)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const errorType = event.error || '';
|
console.error('TTS 错误:', event);
|
||||||
const seriousErrors = ['network-error', 'synthesis-failed', 'synthesis-unavailable', 'not-allowed'];
|
// 不再显示错误提示,改为静默失败
|
||||||
if (seriousErrors.includes(errorType)) {
|
|
||||||
this.$message.error('语音朗读失败:' + this.getErrorMessage(errorType));
|
|
||||||
} else if (errorType === 'text-too-long') {
|
|
||||||
this.$message.warning('文本过长,无法朗读');
|
|
||||||
} else if (errorType) {
|
|
||||||
this.$message.error('语音朗读失败');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 为了兼容移动端,需要在用户交互后立即调用
|
||||||
this.synth.speak(this.currentUtterance);
|
this.synth.speak(this.currentUtterance);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
this.isSpeaking = false;
|
||||||
console.error('调用 speak 失败:', error);
|
console.error('调用 speak 失败:', error);
|
||||||
this.$message.error('语音朗读失败:无法启动语音合成');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/** 停止朗读 */
|
/** 停止朗读 */
|
||||||
|
|
@ -255,6 +276,7 @@ export default {
|
||||||
this.synth.cancel();
|
this.synth.cancel();
|
||||||
}
|
}
|
||||||
this.currentUtterance = null;
|
this.currentUtterance = null;
|
||||||
|
this.isSpeaking = false;
|
||||||
},
|
},
|
||||||
/** 错误信息 */
|
/** 错误信息 */
|
||||||
getErrorMessage(errorType) {
|
getErrorMessage(errorType) {
|
||||||
|
|
@ -442,10 +464,26 @@ export default {
|
||||||
/** 退出 */
|
/** 退出 */
|
||||||
handleExit() {
|
handleExit() {
|
||||||
this.$modal.confirm('确定要退出测评吗?已答题目将会保存。').then(() => {
|
this.$modal.confirm('确定要退出测评吗?已答题目将会保存。').then(() => {
|
||||||
// 根据用户角色跳转到相应页面
|
this.loading = true;
|
||||||
const roles = this.$store.getters.roles || [];
|
// 先等待一小段时间,确保最后的答案保存请求发出
|
||||||
const isStudent = roles.some(role => role === 'student' || role.includes('学员'));
|
setTimeout(() => {
|
||||||
this.$router.push(isStudent ? '/student/tests' : '/psychology/assessment');
|
// 退出前先暂停测评,保存进度
|
||||||
|
pauseAssessment(this.assessmentId).then(() => {
|
||||||
|
this.loading = false;
|
||||||
|
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/assessment');
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('保存测评进度失败:', error);
|
||||||
|
this.loading = false;
|
||||||
|
// 即使保存失败也允许退出,因为答案已经自动保存了
|
||||||
|
const roles = this.$store.getters.roles || [];
|
||||||
|
const isStudent = roles.some(role => role === 'student' || role.includes('学员'));
|
||||||
|
this.$router.push(isStudent ? '/student/tests' : '/psychology/assessment');
|
||||||
|
});
|
||||||
|
}, 500); // 等待500ms,确保答案保存请求已发送
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/** 提交测评 */
|
/** 提交测评 */
|
||||||
|
|
@ -790,5 +828,37 @@ export default {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 语音播放时的动画效果 */
|
||||||
|
.tts-btn.speaking, .option-tts-btn.speaking {
|
||||||
|
animation: pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tts-btn .el-icon-video-pause,
|
||||||
|
.option-tts-btn .el-icon-video-pause {
|
||||||
|
color: #E6A23C !important;
|
||||||
|
animation: rotate 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,34 @@
|
||||||
<el-option
|
<el-option
|
||||||
v-for="scale in scaleList"
|
v-for="scale in scaleList"
|
||||||
:key="scale.scaleId"
|
:key="scale.scaleId"
|
||||||
:label="scale.scaleName"
|
:label="formatScaleLabel(scale)"
|
||||||
:value="scale.scaleId">
|
:value="scale.scaleId">
|
||||||
|
<span class="scale-option">
|
||||||
|
<el-tag
|
||||||
|
size="mini"
|
||||||
|
:type="scale.sourceType === 'questionnaire' ? 'warning' : 'primary'"
|
||||||
|
style="margin-right: 6px;">
|
||||||
|
{{ scale.sourceType === 'questionnaire' ? '问卷' : '量表' }}
|
||||||
|
</el-tag>
|
||||||
|
<span>{{ scale.scaleName }}</span>
|
||||||
|
</span>
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="用户名称" prop="userId">
|
<el-form-item label="用户名称" prop="userId">
|
||||||
<el-select v-model="queryParams.userId" placeholder="请选择用户" clearable filterable style="width: 200px">
|
<el-select
|
||||||
|
v-model="queryParams.userId"
|
||||||
|
placeholder="请选择用户"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
remote
|
||||||
|
:remote-method="searchUsers"
|
||||||
|
:loading="userSearchLoading"
|
||||||
|
style="width: 220px"
|
||||||
|
@focus="handleSelectFocus"
|
||||||
|
@visible-change="val => val && handleSelectFocus()">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="user in userList"
|
v-for="user in userOptions"
|
||||||
:key="user.userId"
|
:key="user.userId"
|
||||||
:label="user.nickName ? `${user.nickName}(${user.userName})` : user.userName"
|
:label="user.nickName ? `${user.nickName}(${user.userName})` : user.userName"
|
||||||
:value="Number(user.userId)">
|
:value="Number(user.userId)">
|
||||||
|
|
@ -45,7 +64,17 @@
|
||||||
|
|
||||||
<el-table v-loading="loading" :data="permissionList" @selection-change="handleSelectionChange">
|
<el-table v-loading="loading" :data="permissionList" @selection-change="handleSelectionChange">
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<el-table-column label="量表名称" align="center" prop="scaleName" width="200" />
|
<el-table-column label="量表/问卷" align="center" prop="scaleName" width="220">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag
|
||||||
|
size="mini"
|
||||||
|
:type="scope.row.sourceType === 'questionnaire' ? 'warning' : 'primary'"
|
||||||
|
style="margin-right: 6px;">
|
||||||
|
{{ scope.row.sourceType === 'questionnaire' ? '问卷' : '量表' }}
|
||||||
|
</el-tag>
|
||||||
|
<span>{{ scope.row.scaleName || '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="用户名称" align="center" prop="userNames" min-width="250">
|
<el-table-column label="用户名称" align="center" prop="userNames" min-width="250">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span v-if="scope.row.hasAllUsers">
|
<span v-if="scope.row.hasAllUsers">
|
||||||
|
|
@ -66,9 +95,10 @@
|
||||||
<span v-else style="color: #909399;">-</span>
|
<span v-else style="color: #909399;">-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="部门名称" align="center" prop="deptName" width="150" />
|
<!-- 隐藏部门、角色、班级列 -->
|
||||||
<el-table-column label="角色名称" align="center" prop="roleName" width="150" />
|
<!-- <el-table-column label="部门名称" align="center" prop="deptName" width="150" /> -->
|
||||||
<el-table-column label="班级名称" align="center" prop="className" width="150" />
|
<!-- <el-table-column label="角色名称" align="center" prop="roleName" width="150" /> -->
|
||||||
|
<!-- <el-table-column label="班级名称" align="center" prop="className" width="150" /> -->
|
||||||
<el-table-column label="开始时间" align="center" prop="startTime" width="180">
|
<el-table-column label="开始时间" align="center" prop="startTime" width="180">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||||
|
|
@ -102,39 +132,59 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 添加或修改权限对话框 -->
|
<!-- 添加或修改权限对话框 -->
|
||||||
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
|
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
|
||||||
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
|
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
|
||||||
<el-form-item label="量表" prop="scaleId">
|
<el-form-item label="量表" prop="scaleId">
|
||||||
<el-select v-model="form.scaleId" placeholder="请选择量表" style="width: 100%;" filterable>
|
<div style="border: 1px solid #DCDFE6; border-radius: 4px; padding: 10px; max-height: 300px; overflow-y: auto;">
|
||||||
<el-option
|
<el-input
|
||||||
v-for="scale in scaleList"
|
v-model="scaleSearchKeyword"
|
||||||
:key="scale.scaleId"
|
placeholder="请输入量表名称搜索"
|
||||||
:label="scale.scaleName"
|
clearable
|
||||||
:value="scale.scaleId">
|
size="small"
|
||||||
</el-option>
|
style="margin-bottom: 10px;"
|
||||||
</el-select>
|
prefix-icon="el-icon-search">
|
||||||
|
</el-input>
|
||||||
|
<el-table
|
||||||
|
:data="filteredScaleList"
|
||||||
|
@row-click="handleScaleRowClick"
|
||||||
|
highlight-current-row
|
||||||
|
max-height="250">
|
||||||
|
<el-table-column width="55" align="center">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-radio
|
||||||
|
:label="scope.row.scaleId"
|
||||||
|
v-model="form.scaleId"
|
||||||
|
@change="handleScaleChange(scope.row)">
|
||||||
|
<span></span>
|
||||||
|
</el-radio>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="类型" width="80" align="center">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag
|
||||||
|
size="mini"
|
||||||
|
:type="scope.row.sourceType === 'questionnaire' ? 'warning' : 'primary'">
|
||||||
|
{{ scope.row.sourceType === 'questionnaire' ? '问卷' : '量表' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="量表名称" prop="scaleName" :show-overflow-tooltip="true" />
|
||||||
|
<el-table-column label="量表编码" prop="scaleCode" width="150" />
|
||||||
|
<el-table-column label="题目数量" prop="itemCount" width="100" align="center" />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="用户" prop="userIds">
|
<el-form-item label="用户" prop="userIds">
|
||||||
<el-select
|
<el-input
|
||||||
v-model="form.userIds"
|
:value="selectedUserDisplay"
|
||||||
placeholder="请选择用户(可多选,留空表示所有用户)"
|
placeholder="点击选择用户(留空表示所有用户)"
|
||||||
clearable
|
readonly
|
||||||
filterable
|
|
||||||
multiple
|
|
||||||
reserve-keyword
|
|
||||||
:loading="userSearchLoading"
|
|
||||||
style="width: 100%;"
|
style="width: 100%;"
|
||||||
@focus="handleSelectFocus"
|
@focus="showUserSelectDialog = true">
|
||||||
@change="handleUserIdsChange">
|
<el-button slot="append" icon="el-icon-search" @click="showUserSelectDialog = true">选择</el-button>
|
||||||
<el-option
|
</el-input>
|
||||||
v-for="user in userOptions"
|
|
||||||
:key="user.userId"
|
|
||||||
:label="user.nickName ? `${user.nickName}(${user.userName})` : user.userName"
|
|
||||||
:value="user.userId">
|
|
||||||
</el-option>
|
|
||||||
</el-select>
|
|
||||||
<div style="margin-top: 5px; color: #909399; font-size: 12px;">
|
<div style="margin-top: 5px; color: #909399; font-size: 12px;">
|
||||||
提示:可输入姓名或账号搜索,支持多选。留空表示所有用户。
|
提示:留空表示所有用户。点击"选择"按钮可按信息编号、姓名、监区筛选用户。
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="开始时间" prop="startTime">
|
<el-form-item label="开始时间" prop="startTime">
|
||||||
|
|
@ -170,6 +220,74 @@
|
||||||
<el-button @click="cancel">取 消</el-button>
|
<el-button @click="cancel">取 消</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 用户选择对话框 -->
|
||||||
|
<el-dialog title="选择用户" :visible.sync="showUserSelectDialog" width="900px" append-to-body>
|
||||||
|
<el-form :model="userQueryParams" ref="userQueryForm" size="small" :inline="true">
|
||||||
|
<el-form-item label="信息编号" prop="infoNumber">
|
||||||
|
<el-input
|
||||||
|
v-model="userQueryParams.infoNumber"
|
||||||
|
placeholder="请输入信息编号"
|
||||||
|
clearable
|
||||||
|
@keyup.enter.native="handleUserQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="姓名" prop="userName">
|
||||||
|
<el-input
|
||||||
|
v-model="userQueryParams.userName"
|
||||||
|
placeholder="请输入姓名"
|
||||||
|
clearable
|
||||||
|
@keyup.enter.native="handleUserQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="监区" prop="prisonArea">
|
||||||
|
<el-select v-model="userQueryParams.prisonArea" placeholder="请选择监区" clearable filterable style="width: 200px">
|
||||||
|
<el-option
|
||||||
|
v-for="area in prisonAreaOptions"
|
||||||
|
:key="area"
|
||||||
|
:label="area"
|
||||||
|
:value="area">
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleUserQuery">搜索</el-button>
|
||||||
|
<el-button icon="el-icon-refresh" size="mini" @click="resetUserQuery">重置</el-button>
|
||||||
|
<el-button type="success" icon="el-icon-check" size="mini" @click="handleSelectAllUsers">全选所有用户</el-button>
|
||||||
|
<el-button type="warning" icon="el-icon-close" size="mini" @click="handleDeselectAllUsers">取消全选</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-table
|
||||||
|
ref="userTable"
|
||||||
|
:data="userSelectList"
|
||||||
|
@selection-change="handleUserSelectionChange"
|
||||||
|
@select-all="handleUserSelectAll"
|
||||||
|
height="400px"
|
||||||
|
v-loading="userSelectLoading">
|
||||||
|
<el-table-column type="selection" width="55"></el-table-column>
|
||||||
|
<el-table-column label="信息编号" prop="infoNumber" width="120" />
|
||||||
|
<el-table-column label="姓名" prop="userName" :show-overflow-tooltip="true" />
|
||||||
|
<el-table-column label="账号" prop="userAccount" :show-overflow-tooltip="true" />
|
||||||
|
<el-table-column label="监区" prop="prisonArea" :show-overflow-tooltip="true" />
|
||||||
|
<el-table-column label="状态" align="center" prop="status" width="80">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag v-if="scope.row.status === '0'" type="success" size="small">正常</el-tag>
|
||||||
|
<el-tag v-else type="danger" size="small">停用</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<pagination
|
||||||
|
v-show="userSelectTotal > 0"
|
||||||
|
:total="userSelectTotal"
|
||||||
|
:page.sync="userQueryParams.pageNum"
|
||||||
|
:limit.sync="userQueryParams.pageSize"
|
||||||
|
@pagination="getUserSelectList"
|
||||||
|
/>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="handleConfirmUserSelect">确 定</el-button>
|
||||||
|
<el-button @click="showUserSelectDialog = false">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -179,6 +297,7 @@ import { listScale } from "@/api/psychology/scale";
|
||||||
import { allocatedUserList } from "@/api/system/role";
|
import { allocatedUserList } from "@/api/system/role";
|
||||||
import { listRole } from "@/api/system/role";
|
import { listRole } from "@/api/system/role";
|
||||||
import { getUser } from "@/api/system/user";
|
import { getUser } from "@/api/system/user";
|
||||||
|
import { listProfile } from "@/api/psychology/profile";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PsyScalePermission",
|
name: "PsyScalePermission",
|
||||||
|
|
@ -200,6 +319,8 @@ export default {
|
||||||
permissionList: [],
|
permissionList: [],
|
||||||
// 量表列表
|
// 量表列表
|
||||||
scaleList: [],
|
scaleList: [],
|
||||||
|
// 量表搜索关键词
|
||||||
|
scaleSearchKeyword: "",
|
||||||
// 用户列表(用于下拉框显示)
|
// 用户列表(用于下拉框显示)
|
||||||
userOptions: [],
|
userOptions: [],
|
||||||
// 用户搜索加载状态
|
// 用户搜索加载状态
|
||||||
|
|
@ -210,6 +331,26 @@ export default {
|
||||||
title: "",
|
title: "",
|
||||||
// 是否显示弹出层
|
// 是否显示弹出层
|
||||||
open: false,
|
open: false,
|
||||||
|
// 是否显示用户选择对话框
|
||||||
|
showUserSelectDialog: false,
|
||||||
|
// 用户选择相关
|
||||||
|
userSelectList: [],
|
||||||
|
userSelectTotal: 0,
|
||||||
|
userSelectLoading: false,
|
||||||
|
selectedUserIds: [],
|
||||||
|
cachedFilteredUsers: [],
|
||||||
|
updatingSelection: false,
|
||||||
|
// 监区选项
|
||||||
|
prisonAreaOptions: [],
|
||||||
|
// 用户查询参数
|
||||||
|
userQueryParams: {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 8,
|
||||||
|
infoNumber: undefined,
|
||||||
|
userName: undefined,
|
||||||
|
prisonArea: undefined,
|
||||||
|
status: '0'
|
||||||
|
},
|
||||||
// 查询参数
|
// 查询参数
|
||||||
queryParams: {
|
queryParams: {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
|
|
@ -231,12 +372,71 @@ export default {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
// 过滤后的量表列表
|
||||||
|
filteredScaleList() {
|
||||||
|
if (!this.scaleSearchKeyword) {
|
||||||
|
return this.scaleList;
|
||||||
|
}
|
||||||
|
const keyword = this.scaleSearchKeyword.toLowerCase();
|
||||||
|
return this.scaleList.filter(scale => {
|
||||||
|
const scaleName = (scale.scaleName || '').toLowerCase();
|
||||||
|
const scaleCode = (scale.scaleCode || '').toLowerCase();
|
||||||
|
return scaleName.includes(keyword) || scaleCode.includes(keyword);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 选中用户的显示文本
|
||||||
|
selectedUserDisplay() {
|
||||||
|
const userIds = this.form.userIds || [];
|
||||||
|
if (userIds.length === 0) {
|
||||||
|
return "";
|
||||||
|
} else if (userIds.length === 1) {
|
||||||
|
const user = this.userOptions.find(u => u.userId === userIds[0]);
|
||||||
|
return user ? (user.nickName ? `${user.nickName}(${user.userName})` : user.userName) : "";
|
||||||
|
} else {
|
||||||
|
return `已选择 ${userIds.length} 个用户`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 监听用户选择对话框打开
|
||||||
|
showUserSelectDialog(val) {
|
||||||
|
if (val) {
|
||||||
|
// 打开对话框时加载用户列表
|
||||||
|
this.resetUserQuery();
|
||||||
|
this.selectedUserIds = [...(this.form.userIds || [])];
|
||||||
|
this.getUserSelectList().then(() => {
|
||||||
|
// 确保已选中的用户被选中
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.form.userIds && this.form.userIds.length > 0 && this.$refs.userTable) {
|
||||||
|
this.form.userIds.forEach(userId => {
|
||||||
|
const row = this.userSelectList.find(u => u.userId === userId);
|
||||||
|
if (row) {
|
||||||
|
this.$refs.userTable.toggleRowSelection(row, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.cachedFilteredUsers = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getList();
|
this.getList();
|
||||||
this.loadScales();
|
this.loadScales();
|
||||||
this.loadUsers();
|
this.loadUsers();
|
||||||
|
this.loadPrisonAreaOptions();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
formatScaleLabel(scale) {
|
||||||
|
if (!scale) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
const prefix = scale.sourceType === 'questionnaire' ? '[问卷]' : '[量表]'
|
||||||
|
return `${prefix} ${scale.scaleName || ''}`
|
||||||
|
},
|
||||||
/** 查询权限列表 */
|
/** 查询权限列表 */
|
||||||
getList() {
|
getList() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
@ -324,11 +524,16 @@ export default {
|
||||||
},
|
},
|
||||||
/** 加载量表列表(显示所有量表,不包含问卷) */
|
/** 加载量表列表(显示所有量表,不包含问卷) */
|
||||||
loadScales() {
|
loadScales() {
|
||||||
// 不按状态过滤,并一次性拉取足够数量,确保所有量表都能配置权限
|
// 不按状态过滤,并一次性拉取足够数量,确保所有量表/问卷都能配置权限
|
||||||
listScale({ includeQuestionnaire: false, pageNum: 1, pageSize: 1000 }).then(response => {
|
listScale({ includeQuestionnaire: true, pageNum: 1, pageSize: 1000 }).then(response => {
|
||||||
// 过滤掉问卷,只保留量表
|
this.scaleList = (response.rows || []).filter(scale => {
|
||||||
this.scaleList = (response.rows || [])
|
if (!scale) return false
|
||||||
.filter(scale => !scale.sourceType || scale.sourceType === 'scale');
|
// 问卷需要有题目
|
||||||
|
if (scale.sourceType === 'questionnaire') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/** 加载用户列表(只加载学员角色的用户)- 改为远程搜索模式,不预加载 */
|
/** 加载用户列表(只加载学员角色的用户)- 改为远程搜索模式,不预加载 */
|
||||||
|
|
@ -345,6 +550,219 @@ export default {
|
||||||
console.error("获取学员角色ID失败:", error);
|
console.error("获取学员角色ID失败:", error);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/** 加载监区下拉选项 */
|
||||||
|
loadPrisonAreaOptions() {
|
||||||
|
listProfile({ pageNum: 1, pageSize: 10000 }).then(response => {
|
||||||
|
const rows = response.rows || [];
|
||||||
|
const areaSet = new Set();
|
||||||
|
rows.forEach(profile => {
|
||||||
|
if (profile && profile.prisonArea) {
|
||||||
|
areaSet.add(profile.prisonArea);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.prisonAreaOptions = Array.from(areaSet);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("加载监区列表失败:", error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 量表行点击 */
|
||||||
|
handleScaleRowClick(row) {
|
||||||
|
this.form.scaleId = row.scaleId;
|
||||||
|
},
|
||||||
|
/** 量表选择改变 */
|
||||||
|
handleScaleChange(scale) {
|
||||||
|
this.form.scaleId = scale.scaleId;
|
||||||
|
},
|
||||||
|
transformProfileToSelectableUser(profile) {
|
||||||
|
return {
|
||||||
|
userId: profile.userId,
|
||||||
|
infoNumber: profile.infoNumber,
|
||||||
|
userName: profile.userName,
|
||||||
|
userAccount: profile.infoNumber,
|
||||||
|
prisonArea: profile.prisonArea || profile.deptName || "-",
|
||||||
|
status: profile.status || '0'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/** 获取用户选择列表 */
|
||||||
|
getUserSelectList() {
|
||||||
|
this.userSelectLoading = true;
|
||||||
|
// 使用用户档案接口查询,支持信息编号、姓名、监区筛选
|
||||||
|
const query = {
|
||||||
|
pageNum: this.userQueryParams.pageNum,
|
||||||
|
pageSize: this.userQueryParams.pageSize,
|
||||||
|
infoNumber: this.userQueryParams.infoNumber,
|
||||||
|
userName: this.userQueryParams.userName,
|
||||||
|
prisonArea: this.userQueryParams.prisonArea,
|
||||||
|
status: this.userQueryParams.status
|
||||||
|
};
|
||||||
|
return listProfile(query).then(response => {
|
||||||
|
this.userSelectList = (response.rows || []).map(profile => this.transformProfileToSelectableUser(profile));
|
||||||
|
this.userSelectTotal = response.total || 0;
|
||||||
|
this.userSelectLoading = false;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.restoreUserSelection();
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}).catch(() => {
|
||||||
|
this.userSelectLoading = false;
|
||||||
|
return Promise.reject();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 用户查询 */
|
||||||
|
handleUserQuery() {
|
||||||
|
this.userQueryParams.pageNum = 1;
|
||||||
|
this.cachedFilteredUsers = [];
|
||||||
|
this.getUserSelectList();
|
||||||
|
},
|
||||||
|
/** 重置用户查询 */
|
||||||
|
resetUserQuery() {
|
||||||
|
this.resetForm("userQueryForm");
|
||||||
|
this.userQueryParams = {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 8,
|
||||||
|
infoNumber: undefined,
|
||||||
|
userName: undefined,
|
||||||
|
prisonArea: undefined,
|
||||||
|
status: '0'
|
||||||
|
};
|
||||||
|
this.handleUserQuery();
|
||||||
|
},
|
||||||
|
/** 用户选择改变 */
|
||||||
|
handleUserSelectionChange(selection) {
|
||||||
|
if (this.updatingSelection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentPageIds = this.userSelectList.map(item => item.userId);
|
||||||
|
// 移除当前页所有用户,再添加选中的,保证跨页累加
|
||||||
|
this.selectedUserIds = this.selectedUserIds.filter(id => !currentPageIds.includes(id));
|
||||||
|
selection.forEach(item => {
|
||||||
|
if (!this.selectedUserIds.includes(item.userId)) {
|
||||||
|
this.selectedUserIds.push(item.userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async handleUserSelectAll(selection) {
|
||||||
|
if (selection.length === 0) {
|
||||||
|
// 取消全选时,清除当前页的选择
|
||||||
|
const currentPageIds = this.userSelectList.map(item => item.userId);
|
||||||
|
this.selectedUserIds = this.selectedUserIds.filter(id => !currentPageIds.includes(id));
|
||||||
|
if (currentPageIds.length === 0) {
|
||||||
|
this.selectedUserIds = [];
|
||||||
|
}
|
||||||
|
this.cachedFilteredUsers = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selection.length === this.userSelectList.length && this.userSelectList.length > 0) {
|
||||||
|
await this.selectAllUsersUnderCurrentFilter();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchAllUsersUnderCurrentFilter() {
|
||||||
|
const pageSize = 500;
|
||||||
|
let pageNum = 1;
|
||||||
|
let total = 0;
|
||||||
|
const allUsers = [];
|
||||||
|
const baseParams = {
|
||||||
|
infoNumber: this.userQueryParams.infoNumber,
|
||||||
|
userName: this.userQueryParams.userName,
|
||||||
|
prisonArea: this.userQueryParams.prisonArea,
|
||||||
|
status: this.userQueryParams.status
|
||||||
|
};
|
||||||
|
while (true) {
|
||||||
|
const response = await listProfile({
|
||||||
|
...baseParams,
|
||||||
|
pageNum,
|
||||||
|
pageSize
|
||||||
|
});
|
||||||
|
const rows = response.rows || [];
|
||||||
|
if (response.total !== undefined && response.total !== null) {
|
||||||
|
total = response.total;
|
||||||
|
} else if (total === 0 && rows.length > 0) {
|
||||||
|
total = rows.length;
|
||||||
|
}
|
||||||
|
allUsers.push(...rows.map(profile => this.transformProfileToSelectableUser(profile)));
|
||||||
|
if (rows.length < pageSize || (total > 0 && allUsers.length >= total)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pageNum += 1;
|
||||||
|
}
|
||||||
|
return allUsers;
|
||||||
|
},
|
||||||
|
async selectAllUsersUnderCurrentFilter() {
|
||||||
|
try {
|
||||||
|
this.userSelectLoading = true;
|
||||||
|
const users = await this.fetchAllUsersUnderCurrentFilter();
|
||||||
|
if (!users.length) {
|
||||||
|
this.selectedUserIds = [];
|
||||||
|
this.cachedFilteredUsers = [];
|
||||||
|
this.$message.warning("当前筛选下没有可选用户");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.cachedFilteredUsers = users;
|
||||||
|
const allIds = users
|
||||||
|
.map(user => user.userId)
|
||||||
|
.filter(userId => userId !== undefined && userId !== null);
|
||||||
|
this.selectedUserIds = Array.from(new Set(allIds));
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.restoreUserSelection();
|
||||||
|
});
|
||||||
|
this.$message.success(`已选择当前筛选下的 ${this.selectedUserIds.length} 名用户`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("批量选择用户失败:", error);
|
||||||
|
this.$message.error("批量选择用户失败,请稍后重试");
|
||||||
|
} finally {
|
||||||
|
this.userSelectLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 确认用户选择 */
|
||||||
|
handleConfirmUserSelect() {
|
||||||
|
// 更新 form.userIds
|
||||||
|
this.form.userIds = [...this.selectedUserIds];
|
||||||
|
// 确保选中的用户在 userOptions 中
|
||||||
|
this.selectedUserIds.forEach(userId => {
|
||||||
|
const user =
|
||||||
|
this.userSelectList.find(u => u.userId === userId) ||
|
||||||
|
this.cachedFilteredUsers.find(u => u.userId === userId);
|
||||||
|
if (user && !this.userOptions.find(u => u.userId === userId)) {
|
||||||
|
this.userOptions.push({
|
||||||
|
userId: user.userId,
|
||||||
|
userName: user.userAccount || user.userName,
|
||||||
|
nickName: user.userName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.showUserSelectDialog = false;
|
||||||
|
this.cachedFilteredUsers = [];
|
||||||
|
},
|
||||||
|
restoreUserSelection() {
|
||||||
|
if (!this.$refs.userTable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.updatingSelection = true;
|
||||||
|
this.$refs.userTable.clearSelection();
|
||||||
|
this.userSelectList.forEach(row => {
|
||||||
|
if (this.selectedUserIds.includes(row.userId)) {
|
||||||
|
this.$refs.userTable.toggleRowSelection(row, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updatingSelection = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 全选所有用户按钮点击事件 */
|
||||||
|
handleSelectAllUsers() {
|
||||||
|
this.selectAllUsersUnderCurrentFilter();
|
||||||
|
},
|
||||||
|
/** 取消全选按钮点击事件 */
|
||||||
|
handleDeselectAllUsers() {
|
||||||
|
this.selectedUserIds = [];
|
||||||
|
this.cachedFilteredUsers = [];
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs.userTable) {
|
||||||
|
this.$refs.userTable.clearSelection();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.$message.success("已取消所有选择");
|
||||||
|
},
|
||||||
/** 搜索用户(远程搜索) */
|
/** 搜索用户(远程搜索) */
|
||||||
searchUsers(keyword) {
|
searchUsers(keyword) {
|
||||||
if (!this.studentRoleId) {
|
if (!this.studentRoleId) {
|
||||||
|
|
@ -545,6 +963,9 @@ export default {
|
||||||
_permissionIds: undefined, // 存储所有相关的权限ID(用于编辑时删除)
|
_permissionIds: undefined, // 存储所有相关的权限ID(用于编辑时删除)
|
||||||
_groupKey: undefined // 分组key
|
_groupKey: undefined // 分组key
|
||||||
};
|
};
|
||||||
|
this.scaleSearchKeyword = "";
|
||||||
|
this.selectedUserIds = [];
|
||||||
|
this.cachedFilteredUsers = [];
|
||||||
this.resetForm("form");
|
this.resetForm("form");
|
||||||
},
|
},
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
|
|
@ -574,6 +995,7 @@ export default {
|
||||||
/** 新增按钮操作 */
|
/** 新增按钮操作 */
|
||||||
handleAdd() {
|
handleAdd() {
|
||||||
this.reset();
|
this.reset();
|
||||||
|
this.scaleSearchKeyword = "";
|
||||||
// 确保 userOptions 有初始数据
|
// 确保 userOptions 有初始数据
|
||||||
if (this.userOptions.length === 0) {
|
if (this.userOptions.length === 0) {
|
||||||
this.searchUsers("");
|
this.searchUsers("");
|
||||||
|
|
@ -584,6 +1006,7 @@ export default {
|
||||||
/** 修改按钮操作 */
|
/** 修改按钮操作 */
|
||||||
handleUpdate(row) {
|
handleUpdate(row) {
|
||||||
this.reset();
|
this.reset();
|
||||||
|
this.scaleSearchKeyword = "";
|
||||||
// 汇总后的记录包含多个权限ID,需要加载所有相关的权限记录
|
// 汇总后的记录包含多个权限ID,需要加载所有相关的权限记录
|
||||||
const permissionIds = row.permissionIds || (row.permissionId ? [row.permissionId] : []);
|
const permissionIds = row.permissionIds || (row.permissionId ? [row.permissionId] : []);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,6 @@
|
||||||
@keyup.enter.native="handleQuery"
|
@keyup.enter.native="handleQuery"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="监狱" prop="prison">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.prison"
|
|
||||||
placeholder="请输入监狱名称"
|
|
||||||
clearable
|
|
||||||
@keyup.enter.native="handleQuery"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="监区" prop="prisonArea">
|
<el-form-item label="监区" prop="prisonArea">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.prisonArea"
|
v-model="queryParams.prisonArea"
|
||||||
|
|
@ -61,14 +53,6 @@
|
||||||
<el-option label="其他" value="其他" />
|
<el-option label="其他" value="其他" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="罪名" prop="crimeName">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.crimeName"
|
|
||||||
placeholder="请输入罪名"
|
|
||||||
clearable
|
|
||||||
@keyup.enter.native="handleQuery"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
@ -126,6 +110,11 @@
|
||||||
<span v-else>{{ scope.row.gender || '-' }}</span>
|
<span v-else>{{ scope.row.gender || '-' }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="出生日期" align="center" prop="birthday" width="120">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span>{{ parseTime(scope.row.birthday, '{y}-{m}-{d}') || '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="民族" align="center" prop="nation" width="80">
|
<el-table-column label="民族" align="center" prop="nation" width="80">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span>{{ scope.row.nation || '-' }}</span>
|
<span>{{ scope.row.nation || '-' }}</span>
|
||||||
|
|
@ -136,11 +125,6 @@
|
||||||
<span>{{ scope.row.educationLevel || '-' }}</span>
|
<span>{{ scope.row.educationLevel || '-' }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="出生日期" align="center" prop="birthday" width="120">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<span>{{ parseTime(scope.row.birthday, '{y}-{m}-{d}') || '-' }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="罪名" align="center" prop="crimeName" width="120">
|
<el-table-column label="罪名" align="center" prop="crimeName" width="120">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span>{{ scope.row.crimeName || '-' }}</span>
|
<span>{{ scope.row.crimeName || '-' }}</span>
|
||||||
|
|
@ -168,9 +152,9 @@
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="状态" align="center" prop="status" width="120">
|
<el-table-column label="状态" align="center" prop="status" width="120">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="scope.row.status"
|
v-model="scope.row.status"
|
||||||
size="mini"
|
size="mini"
|
||||||
@change="handleStatusChange(scope.row)"
|
@change="handleStatusChange(scope.row)"
|
||||||
placeholder="请选择状态"
|
placeholder="请选择状态"
|
||||||
style="width: 100px;"
|
style="width: 100px;"
|
||||||
|
|
@ -258,12 +242,23 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="民族" prop="nation">
|
<el-form-item label="出生日期" prop="birthday">
|
||||||
<el-input v-model="form.nation" placeholder="请输入民族" />
|
<el-date-picker
|
||||||
|
v-model="form.birthday"
|
||||||
|
type="date"
|
||||||
|
placeholder="选择出生日期"
|
||||||
|
value-format="yyyy-MM-dd"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row>
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="民族" prop="nation">
|
||||||
|
<el-input v-model="form.nation" placeholder="请输入民族" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="文化程度" prop="educationLevel">
|
<el-form-item label="文化程度" prop="educationLevel">
|
||||||
<el-select v-model="form.educationLevel" placeholder="请选择文化程度">
|
<el-select v-model="form.educationLevel" placeholder="请选择文化程度">
|
||||||
|
|
@ -279,20 +274,14 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="出生日期" prop="birthday">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="form.birthday"
|
|
||||||
type="date"
|
|
||||||
placeholder="选择出生日期"
|
|
||||||
value-format="yyyy-MM-dd"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="24">
|
<el-col :span="12">
|
||||||
|
<el-form-item label="监狱" prop="prison" v-if="false">
|
||||||
|
<el-input v-model="form.prison" placeholder="请输入监狱名称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
<el-form-item label="罪名" prop="crimeName">
|
<el-form-item label="罪名" prop="crimeName">
|
||||||
<el-input v-model="form.crimeName" placeholder="请输入罪名" />
|
<el-input v-model="form.crimeName" placeholder="请输入罪名" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
@ -434,28 +423,42 @@
|
||||||
|
|
||||||
<!-- 用户档案导入对话框 -->
|
<!-- 用户档案导入对话框 -->
|
||||||
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
|
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
|
||||||
<el-upload ref="upload" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :on-error="handleFileError" :auto-upload="false" drag>
|
<el-upload
|
||||||
|
ref="upload"
|
||||||
|
:limit="1"
|
||||||
|
accept=".xlsx, .xls"
|
||||||
|
:headers="upload.headers"
|
||||||
|
:action="upload.url + '?updateSupport=' + upload.updateSupport"
|
||||||
|
:disabled="upload.isUploading"
|
||||||
|
:on-progress="handleFileUploadProgress"
|
||||||
|
:on-success="handleFileSuccess"
|
||||||
|
:on-error="handleFileError"
|
||||||
|
:auto-upload="false"
|
||||||
|
drag>
|
||||||
<i class="el-icon-upload"></i>
|
<i class="el-icon-upload"></i>
|
||||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
<div class="el-upload__tip text-center" slot="tip">
|
<div class="el-upload__tip text-center" slot="tip">
|
||||||
<div class="el-upload__tip" slot="tip">
|
|
||||||
<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的用户档案数据
|
|
||||||
</div>
|
|
||||||
<span>仅允许导入xls、xlsx格式文件。</span>
|
<span>仅允许导入xls、xlsx格式文件。</span>
|
||||||
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
|
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="upload.showProgress" class="upload-progress">
|
||||||
|
<el-progress :percentage="upload.progress"></el-progress>
|
||||||
|
<div class="upload-progress__text">{{ upload.statusText }}</div>
|
||||||
|
</div>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
<div slot="footer" class="dialog-footer">
|
<div slot="footer" class="dialog-footer">
|
||||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
<el-button type="primary" :loading="upload.isUploading" @click="submitFileForm">确 定</el-button>
|
||||||
<el-button @click="upload.open = false">取 消</el-button>
|
<el-button :disabled="upload.isUploading" @click="upload.open = false">取 消</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { listStudentProfile, getProfile, getProfileByUserId, delProfile, addProfile, updateProfile, getUserInfo, addUserInProfile, getUserInfoById, updateUserInProfile, delUserInProfile } from "@/api/psychology/profile"
|
import { listStudentProfile, getProfile, getProfileByUserId, delProfile, addProfile, updateProfile, getUserInfo, addUserInProfile, getUserInfoById, updateUserInProfile, delUserInProfile, getProfileImportProgress } from "@/api/psychology/profile"
|
||||||
import { deptTreeSelect } from "@/api/system/user"
|
import { deptTreeSelect } from "@/api/system/user"
|
||||||
|
import { allocatedUserList } from "@/api/system/role"
|
||||||
|
import { listRole } from "@/api/system/role"
|
||||||
import { getToken } from "@/utils/auth"
|
import { getToken } from "@/utils/auth"
|
||||||
import Treeselect from "@riophae/vue-treeselect"
|
import Treeselect from "@riophae/vue-treeselect"
|
||||||
import "@riophae/vue-treeselect/dist/vue-treeselect.css"
|
import "@riophae/vue-treeselect/dist/vue-treeselect.css"
|
||||||
|
|
@ -496,6 +499,11 @@ export default {
|
||||||
postOptions: [],
|
postOptions: [],
|
||||||
// 角色选项
|
// 角色选项
|
||||||
roleOptions: [],
|
roleOptions: [],
|
||||||
|
// 学员角色ID(用于只允许删除学员用户等逻辑)
|
||||||
|
studentRoleId: undefined,
|
||||||
|
// 监狱/监区筛选项
|
||||||
|
filterPrisonOptions: [],
|
||||||
|
filterPrisonAreaOptions: [],
|
||||||
// 默认密码
|
// 默认密码
|
||||||
initPassword: undefined,
|
initPassword: undefined,
|
||||||
// 用户表单参数
|
// 用户表单参数
|
||||||
|
|
@ -513,7 +521,9 @@ export default {
|
||||||
educationLevel: undefined,
|
educationLevel: undefined,
|
||||||
crimeName: undefined,
|
crimeName: undefined,
|
||||||
userId: undefined,
|
userId: undefined,
|
||||||
deptId: undefined
|
deptId: undefined,
|
||||||
|
status: undefined,
|
||||||
|
idCard: undefined
|
||||||
},
|
},
|
||||||
// 表单参数
|
// 表单参数
|
||||||
form: {},
|
form: {},
|
||||||
|
|
@ -523,11 +533,12 @@ export default {
|
||||||
{ required: true, message: "档案类型不能为空", trigger: "change" }
|
{ required: true, message: "档案类型不能为空", trigger: "change" }
|
||||||
],
|
],
|
||||||
infoNumber: [
|
infoNumber: [
|
||||||
|
{ required: true, message: "信息编号不能为空", trigger: "blur" },
|
||||||
{ pattern: /^\d+$/, message: "信息编号只能输入数字", trigger: "blur" }
|
{ pattern: /^\d+$/, message: "信息编号只能输入数字", trigger: "blur" }
|
||||||
],
|
],
|
||||||
userName: [
|
userName: [
|
||||||
{ required: true, message: "罪犯姓名不能为空", trigger: "blur" },
|
{ required: true, message: "罪犯姓名不能为空", trigger: "blur" },
|
||||||
{ pattern: /^[\u4e00-\u9fa5]+$/, message: "姓名只能输入汉字", trigger: "blur" }
|
{ pattern: /^[\u4e00-\u9fa5\d]+$/, message: "姓名只能输入汉字和数字", trigger: "blur" }
|
||||||
],
|
],
|
||||||
prisonArea: [
|
prisonArea: [
|
||||||
{ required: true, message: "监区不能为空", trigger: "blur" }
|
{ required: true, message: "监区不能为空", trigger: "blur" }
|
||||||
|
|
@ -574,16 +585,27 @@ export default {
|
||||||
// 设置上传的请求头部
|
// 设置上传的请求头部
|
||||||
headers: { Authorization: "Bearer " + getToken() },
|
headers: { Authorization: "Bearer " + getToken() },
|
||||||
// 上传的地址
|
// 上传的地址
|
||||||
url: process.env.VUE_APP_BASE_API + "/psychology/profile/importData"
|
url: process.env.VUE_APP_BASE_API + "/psychology/profile/importData",
|
||||||
}
|
// 进度相关
|
||||||
|
showProgress: false,
|
||||||
|
progress: 0,
|
||||||
|
statusText: "",
|
||||||
|
stage: "idle"
|
||||||
|
},
|
||||||
|
importProgressTimer: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.stopImportProgressPolling()
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getList()
|
this.getList()
|
||||||
this.getDeptTree()
|
this.getDeptTree()
|
||||||
this.getConfigKey("sys.user.initPassword").then(response => {
|
this.getConfigKey("sys.user.initPassword").then(response => {
|
||||||
this.initPassword = response.msg
|
this.initPassword = response.msg
|
||||||
})
|
})
|
||||||
|
// 初始化学员角色ID,用于删除权限校验
|
||||||
|
this.initStudentRoleId()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 查询学员档案列表(后端分页) */
|
/** 查询学员档案列表(后端分页) */
|
||||||
|
|
@ -619,6 +641,38 @@ export default {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
/** 过滤学员用户 */
|
||||||
|
filterStudentUsers(rows) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!this.studentRoleId || rows.length === 0) {
|
||||||
|
resolve(rows)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 获取所有学员用户ID列表
|
||||||
|
allocatedUserList({
|
||||||
|
roleId: this.studentRoleId,
|
||||||
|
status: '0',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10000
|
||||||
|
}).then(response => {
|
||||||
|
const studentUserIds = new Set()
|
||||||
|
const studentUsers = response.rows || []
|
||||||
|
studentUsers.forEach(user => {
|
||||||
|
if (user.userId) {
|
||||||
|
studentUserIds.add(user.userId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 过滤出只包含学员用户的记录
|
||||||
|
const filteredRows = rows.filter(row => {
|
||||||
|
return row.userId && studentUserIds.has(row.userId)
|
||||||
|
})
|
||||||
|
resolve(filteredRows)
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("获取学员用户列表失败:", error)
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
// 取消按钮
|
// 取消按钮
|
||||||
cancel() {
|
cancel() {
|
||||||
this.open = false
|
this.open = false
|
||||||
|
|
@ -635,9 +689,9 @@ export default {
|
||||||
prison: undefined,
|
prison: undefined,
|
||||||
prisonArea: undefined,
|
prisonArea: undefined,
|
||||||
gender: undefined,
|
gender: undefined,
|
||||||
|
birthday: undefined,
|
||||||
nation: undefined,
|
nation: undefined,
|
||||||
educationLevel: undefined,
|
educationLevel: undefined,
|
||||||
birthday: undefined,
|
|
||||||
crimeName: undefined,
|
crimeName: undefined,
|
||||||
sentenceTerm: undefined,
|
sentenceTerm: undefined,
|
||||||
sentenceStartDate: undefined,
|
sentenceStartDate: undefined,
|
||||||
|
|
@ -654,8 +708,8 @@ export default {
|
||||||
},
|
},
|
||||||
// 处理姓名输入,只允许汉字
|
// 处理姓名输入,只允许汉字
|
||||||
handleUserNameInput(value) {
|
handleUserNameInput(value) {
|
||||||
// 移除所有非汉字字符
|
// 移除所有非汉字和非数字字符
|
||||||
this.form.userName = value.replace(/[^\u4e00-\u9fa5]/g, '')
|
this.form.userName = value.replace(/[^\u4e00-\u9fa5\d]/g, '')
|
||||||
},
|
},
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
handleQuery() {
|
handleQuery() {
|
||||||
|
|
@ -815,15 +869,15 @@ export default {
|
||||||
this.$modal.msgError("请选择要修改的档案")
|
this.$modal.msgError("请选择要修改的档案")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = row.userId
|
const userId = row.userId
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
this.$modal.msgError("用户ID不能为空")
|
this.$modal.msgError("用户ID不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.reset()
|
this.reset()
|
||||||
|
|
||||||
// 优先尝试通过profileId获取档案
|
// 优先尝试通过profileId获取档案
|
||||||
if (row.profileId) {
|
if (row.profileId) {
|
||||||
getProfile(row.profileId).then(response => {
|
getProfile(row.profileId).then(response => {
|
||||||
|
|
@ -874,7 +928,6 @@ export default {
|
||||||
gender: (row && row.gender) || undefined,
|
gender: (row && row.gender) || undefined,
|
||||||
nation: (row && row.nation) || undefined,
|
nation: (row && row.nation) || undefined,
|
||||||
educationLevel: (row && row.educationLevel) || undefined,
|
educationLevel: (row && row.educationLevel) || undefined,
|
||||||
birthday: (row && row.birthday) || undefined,
|
|
||||||
crimeName: (row && row.crimeName) || undefined,
|
crimeName: (row && row.crimeName) || undefined,
|
||||||
sentenceTerm: (row && row.sentenceTerm) || undefined,
|
sentenceTerm: (row && row.sentenceTerm) || undefined,
|
||||||
sentenceStartDate: undefined,
|
sentenceStartDate: undefined,
|
||||||
|
|
@ -896,6 +949,7 @@ export default {
|
||||||
prison: (row && row.prison) || undefined,
|
prison: (row && row.prison) || undefined,
|
||||||
prisonArea: (row && row.prisonArea) || undefined,
|
prisonArea: (row && row.prisonArea) || undefined,
|
||||||
gender: (row && row.gender) || undefined,
|
gender: (row && row.gender) || undefined,
|
||||||
|
birthday: (row && row.birthday) || undefined,
|
||||||
nation: (row && row.nation) || undefined,
|
nation: (row && row.nation) || undefined,
|
||||||
educationLevel: (row && row.educationLevel) || undefined,
|
educationLevel: (row && row.educationLevel) || undefined,
|
||||||
crimeName: (row && row.crimeName) || undefined,
|
crimeName: (row && row.crimeName) || undefined,
|
||||||
|
|
@ -948,31 +1002,36 @@ export default {
|
||||||
this.$modal.msgError("请选择要删除的记录")
|
this.$modal.msgError("请选择要删除的记录")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 检查是否都是学员用户(非学员用户不能删除)
|
||||||
const profileIdSet = new Set()
|
this.checkStudentUsers(targets).then(isAllStudents => {
|
||||||
const userIdSet = new Set()
|
if (!isAllStudents) {
|
||||||
const userIdsWithProfile = new Set() // 记录有档案的用户ID
|
this.$modal.msgError("只能删除学员用户,系统管理员等非学员用户不能删除")
|
||||||
targets.forEach(item => {
|
return
|
||||||
// 优先使用 profileId,如果没有则使用 userId
|
|
||||||
if (item.profileId) {
|
|
||||||
profileIdSet.add(item.profileId)
|
|
||||||
// 记录有档案的用户ID(删除档案时会自动删除用户,不需要单独删除)
|
|
||||||
if (item.userId) {
|
|
||||||
userIdsWithProfile.add(item.userId)
|
|
||||||
}
|
|
||||||
} else if (item.userId) {
|
|
||||||
// 没有 profileId,但有 userId,说明用户存在但可能没有档案,需要单独删除用户
|
|
||||||
userIdSet.add(item.userId)
|
|
||||||
}
|
}
|
||||||
})
|
const profileIdSet = new Set()
|
||||||
const profileIds = Array.from(profileIdSet)
|
const userIdSet = new Set()
|
||||||
// 只保留没有档案的用户ID(避免重复删除)
|
const userIdsWithProfile = new Set() // 记录有档案的用户ID
|
||||||
const userIds = Array.from(userIdSet).filter(userId => !userIdsWithProfile.has(userId))
|
targets.forEach(item => {
|
||||||
|
// 优先使用 profileId,如果没有则使用 userId
|
||||||
|
if (item.profileId) {
|
||||||
|
profileIdSet.add(item.profileId)
|
||||||
|
// 记录有档案的用户ID(删除档案时会自动删除用户,不需要单独删除)
|
||||||
|
if (item.userId) {
|
||||||
|
userIdsWithProfile.add(item.userId)
|
||||||
|
}
|
||||||
|
} else if (item.userId) {
|
||||||
|
// 没有 profileId,但有 userId,说明用户存在但可能没有档案,需要单独删除用户
|
||||||
|
userIdSet.add(item.userId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const profileIds = Array.from(profileIdSet)
|
||||||
|
// 只保留没有档案的用户ID(避免重复删除)
|
||||||
|
const userIds = Array.from(userIdSet).filter(userId => !userIdsWithProfile.has(userId))
|
||||||
|
|
||||||
if (!profileIds.length && !userIds.length) {
|
if (!profileIds.length && !userIds.length) {
|
||||||
this.$modal.msgError("未找到可删除的档案或用户")
|
this.$modal.msgError("未找到可删除的档案或用户")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = []
|
const parts = []
|
||||||
if (profileIds.length) {
|
if (profileIds.length) {
|
||||||
|
|
@ -981,31 +1040,94 @@ export default {
|
||||||
if (userIds.length) {
|
if (userIds.length) {
|
||||||
parts.push(`用户编号 ${userIds.join(',')}`)
|
parts.push(`用户编号 ${userIds.join(',')}`)
|
||||||
}
|
}
|
||||||
const message = `是否确认删除${parts.join(' 和 ')}?`
|
const message = `是否确认删除${parts.join(' 和 ')}?`
|
||||||
|
|
||||||
this.$modal.confirm(message).then(() => {
|
this.$modal.confirm(message).then(() => {
|
||||||
const tasks = []
|
const tasks = []
|
||||||
// 删除档案(如果有)
|
// 删除档案(如果有)
|
||||||
profileIds.forEach(id => tasks.push(delProfile(id)))
|
profileIds.forEach(id => tasks.push(delProfile(id)))
|
||||||
// 删除用户(只删除没有档案的用户)
|
// 删除用户(只删除没有档案的用户)
|
||||||
// 注意:如果用户有档案,删除档案时会自动删除用户,所以这里只删除没有档案的用户
|
// 注意:如果用户有档案,删除档案时会自动删除用户,所以这里只删除没有档案的用户
|
||||||
if (userIds.length) {
|
if (userIds.length) {
|
||||||
// 找出没有档案的用户(在 targets 中没有 profileId 的用户)
|
// 找出没有档案的用户(在 targets 中没有 profileId 的用户)
|
||||||
const userIdsWithoutProfile = userIds.filter(userId => {
|
const userIdsWithoutProfile = userIds.filter(userId => {
|
||||||
return !targets.some(item => item.userId === userId && item.profileId)
|
return !targets.some(item => item.userId === userId && item.profileId)
|
||||||
})
|
})
|
||||||
if (userIdsWithoutProfile.length > 0) {
|
if (userIdsWithoutProfile.length > 0) {
|
||||||
tasks.push(delUserInProfile(userIdsWithoutProfile))
|
tasks.push(delUserInProfile(userIdsWithoutProfile))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return Promise.all(tasks)
|
||||||
return Promise.all(tasks)
|
}).then(() => {
|
||||||
}).then(() => {
|
this.getList()
|
||||||
this.getList()
|
this.$modal.msgSuccess("删除成功")
|
||||||
this.$modal.msgSuccess("删除成功")
|
}).catch(error => {
|
||||||
|
console.error("删除失败:", error)
|
||||||
|
const errorMsg = error.msg || error.message || "删除失败"
|
||||||
|
this.$modal.msgError(errorMsg)
|
||||||
|
})
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error("删除失败:", error)
|
console.error("检查学员用户失败:", error)
|
||||||
const errorMsg = error.msg || error.message || "删除失败"
|
this.$modal.msgError("检查用户角色失败,无法删除")
|
||||||
this.$modal.msgError(errorMsg)
|
})
|
||||||
|
},
|
||||||
|
/** 初始化学员角色ID */
|
||||||
|
initStudentRoleId() {
|
||||||
|
listRole({}).then(response => {
|
||||||
|
const roles = response.rows || response.data || []
|
||||||
|
if (!Array.isArray(roles) || roles.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let candidate = roles.find(r => r && r.roleKey && r.roleKey.toLowerCase() === 'student')
|
||||||
|
if (!candidate) {
|
||||||
|
candidate = roles.find(r => r && r.roleName && r.roleName.indexOf('学员') !== -1)
|
||||||
|
}
|
||||||
|
if (!candidate) {
|
||||||
|
candidate = roles.find(r => r && r.roleId === 101)
|
||||||
|
}
|
||||||
|
if (candidate && candidate.roleId) {
|
||||||
|
this.studentRoleId = candidate.roleId
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// 角色获取失败时,不中断页面其它功能
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/** 检查用户是否都是学员用户 */
|
||||||
|
checkStudentUsers(targets) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!targets || targets.length === 0) {
|
||||||
|
resolve(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 如果学员角色ID尚未初始化,则不允许删除,给出提示
|
||||||
|
if (!this.studentRoleId) {
|
||||||
|
this.$modal.msgError('未正确配置学员角色,暂不允许删除用户,请联系管理员')
|
||||||
|
resolve(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 获取所有学员用户ID列表
|
||||||
|
allocatedUserList({
|
||||||
|
roleId: this.studentRoleId,
|
||||||
|
status: '0',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10000
|
||||||
|
}).then(response => {
|
||||||
|
const studentUserIds = new Set()
|
||||||
|
const studentUsers = response.rows || []
|
||||||
|
studentUsers.forEach(user => {
|
||||||
|
if (user.userId) {
|
||||||
|
studentUserIds.add(user.userId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 检查所有目标用户是否都是学员用户
|
||||||
|
const allAreStudents = targets.every(item => {
|
||||||
|
return item.userId && studentUserIds.has(item.userId)
|
||||||
|
})
|
||||||
|
resolve(allAreStudents)
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("获取学员用户列表失败:", error)
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/** 导出按钮操作 */
|
/** 导出按钮操作 */
|
||||||
|
|
@ -1052,7 +1174,78 @@ export default {
|
||||||
this.$modal.msgError('请选择后缀为 "xls"或"xlsx"的文件。')
|
this.$modal.msgError('请选择后缀为 "xls"或"xlsx"的文件。')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
this.upload.showProgress = true
|
||||||
|
this.upload.progress = 0
|
||||||
|
this.upload.statusText = "准备上传..."
|
||||||
|
this.upload.isUploading = true
|
||||||
|
this.upload.stage = "uploading"
|
||||||
this.$refs.upload.submit()
|
this.$refs.upload.submit()
|
||||||
|
this.startImportProgressPolling()
|
||||||
|
},
|
||||||
|
normalizeUploadResponse(payload) {
|
||||||
|
if (!payload) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
if (typeof payload === "string") {
|
||||||
|
try {
|
||||||
|
return JSON.parse(payload)
|
||||||
|
} catch (error) {
|
||||||
|
return { msg: payload }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
},
|
||||||
|
resetUploadProgress() {
|
||||||
|
this.upload.isUploading = false
|
||||||
|
this.upload.showProgress = false
|
||||||
|
this.upload.progress = 0
|
||||||
|
this.upload.statusText = ""
|
||||||
|
this.upload.stage = "idle"
|
||||||
|
this.stopImportProgressPolling()
|
||||||
|
},
|
||||||
|
startImportProgressPolling() {
|
||||||
|
if (this.importProgressTimer) {
|
||||||
|
clearInterval(this.importProgressTimer)
|
||||||
|
}
|
||||||
|
this.importProgressTimer = setInterval(() => {
|
||||||
|
this.fetchImportProgress()
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
stopImportProgressPolling() {
|
||||||
|
if (this.importProgressTimer) {
|
||||||
|
clearInterval(this.importProgressTimer)
|
||||||
|
this.importProgressTimer = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchImportProgress() {
|
||||||
|
getProfileImportProgress().then(response => {
|
||||||
|
const progress = response.data || response
|
||||||
|
if (!progress) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.upload.showProgress = true
|
||||||
|
this.upload.stage = "processing"
|
||||||
|
const percent = progress.total > 0 ? Math.round((progress.processed / progress.total) * 100) : 0
|
||||||
|
this.upload.progress = Math.min(Math.max(percent, 0), 100)
|
||||||
|
this.upload.statusText = this.formatProgressStatus(progress)
|
||||||
|
if (["success", "failed"].includes(progress.status)) {
|
||||||
|
this.stopImportProgressPolling()
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// 忽略轮询失败
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formatProgressStatus(progress) {
|
||||||
|
if (!progress) {
|
||||||
|
return "正在处理..."
|
||||||
|
}
|
||||||
|
if (progress.status === "success") {
|
||||||
|
return `导入成功,共 ${progress.success} 条`
|
||||||
|
}
|
||||||
|
if (progress.status === "failed") {
|
||||||
|
return `导入失败,成功 ${progress.success} 条,失败 ${progress.failure} 条`
|
||||||
|
}
|
||||||
|
return `正在处理:已完成 ${progress.processed}/${progress.total}`
|
||||||
},
|
},
|
||||||
/** 状态变更 */
|
/** 状态变更 */
|
||||||
handleStatusChange(row) {
|
handleStatusChange(row) {
|
||||||
|
|
@ -1062,7 +1255,7 @@ export default {
|
||||||
'2': '外出',
|
'2': '外出',
|
||||||
'3': '假释'
|
'3': '假释'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保状态是字符串类型,并验证有效性
|
// 确保状态是字符串类型,并验证有效性
|
||||||
const newStatus = String(row.status || '0')
|
const newStatus = String(row.status || '0')
|
||||||
if (!['0', '1', '2', '3'].includes(newStatus)) {
|
if (!['0', '1', '2', '3'].includes(newStatus)) {
|
||||||
|
|
@ -1071,19 +1264,19 @@ export default {
|
||||||
row.status = '0'
|
row.status = '0'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusText = statusMap[newStatus] || '未知'
|
const statusText = statusMap[newStatus] || '未知'
|
||||||
const oldStatus = row._oldStatus !== undefined ? row._oldStatus : String(row.status || '0')
|
const oldStatus = row._oldStatus !== undefined ? row._oldStatus : String(row.status || '0')
|
||||||
|
|
||||||
// 如果状态没有变化,直接返回
|
// 如果状态没有变化,直接返回
|
||||||
if (newStatus === oldStatus) {
|
if (newStatus === oldStatus) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存旧状态,用于取消时恢复
|
// 保存旧状态,用于取消时恢复
|
||||||
row._oldStatus = oldStatus
|
row._oldStatus = oldStatus
|
||||||
row.status = newStatus
|
row.status = newStatus
|
||||||
|
|
||||||
// 检查必要字段
|
// 检查必要字段
|
||||||
if (!row.profileId) {
|
if (!row.profileId) {
|
||||||
this.$modal.msgError("档案ID不能为空")
|
this.$modal.msgError("档案ID不能为空")
|
||||||
|
|
@ -1091,16 +1284,16 @@ export default {
|
||||||
delete row._oldStatus
|
delete row._oldStatus
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$modal.confirm('确认将状态修改为"' + statusText + '"?').then(() => {
|
this.$modal.confirm('确认将状态修改为"' + statusText + '"?').then(() => {
|
||||||
// 先获取完整的档案信息,确保有所有必要字段
|
// 先获取完整的档案信息,确保有所有必要字段
|
||||||
return getProfile(row.profileId).then(response => {
|
return getProfile(row.profileId).then(response => {
|
||||||
if (!response || !response.data) {
|
if (!response || !response.data) {
|
||||||
throw new Error("获取档案信息失败,请确认档案是否存在")
|
throw new Error("获取档案信息失败,请确认档案是否存在")
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileData = response.data
|
const profileData = response.data
|
||||||
|
|
||||||
// 确保传递必要的字段
|
// 确保传递必要的字段
|
||||||
const updateData = {
|
const updateData = {
|
||||||
profileId: row.profileId,
|
profileId: row.profileId,
|
||||||
|
|
@ -1109,7 +1302,7 @@ export default {
|
||||||
infoNumber: profileData.infoNumber || row.infoNumber,
|
infoNumber: profileData.infoNumber || row.infoNumber,
|
||||||
userName: profileData.userName || row.userName
|
userName: profileData.userName || row.userName
|
||||||
}
|
}
|
||||||
|
|
||||||
// 再次验证必要字段
|
// 再次验证必要字段
|
||||||
if (!updateData.userId) {
|
if (!updateData.userId) {
|
||||||
const error = new Error("用户ID不能为空,请先完善档案信息")
|
const error = new Error("用户ID不能为空,请先完善档案信息")
|
||||||
|
|
@ -1117,14 +1310,14 @@ export default {
|
||||||
delete row._oldStatus
|
delete row._oldStatus
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!updateData.infoNumber) {
|
if (!updateData.infoNumber) {
|
||||||
const error = new Error("信息编号不能为空,请先完善档案信息")
|
const error = new Error("信息编号不能为空,请先完善档案信息")
|
||||||
row.status = oldStatus
|
row.status = oldStatus
|
||||||
delete row._oldStatus
|
delete row._oldStatus
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateProfile(updateData)
|
return updateProfile(updateData)
|
||||||
})
|
})
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
|
|
@ -1135,14 +1328,14 @@ export default {
|
||||||
// 如果是用户取消操作(Element UI confirm 取消时会 reject,但没有错误信息)
|
// 如果是用户取消操作(Element UI confirm 取消时会 reject,但没有错误信息)
|
||||||
// 检查是否是取消操作:没有 response 且没有 message,或者 message 为空
|
// 检查是否是取消操作:没有 response 且没有 message,或者 message 为空
|
||||||
const isCancel = !error.response && (!error.message || error.message === '')
|
const isCancel = !error.response && (!error.message || error.message === '')
|
||||||
|
|
||||||
if (isCancel) {
|
if (isCancel) {
|
||||||
// 用户取消,恢复状态,不显示错误
|
// 用户取消,恢复状态,不显示错误
|
||||||
row.status = oldStatus
|
row.status = oldStatus
|
||||||
delete row._oldStatus
|
delete row._oldStatus
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 真正的错误,显示错误信息
|
// 真正的错误,显示错误信息
|
||||||
console.error("状态修改失败:", error)
|
console.error("状态修改失败:", error)
|
||||||
const errorMsg = error.response?.data?.msg || error.message || "状态修改失败"
|
const errorMsg = error.response?.data?.msg || error.message || "状态修改失败"
|
||||||
|
|
@ -1187,5 +1380,15 @@ export default {
|
||||||
.app-container {
|
.app-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
.upload-progress {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 0 10px 10px;
|
||||||
|
}
|
||||||
|
.upload-progress__text {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #606266;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@
|
||||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="360">
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="420">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button
|
<el-button
|
||||||
size="mini"
|
size="mini"
|
||||||
|
|
@ -334,6 +334,13 @@ export default {
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getList()
|
this.getList()
|
||||||
|
// 检查路由参数,如果存在 action: 'edit' 和 questionnaireId,自动打开编辑对话框
|
||||||
|
const route = this.$route
|
||||||
|
if (route.query.action === 'edit' && route.query.questionnaireId) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.handleUpdateFromRoute(route.query.questionnaireId)
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 查询问卷列表 */
|
/** 查询问卷列表 */
|
||||||
|
|
@ -395,10 +402,31 @@ export default {
|
||||||
handleUpdate(row) {
|
handleUpdate(row) {
|
||||||
this.reset()
|
this.reset()
|
||||||
const questionnaireId = row.questionnaireId || this.ids[0]
|
const questionnaireId = row.questionnaireId || this.ids[0]
|
||||||
|
this.handleUpdateFromRoute(questionnaireId)
|
||||||
|
},
|
||||||
|
/** 从路由参数或传入的ID打开编辑对话框 */
|
||||||
|
handleUpdateFromRoute(questionnaireId) {
|
||||||
|
if (!questionnaireId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.reset()
|
||||||
getQuestionnaire(questionnaireId).then(response => {
|
getQuestionnaire(questionnaireId).then(response => {
|
||||||
this.form = response.data
|
this.form = response.data
|
||||||
this.open = true
|
this.open = true
|
||||||
this.title = "修改问卷"
|
this.title = "修改问卷"
|
||||||
|
// 清除路由参数,避免刷新时重复打开
|
||||||
|
if (this.$route.query.action === 'edit') {
|
||||||
|
const newQuery = { ...this.$route.query }
|
||||||
|
delete newQuery.action
|
||||||
|
delete newQuery.questionnaireId
|
||||||
|
this.$router.replace({
|
||||||
|
path: this.$route.path,
|
||||||
|
query: Object.keys(newQuery).length > 0 ? newQuery : {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("获取问卷信息失败:", error)
|
||||||
|
this.$modal.msgError("获取问卷信息失败")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/** 提交按钮 */
|
/** 提交按钮 */
|
||||||
|
|
|
||||||
|
|
@ -3,33 +3,30 @@
|
||||||
<el-card class="user-selector" shadow="never">
|
<el-card class="user-selector" shadow="never">
|
||||||
<el-form :inline="true" size="small">
|
<el-form :inline="true" size="small">
|
||||||
<el-form-item label="选择用户">
|
<el-form-item label="选择用户">
|
||||||
<el-select
|
<el-autocomplete
|
||||||
v-model="selectedUserId"
|
v-model="userSearchKeyword"
|
||||||
style="width: 280px"
|
style="width: 320px"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
:fetch-suggestions="searchUsers"
|
||||||
remote
|
placeholder="输入姓名或信息编号搜索"
|
||||||
reserve-keyword
|
value-key="value"
|
||||||
:remote-method="searchUsers"
|
:trigger-on-focus="false"
|
||||||
:loading="userOptionsLoading"
|
:debounce="400"
|
||||||
placeholder="输入姓名或账号搜索"
|
@select="handleUserSelect"
|
||||||
@change="handleUserChange"
|
|
||||||
>
|
>
|
||||||
<el-option
|
<template slot-scope="{ item }">
|
||||||
v-for="item in userOptions"
|
|
||||||
:key="item.userId"
|
|
||||||
:label="buildUserLabel(item)"
|
|
||||||
:value="item.userId"
|
|
||||||
>
|
|
||||||
<div class="user-option">
|
<div class="user-option">
|
||||||
<span class="name">{{ item.nickName || item.userName }}</span>
|
<span class="name">{{ item.nickName || item.userName }}</span>
|
||||||
<span class="dept">{{ item.deptName || '未分配单位' }}</span>
|
<span class="dept">{{ item.infoNumber ? `编号:${item.infoNumber}` : '' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</el-option>
|
</template>
|
||||||
</el-select>
|
</el-autocomplete>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" icon="el-icon-search" :disabled="!selectedUserId" @click="loadUserData">
|
<el-button type="primary" icon="el-icon-search" @click="handleManualSearch">
|
||||||
|
搜索
|
||||||
|
</el-button>
|
||||||
|
<el-button type="success" icon="el-icon-refresh" :disabled="!selectedUserId" @click="loadUserData">
|
||||||
载入
|
载入
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button icon="el-icon-refresh" @click="resetSelection">重置</el-button>
|
<el-button icon="el-icon-refresh" @click="resetSelection">重置</el-button>
|
||||||
|
|
@ -53,7 +50,7 @@
|
||||||
|
|
||||||
<el-card class="scale-list" shadow="never" v-loading="loading">
|
<el-card class="scale-list" shadow="never" v-loading="loading">
|
||||||
<div slot="header">
|
<div slot="header">
|
||||||
<span>量表列表(请勾选需要分析的量表)</span>
|
<span>量表/问卷列表(请勾选需要分析的量表/问卷)</span>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|
@ -76,7 +73,17 @@
|
||||||
empty-text="暂无可用的测评报告"
|
empty-text="暂无可用的测评报告"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" />
|
<el-table-column type="selection" width="55" />
|
||||||
<el-table-column prop="scaleName" label="量表名称" min-width="200" />
|
<el-table-column prop="scaleName" label="量表/问卷名称" min-width="220">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag
|
||||||
|
v-if="scope.row.sourceType === 'questionnaire'"
|
||||||
|
size="mini"
|
||||||
|
type="warning"
|
||||||
|
style="margin-right: 6px"
|
||||||
|
>问卷</el-tag>
|
||||||
|
<span>{{ scope.row.scaleName || '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="reportTitle" label="报告标题" min-width="240" :show-overflow-tooltip="true" />
|
<el-table-column prop="reportTitle" label="报告标题" min-width="240" :show-overflow-tooltip="true" />
|
||||||
<el-table-column label="测评时间" width="200" align="center">
|
<el-table-column label="测评时间" width="200" align="center">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
|
|
@ -107,6 +114,12 @@
|
||||||
<div v-else class="empty-report">报告生成中,请稍候...</div>
|
<div v-else class="empty-report">报告生成中,请稍候...</div>
|
||||||
</div>
|
</div>
|
||||||
<div slot="footer">
|
<div slot="footer">
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
icon="el-icon-printer"
|
||||||
|
:disabled="!comprehensiveReport"
|
||||||
|
@click="printReport"
|
||||||
|
>打印报告</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon="el-icon-download"
|
icon="el-icon-download"
|
||||||
|
|
@ -120,8 +133,8 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getUserAssessmentSummary, getStudentOptions } from '@/api/psychology/assessment'
|
import { getUserAssessmentSummary, getStudentOptions } from '@/api/psychology/assessment'
|
||||||
import { getProfileByUserId } from '@/api/psychology/profile'
|
import { getProfileByUserId, listProfile } from '@/api/psychology/profile'
|
||||||
import { getReport } from '@/api/psychology/report'
|
import { getReport, listReport } from '@/api/psychology/report'
|
||||||
import { parseTime } from '@/utils/ruoyi'
|
import { parseTime } from '@/utils/ruoyi'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
|
|
@ -130,8 +143,9 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedUserId: undefined,
|
selectedUserId: undefined,
|
||||||
userOptions: [],
|
userSearchKeyword: '',
|
||||||
userOptionsLoading: false,
|
userSearchLoading: false,
|
||||||
|
cachedUserOptions: [],
|
||||||
userProfile: null,
|
userProfile: null,
|
||||||
userSummary: null,
|
userSummary: null,
|
||||||
reportOptions: [],
|
reportOptions: [],
|
||||||
|
|
@ -140,22 +154,57 @@ export default {
|
||||||
generating: false,
|
generating: false,
|
||||||
reportDialogVisible: false,
|
reportDialogVisible: false,
|
||||||
comprehensiveReport: '',
|
comprehensiveReport: '',
|
||||||
|
// ========== 本地大模型配置 ==========
|
||||||
OLLAMA_URL: 'http://192.168.0.106:11434/api/generate',
|
OLLAMA_URL: 'http://192.168.0.106:11434/api/generate',
|
||||||
MODEL: 'deepseek-r1:32b'
|
MODEL: 'deepseek-r1:32b'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.searchUsers('')
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
searchUsers(query) {
|
searchUsers(query, cb) {
|
||||||
this.userOptionsLoading = true
|
this.fetchUserOptions(query)
|
||||||
getStudentOptions({ keyword: query, limit: 20 })
|
.then((list) => cb(list))
|
||||||
.then((res) => {
|
.catch(() => cb([]))
|
||||||
this.userOptions = res.data || []
|
},
|
||||||
|
fetchUserOptions(keyword) {
|
||||||
|
const trimmed = (keyword || '').trim()
|
||||||
|
if (!trimmed) {
|
||||||
|
return Promise.resolve([])
|
||||||
|
}
|
||||||
|
this.userSearchLoading = true
|
||||||
|
const studentPromise = getStudentOptions({ keyword: trimmed, limit: 20 })
|
||||||
|
.then((res) => this.normalizeStudentOptions(res.data || []))
|
||||||
|
.catch(() => [])
|
||||||
|
let profilePromise = Promise.resolve([])
|
||||||
|
// 如果是纯数字,通过信息编号搜索
|
||||||
|
if (/^\d+$/.test(trimmed)) {
|
||||||
|
profilePromise = listProfile({
|
||||||
|
infoNumber: trimmed,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20
|
||||||
|
})
|
||||||
|
.then((res) => this.normalizeProfileOptions(res.rows || []))
|
||||||
|
.catch(() => [])
|
||||||
|
} else {
|
||||||
|
// 如果不是纯数字(输入姓名),通过姓名搜索档案以获取编号信息
|
||||||
|
profilePromise = listProfile({
|
||||||
|
userName: trimmed,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20
|
||||||
|
})
|
||||||
|
.then((res) => this.normalizeProfileOptions(res.rows || []))
|
||||||
|
.catch(() => [])
|
||||||
|
}
|
||||||
|
return Promise.all([studentPromise, profilePromise])
|
||||||
|
.then(([studentList, profileList]) => {
|
||||||
|
// 合并时,优先使用档案数据(因为档案数据包含完整的infoNumber)
|
||||||
|
const merged = this.mergeUserOptions([...profileList, ...studentList])
|
||||||
|
this.cachedUserOptions = merged
|
||||||
|
return merged
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.userOptionsLoading = false
|
this.userSearchLoading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
buildUserLabel(option) {
|
buildUserLabel(option) {
|
||||||
|
|
@ -163,13 +212,112 @@ export default {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
const name = option.nickName || option.userName || ''
|
const name = option.nickName || option.userName || ''
|
||||||
|
const info = option.infoNumber ? `(编号:${option.infoNumber})` : ''
|
||||||
const dept = option.deptName ? ` - ${option.deptName}` : ''
|
const dept = option.deptName ? ` - ${option.deptName}` : ''
|
||||||
return name + dept
|
return `${name}${info}${dept}`
|
||||||
},
|
},
|
||||||
handleUserChange() {
|
handleUserSelect(option) {
|
||||||
if (!this.selectedUserId) {
|
if (!option || !option.userId) {
|
||||||
this.resetSelection()
|
return
|
||||||
}
|
}
|
||||||
|
this.selectedUserId = option.userId
|
||||||
|
this.userSearchKeyword = this.buildUserLabel(option)
|
||||||
|
},
|
||||||
|
handleManualSearch() {
|
||||||
|
const keyword = (this.userSearchKeyword || '').trim()
|
||||||
|
if (!keyword) {
|
||||||
|
this.$message.warning('请输入姓名或信息编号')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从输入框中提取纯姓名或编号(去除括号、编号等格式)
|
||||||
|
let searchKeyword = keyword
|
||||||
|
// 如果包含编号格式,提取编号
|
||||||
|
const numberMatch = keyword.match(/编号[::]\s*(\d+)/)
|
||||||
|
if (numberMatch) {
|
||||||
|
searchKeyword = numberMatch[1]
|
||||||
|
} else {
|
||||||
|
// 如果包含姓名格式,提取姓名(去除括号和后面的内容)
|
||||||
|
const nameMatch = keyword.match(/^([^((]+)/)
|
||||||
|
if (nameMatch) {
|
||||||
|
searchKeyword = nameMatch[1].trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fetchUserOptions(searchKeyword).then((list) => {
|
||||||
|
if (!list.length) {
|
||||||
|
// 不显示错误消息,让用户继续搜索或选择
|
||||||
|
// 如果用户已经选择了用户,保持选择状态
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (list.length === 1) {
|
||||||
|
// 找到唯一结果,自动选择
|
||||||
|
this.handleUserSelect(list[0])
|
||||||
|
} else {
|
||||||
|
// 如果找到多条,尝试精确匹配输入框的完整内容
|
||||||
|
const exactMatch = list.find(opt => {
|
||||||
|
const label = this.buildUserLabel(opt)
|
||||||
|
return label === keyword
|
||||||
|
})
|
||||||
|
if (exactMatch) {
|
||||||
|
// 精确匹配成功,自动选择
|
||||||
|
this.handleUserSelect(exactMatch)
|
||||||
|
} else {
|
||||||
|
// 多条记录,提示用户从下拉列表选择
|
||||||
|
this.$message.info('找到多条记录,请从下拉列表选择具体用户')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// 搜索失败时,不显示错误消息,让用户继续操作
|
||||||
|
})
|
||||||
|
},
|
||||||
|
normalizeStudentOptions(list) {
|
||||||
|
return list.map((item) => ({
|
||||||
|
userId: item.userId,
|
||||||
|
userName: item.userName,
|
||||||
|
nickName: item.nickName,
|
||||||
|
infoNumber: item.infoNumber,
|
||||||
|
deptName: item.deptName,
|
||||||
|
value: this.buildUserLabel(item)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
normalizeProfileOptions(rows) {
|
||||||
|
return rows
|
||||||
|
.filter((profile) => profile && profile.userId)
|
||||||
|
.map((profile) => ({
|
||||||
|
userId: profile.userId,
|
||||||
|
userName: profile.userName || profile.nickName,
|
||||||
|
nickName: profile.userName || profile.nickName,
|
||||||
|
infoNumber: profile.infoNumber,
|
||||||
|
deptName: profile.prisonArea || profile.deptName,
|
||||||
|
value: this.buildUserLabel({
|
||||||
|
userName: profile.userName || profile.nickName,
|
||||||
|
nickName: profile.userName || profile.nickName,
|
||||||
|
infoNumber: profile.infoNumber
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
mergeUserOptions(list) {
|
||||||
|
const map = new Map()
|
||||||
|
list.forEach((item) => {
|
||||||
|
if (!item || !item.userId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!map.has(item.userId)) {
|
||||||
|
map.set(item.userId, item)
|
||||||
|
} else {
|
||||||
|
// 如果已存在该用户,优先保留有infoNumber的数据
|
||||||
|
const existing = map.get(item.userId)
|
||||||
|
if (!existing.infoNumber && item.infoNumber) {
|
||||||
|
// 如果已存在的没有infoNumber,而新项有,则更新
|
||||||
|
map.set(item.userId, item)
|
||||||
|
} else if (existing.infoNumber && !item.infoNumber) {
|
||||||
|
// 如果已存在的有infoNumber,而新项没有,则保留已存在的
|
||||||
|
// 不做任何操作
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return Array.from(map.values())
|
||||||
},
|
},
|
||||||
async loadUserData() {
|
async loadUserData() {
|
||||||
if (!this.selectedUserId) {
|
if (!this.selectedUserId) {
|
||||||
|
|
@ -184,7 +332,7 @@ export default {
|
||||||
|
|
||||||
// 转换量表数据
|
// 转换量表数据
|
||||||
const rawScales = (this.userSummary && Array.isArray(this.userSummary.scales)) ? this.userSummary.scales : []
|
const rawScales = (this.userSummary && Array.isArray(this.userSummary.scales)) ? this.userSummary.scales : []
|
||||||
this.reportOptions = rawScales.reduce((result, scale) => {
|
const scaleReports = rawScales.reduce((result, scale) => {
|
||||||
const attempts = Array.isArray(scale.attempts) ? scale.attempts : []
|
const attempts = Array.isArray(scale.attempts) ? scale.attempts : []
|
||||||
const reportRows = attempts
|
const reportRows = attempts
|
||||||
.filter((attempt) => attempt && attempt.reportId)
|
.filter((attempt) => attempt && attempt.reportId)
|
||||||
|
|
@ -198,10 +346,18 @@ export default {
|
||||||
submitTime: attempt.submitTime || attempt.startTime,
|
submitTime: attempt.submitTime || attempt.startTime,
|
||||||
totalScore: attempt.totalScore,
|
totalScore: attempt.totalScore,
|
||||||
summary: attempt.reportSummary || '',
|
summary: attempt.reportSummary || '',
|
||||||
status: attempt.status
|
status: attempt.status,
|
||||||
|
sourceType: 'assessment'
|
||||||
}))
|
}))
|
||||||
return result.concat(reportRows)
|
return result.concat(reportRows)
|
||||||
}, [])
|
}, [])
|
||||||
|
const questionnaireReports = await this.loadQuestionnaireReports(this.selectedUserId)
|
||||||
|
const combinedReports = [...scaleReports, ...questionnaireReports].sort((a, b) => {
|
||||||
|
const timeA = a.submitTime ? new Date(a.submitTime).getTime() : 0
|
||||||
|
const timeB = b.submitTime ? new Date(b.submitTime).getTime() : 0
|
||||||
|
return timeB - timeA
|
||||||
|
})
|
||||||
|
this.reportOptions = combinedReports
|
||||||
this.selectedReports = []
|
this.selectedReports = []
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.reportTable && this.$refs.reportTable.doLayout) {
|
if (this.$refs.reportTable && this.$refs.reportTable.doLayout) {
|
||||||
|
|
@ -226,12 +382,44 @@ export default {
|
||||||
},
|
},
|
||||||
resetSelection() {
|
resetSelection() {
|
||||||
this.selectedUserId = undefined
|
this.selectedUserId = undefined
|
||||||
|
this.userSearchKeyword = ''
|
||||||
this.userProfile = null
|
this.userProfile = null
|
||||||
this.userSummary = null
|
this.userSummary = null
|
||||||
this.reportOptions = []
|
this.reportOptions = []
|
||||||
this.selectedReports = []
|
this.selectedReports = []
|
||||||
this.comprehensiveReport = ''
|
this.comprehensiveReport = ''
|
||||||
},
|
},
|
||||||
|
async loadQuestionnaireReports(userId) {
|
||||||
|
if (!userId) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await listReport({
|
||||||
|
userId: userId,
|
||||||
|
sourceType: 'questionnaire',
|
||||||
|
isGenerated: '1',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 1000
|
||||||
|
})
|
||||||
|
const rows = response.rows || []
|
||||||
|
return rows.map((row) => ({
|
||||||
|
key: `questionnaire-${row.reportId}`,
|
||||||
|
scaleId: row.sourceId,
|
||||||
|
scaleName: row.reportTitle || '问卷报告',
|
||||||
|
assessmentId: row.sourceId,
|
||||||
|
reportId: row.reportId,
|
||||||
|
reportTitle: row.reportTitle || '问卷报告',
|
||||||
|
submitTime: row.generateTime || row.createTime,
|
||||||
|
totalScore: row.totalScore || row.score || '-',
|
||||||
|
summary: row.summary || '',
|
||||||
|
status: row.isGenerated === '1' ? '1' : '0',
|
||||||
|
sourceType: 'questionnaire'
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载问卷报告失败:', error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
formatDateTime(value) {
|
formatDateTime(value) {
|
||||||
if (!value) return '-'
|
if (!value) return '-'
|
||||||
return parseTime(value)
|
return parseTime(value)
|
||||||
|
|
@ -294,18 +482,20 @@ export default {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await getReport(row.reportId, 'assessment')
|
const sourceType = row.sourceType === 'questionnaire' ? 'questionnaire' : 'assessment'
|
||||||
|
const response = await getReport(row.reportId, sourceType)
|
||||||
if (response && response.data) {
|
if (response && response.data) {
|
||||||
reports.push({
|
reports.push({
|
||||||
scaleName: row.scaleName,
|
scaleName: row.scaleName || row.reportTitle || '问卷报告',
|
||||||
submitTime: row.submitTime,
|
submitTime: row.submitTime,
|
||||||
totalScore: row.totalScore,
|
totalScore: row.totalScore,
|
||||||
summary: response.data.summary || row.summary || '',
|
summary: response.data.summary || row.summary || '',
|
||||||
content: response.data.reportContent || ''
|
content: response.data.reportContent || '',
|
||||||
|
sourceType
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`获取量表 ${row.scaleName} 的报告失败:`, error)
|
console.warn(`获取${row.sourceType === 'questionnaire' ? '问卷' : '量表'} ${row.scaleName} 的报告失败:`, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return reports
|
return reports
|
||||||
|
|
@ -357,12 +547,13 @@ export default {
|
||||||
|
|
||||||
const scaleReportsText = scaleReports
|
const scaleReportsText = scaleReports
|
||||||
.map((report, index) => {
|
.map((report, index) => {
|
||||||
|
const typeLabel = report.sourceType === 'questionnaire' ? '问卷' : '量表'
|
||||||
const contentText = report.content
|
const contentText = report.content
|
||||||
.replace(/<[^>]*>/g, '')
|
.replace(/<[^>]*>/g, '')
|
||||||
.replace(/ /g, ' ')
|
.replace(/ /g, ' ')
|
||||||
.substring(0, 500)
|
.substring(0, 500)
|
||||||
return `
|
return `
|
||||||
量表${index + 1}:${report.scaleName}
|
${typeLabel}${index + 1}:${report.scaleName}
|
||||||
测评时间:${this.formatDateTime(report.submitTime)}
|
测评时间:${this.formatDateTime(report.submitTime)}
|
||||||
总分:${report.totalScore}
|
总分:${report.totalScore}
|
||||||
报告摘要:${report.summary || '无'}
|
报告摘要:${report.summary || '无'}
|
||||||
|
|
@ -373,6 +564,7 @@ export default {
|
||||||
|
|
||||||
return `${SYSTEM_PROMPT}\n\n${userInfoText}\n\n${scaleReportsText}`
|
return `${SYSTEM_PROMPT}\n\n${userInfoText}\n\n${scaleReportsText}`
|
||||||
},
|
},
|
||||||
|
// 本地 OLLAMA API 调用方法
|
||||||
async callOLLAMA(prompt) {
|
async callOLLAMA(prompt) {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(this.OLLAMA_URL, {
|
const { data } = await axios.post(this.OLLAMA_URL, {
|
||||||
|
|
@ -397,13 +589,13 @@ export default {
|
||||||
.trim()
|
.trim()
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
throw new Error('AI分析返回结果为空')
|
throw new Error('本地AI分析返回结果为空')
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('AI分析失败:', error)
|
console.error('本地AI分析失败:', error)
|
||||||
throw new Error('AI分析失败:' + (error.message || '未知错误'))
|
throw new Error('本地AI分析失败:' + (error.message || '未知错误'))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
formatReport(aiReport, userInfo, scaleReports) {
|
formatReport(aiReport, userInfo, scaleReports) {
|
||||||
|
|
@ -449,7 +641,8 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; text-align: right; color: #909399; font-size: 12px;">
|
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; text-align: right; color: #909399; font-size: 12px;">
|
||||||
报告生成时间:${parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')}
|
<div>报告生成时间:${parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')}</div>
|
||||||
|
<div style="margin-top: 8px;">被评估人:<span style="color: #303133; font-weight: bold;">${userInfo.userName || '未知'}</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
@ -477,13 +670,8 @@ export default {
|
||||||
|
|
||||||
return html
|
return html
|
||||||
},
|
},
|
||||||
exportReport(format) {
|
buildReportHtml() {
|
||||||
if (!this.comprehensiveReport) {
|
return `
|
||||||
this.$message.warning('报告内容为空')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const reportHtml = `
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
|
@ -505,6 +693,14 @@ export default {
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
|
},
|
||||||
|
exportReport(format) {
|
||||||
|
if (!this.comprehensiveReport) {
|
||||||
|
this.$message.warning('报告内容为空')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const reportHtml = this.buildReportHtml()
|
||||||
|
|
||||||
if (format === 'word') {
|
if (format === 'word') {
|
||||||
const blob = new Blob(['\ufeff', reportHtml], { type: 'application/msword' })
|
const blob = new Blob(['\ufeff', reportHtml], { type: 'application/msword' })
|
||||||
|
|
@ -518,16 +714,24 @@ export default {
|
||||||
window.URL.revokeObjectURL(link.href)
|
window.URL.revokeObjectURL(link.href)
|
||||||
this.$message.success('导出成功')
|
this.$message.success('导出成功')
|
||||||
} else {
|
} else {
|
||||||
const printWindow = window.open('', '_blank')
|
this.printReport()
|
||||||
if (!printWindow) {
|
|
||||||
this.$message.error('无法打开打印窗口,请检查浏览器是否阻止了弹窗')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
printWindow.document.write(reportHtml)
|
|
||||||
printWindow.document.close()
|
|
||||||
printWindow.focus()
|
|
||||||
printWindow.print()
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
printReport() {
|
||||||
|
if (!this.comprehensiveReport) {
|
||||||
|
this.$message.warning('报告内容为空')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const reportHtml = this.buildReportHtml()
|
||||||
|
const printWindow = window.open('', '_blank')
|
||||||
|
if (!printWindow) {
|
||||||
|
this.$message.error('无法打开打印窗口,请检查浏览器是否阻止了弹窗')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printWindow.document.write(reportHtml)
|
||||||
|
printWindow.document.close()
|
||||||
|
printWindow.focus()
|
||||||
|
printWindow.print()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,38 @@
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
|
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
|
||||||
<el-form-item label="量表名称" prop="scaleName">
|
<el-form-item label="量表名称" prop="scaleName">
|
||||||
<el-input
|
<el-select
|
||||||
v-model="queryParams.scaleName"
|
v-model="queryParams.scaleName"
|
||||||
placeholder="请输入量表名称"
|
placeholder="请选择或输入量表名称"
|
||||||
clearable
|
clearable
|
||||||
@keyup.enter.native="handleQuery"
|
filterable
|
||||||
/>
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="name in uniqueScaleNames"
|
||||||
|
:key="name"
|
||||||
|
:label="name"
|
||||||
|
:value="name"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="量表编码" prop="scaleCode">
|
<el-form-item label="量表编码" prop="scaleCode">
|
||||||
<el-input
|
<el-select
|
||||||
v-model="queryParams.scaleCode"
|
v-model="queryParams.scaleCode"
|
||||||
placeholder="请输入量表编码"
|
placeholder="请选择或输入量表编码"
|
||||||
clearable
|
clearable
|
||||||
@keyup.enter.native="handleQuery"
|
filterable
|
||||||
/>
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="code in uniqueScaleCodes"
|
||||||
|
:key="code"
|
||||||
|
:label="code"
|
||||||
|
:value="code"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="量表类型" prop="scaleType">
|
<el-form-item label="量表类型" prop="scaleType">
|
||||||
<el-select v-model="queryParams.scaleType" placeholder="量表类型" clearable>
|
<el-select v-model="queryParams.scaleType" placeholder="量表类型" clearable>
|
||||||
|
|
@ -30,7 +48,7 @@
|
||||||
<el-form-item label="状态" prop="status">
|
<el-form-item label="状态" prop="status">
|
||||||
<el-select v-model="queryParams.status" placeholder="状态" clearable>
|
<el-select v-model="queryParams.status" placeholder="状态" clearable>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in dict.type.psy_scale_status"
|
v-for="dict in scaleStatusOptions"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
:label="dict.label"
|
:label="dict.label"
|
||||||
:value="dict.value"
|
:value="dict.value"
|
||||||
|
|
@ -427,6 +445,95 @@
|
||||||
<p style="margin-top: 20px; color: #606266;">正在生成二维码...</p>
|
<p style="margin-top: 20px; color: #606266;">正在生成二维码...</p>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 添加或修改问卷对话框(复用问卷管理的对话框) -->
|
||||||
|
<el-dialog :title="questionnaireTitle" :visible.sync="questionnaireOpen" width="900px" append-to-body>
|
||||||
|
<el-form ref="questionnaireForm" :model="questionnaireForm" :rules="questionnaireRules" label-width="120px">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="问卷编码" prop="questionnaireCode">
|
||||||
|
<el-input v-model="questionnaireForm.questionnaireCode" placeholder="请输入问卷编码" :disabled="questionnaireForm.questionnaireId != undefined" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="问卷名称" prop="questionnaireName">
|
||||||
|
<el-input v-model="questionnaireForm.questionnaireName" placeholder="请输入问卷名称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="问卷类型" prop="questionnaireType">
|
||||||
|
<el-select v-model="questionnaireForm.questionnaireType" placeholder="请选择问卷类型">
|
||||||
|
<el-option label="自定义" value="custom" />
|
||||||
|
<el-option label="考试" value="exam" />
|
||||||
|
<el-option label="练习" value="practice" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="组卷方式" prop="paperType">
|
||||||
|
<el-select v-model="questionnaireForm.paperType" placeholder="请选择组卷方式">
|
||||||
|
<el-option label="手动" value="manual" />
|
||||||
|
<el-option label="随机" value="random" />
|
||||||
|
<el-option label="混合" value="mixed" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="题目数量" prop="itemCount">
|
||||||
|
<el-input-number v-model="questionnaireForm.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="questionnaireForm.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>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="及格分数" prop="passScore">
|
||||||
|
<el-input-number v-model="questionnaireForm.passScore" :min="0" :precision="2" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="预计时间(分)" prop="estimatedTime">
|
||||||
|
<el-input-number v-model="questionnaireForm.estimatedTime" :min="1" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="问卷描述" prop="description">
|
||||||
|
<el-input v-model="questionnaireForm.description" type="textarea" :rows="4" placeholder="请输入问卷描述" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="排序" prop="sortOrder">
|
||||||
|
<el-input-number v-model="questionnaireForm.sortOrder" :min="0" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-radio-group v-model="questionnaireForm.status">
|
||||||
|
<el-radio label="0">正常</el-radio>
|
||||||
|
<el-radio label="1">停用</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="备注">
|
||||||
|
<el-input v-model="questionnaireForm.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="submitQuestionnaireForm">确 定</el-button>
|
||||||
|
<el-button @click="cancelQuestionnaire">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -454,6 +561,8 @@ export default {
|
||||||
total: 0,
|
total: 0,
|
||||||
// 量表表格数据
|
// 量表表格数据
|
||||||
scaleList: [],
|
scaleList: [],
|
||||||
|
// 所有量表数据(不分页,用于下拉框)
|
||||||
|
allScaleList: [],
|
||||||
// 弹出层标题
|
// 弹出层标题
|
||||||
title: "",
|
title: "",
|
||||||
// 是否显示弹出层
|
// 是否显示弹出层
|
||||||
|
|
@ -474,6 +583,21 @@ export default {
|
||||||
// 二维码对话框
|
// 二维码对话框
|
||||||
qrcodeOpen: false,
|
qrcodeOpen: false,
|
||||||
qrcodeInfo: null,
|
qrcodeInfo: null,
|
||||||
|
// 问卷修改对话框
|
||||||
|
questionnaireOpen: false,
|
||||||
|
questionnaireTitle: "修改问卷",
|
||||||
|
questionnaireForm: {},
|
||||||
|
questionnaireRules: {
|
||||||
|
questionnaireCode: [
|
||||||
|
{ required: true, message: "问卷编码不能为空", trigger: "blur" }
|
||||||
|
],
|
||||||
|
questionnaireName: [
|
||||||
|
{ required: true, message: "问卷名称不能为空", trigger: "blur" }
|
||||||
|
],
|
||||||
|
questionnaireType: [
|
||||||
|
{ required: true, message: "问卷类型不能为空", trigger: "change" }
|
||||||
|
]
|
||||||
|
},
|
||||||
// 查询参数
|
// 查询参数
|
||||||
queryParams: {
|
queryParams: {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
|
|
@ -481,7 +605,8 @@ export default {
|
||||||
scaleName: undefined,
|
scaleName: undefined,
|
||||||
scaleCode: undefined,
|
scaleCode: undefined,
|
||||||
scaleType: undefined,
|
scaleType: undefined,
|
||||||
status: undefined
|
status: undefined,
|
||||||
|
includeQuestionnaire: true // 包含问卷数据
|
||||||
},
|
},
|
||||||
// 表单参数
|
// 表单参数
|
||||||
form: {},
|
form: {},
|
||||||
|
|
@ -513,10 +638,50 @@ export default {
|
||||||
seen.add(dict.value);
|
seen.add(dict.value);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
/** 状态下拉选项,缺失字典时使用本地默认值 */
|
||||||
|
scaleStatusOptions() {
|
||||||
|
const dictList = (this.dict && this.dict.type && this.dict.type.psy_scale_status) || [];
|
||||||
|
if (dictList.length) {
|
||||||
|
return dictList;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ label: "正常", value: "0" },
|
||||||
|
{ label: "停用", value: "1" }
|
||||||
|
];
|
||||||
|
},
|
||||||
|
/** 去重后的量表名称列表 */
|
||||||
|
uniqueScaleNames() {
|
||||||
|
// 使用所有量表列表而不是分页列表
|
||||||
|
if (!this.allScaleList || this.allScaleList.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const nameSet = new Set();
|
||||||
|
this.allScaleList.forEach(scale => {
|
||||||
|
if (scale.scaleName && scale.scaleName.trim()) {
|
||||||
|
nameSet.add(scale.scaleName.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Array.from(nameSet).sort();
|
||||||
|
},
|
||||||
|
/** 去重后的量表编码列表 */
|
||||||
|
uniqueScaleCodes() {
|
||||||
|
// 使用所有量表列表而不是分页列表
|
||||||
|
if (!this.allScaleList || this.allScaleList.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const codeSet = new Set();
|
||||||
|
this.allScaleList.forEach(scale => {
|
||||||
|
if (scale.scaleCode && scale.scaleCode.trim()) {
|
||||||
|
codeSet.add(scale.scaleCode.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Array.from(codeSet).sort();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getList()
|
this.getAllScaleList() // 先获取所有量表用于下拉框
|
||||||
|
this.getList() // 再获取分页数据
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 获取字典标签 */
|
/** 获取字典标签 */
|
||||||
|
|
@ -525,6 +690,21 @@ export default {
|
||||||
const dict = dictList.find(item => item.value === value);
|
const dict = dictList.find(item => item.value === value);
|
||||||
return dict ? dict.label : value;
|
return dict ? dict.label : value;
|
||||||
},
|
},
|
||||||
|
/** 获取所有量表列表(不分页,用于下拉框) */
|
||||||
|
getAllScaleList() {
|
||||||
|
// 创建一个不分页的查询参数
|
||||||
|
const allParams = {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10000, // 设置一个足够大的数值获取所有数据
|
||||||
|
includeQuestionnaire: true // 包含问卷数据
|
||||||
|
}
|
||||||
|
listScale(allParams).then(response => {
|
||||||
|
this.allScaleList = response.rows || []
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('获取所有量表列表失败:', error)
|
||||||
|
this.allScaleList = []
|
||||||
|
})
|
||||||
|
},
|
||||||
/** 查询量表列表 */
|
/** 查询量表列表 */
|
||||||
getList() {
|
getList() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|
@ -610,12 +790,14 @@ export default {
|
||||||
updateScale(this.form).then(response => {
|
updateScale(this.form).then(response => {
|
||||||
this.$modal.msgSuccess("修改成功")
|
this.$modal.msgSuccess("修改成功")
|
||||||
this.open = false
|
this.open = false
|
||||||
|
this.getAllScaleList() // 刷新所有量表列表
|
||||||
this.getList()
|
this.getList()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
addScale(this.form).then(response => {
|
addScale(this.form).then(response => {
|
||||||
this.$modal.msgSuccess("新增成功")
|
this.$modal.msgSuccess("新增成功")
|
||||||
this.open = false
|
this.open = false
|
||||||
|
this.getAllScaleList() // 刷新所有量表列表
|
||||||
this.getList()
|
this.getList()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -628,6 +810,7 @@ export default {
|
||||||
this.$modal.confirm('是否确认删除量表编号为"' + scaleIds + '"的数据项?').then(function() {
|
this.$modal.confirm('是否确认删除量表编号为"' + scaleIds + '"的数据项?').then(function() {
|
||||||
return delScale(scaleIds)
|
return delScale(scaleIds)
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
this.getAllScaleList() // 刷新所有量表列表
|
||||||
this.getList()
|
this.getList()
|
||||||
this.$modal.msgSuccess("删除成功")
|
this.$modal.msgSuccess("删除成功")
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
|
|
@ -779,6 +962,7 @@ export default {
|
||||||
this.$modal.msgSuccess(response.msg || "导入成功")
|
this.$modal.msgSuccess(response.msg || "导入成功")
|
||||||
this.importOpen = false
|
this.importOpen = false
|
||||||
this.importJsonText = ""
|
this.importJsonText = ""
|
||||||
|
this.getAllScaleList() // 刷新所有量表列表
|
||||||
this.getList()
|
this.getList()
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.$modal.msgError(error.msg || "导入失败")
|
this.$modal.msgError(error.msg || "导入失败")
|
||||||
|
|
@ -815,6 +999,7 @@ export default {
|
||||||
this.upload.fileContent = null
|
this.upload.fileContent = null
|
||||||
this.$refs.upload.clearFiles()
|
this.$refs.upload.clearFiles()
|
||||||
this.upload.isUploading = false
|
this.upload.isUploading = false
|
||||||
|
this.getAllScaleList() // 刷新所有量表列表
|
||||||
this.getList()
|
this.getList()
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.$modal.msgError(error.msg || "导入失败")
|
this.$modal.msgError(error.msg || "导入失败")
|
||||||
|
|
@ -847,6 +1032,7 @@ export default {
|
||||||
this.upload.fileContent = null
|
this.upload.fileContent = null
|
||||||
this.$refs.upload.clearFiles()
|
this.$refs.upload.clearFiles()
|
||||||
this.upload.isUploading = false
|
this.upload.isUploading = false
|
||||||
|
this.getAllScaleList() // 刷新所有量表列表
|
||||||
this.getList()
|
this.getList()
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.$modal.msgError(error.msg || "导入失败")
|
this.$modal.msgError(error.msg || "导入失败")
|
||||||
|
|
@ -916,8 +1102,62 @@ export default {
|
||||||
/** 问卷修改按钮操作 */
|
/** 问卷修改按钮操作 */
|
||||||
handleQuestionnaireUpdate(row) {
|
handleQuestionnaireUpdate(row) {
|
||||||
const questionnaireId = row.originalId || Math.abs(row.scaleId)
|
const questionnaireId = row.originalId || Math.abs(row.scaleId)
|
||||||
// 跳转到问卷管理页面进行编辑
|
this.resetQuestionnaireForm()
|
||||||
this.$router.push({ path: '/psychology/questionnaire', query: { questionnaireId: questionnaireId, action: 'edit' } })
|
getQuestionnaire(questionnaireId).then(response => {
|
||||||
|
this.questionnaireForm = response.data
|
||||||
|
this.questionnaireOpen = true
|
||||||
|
this.questionnaireTitle = "修改问卷"
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("获取问卷信息失败:", error)
|
||||||
|
this.$modal.msgError("获取问卷信息失败")
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/** 提交问卷表单 */
|
||||||
|
submitQuestionnaireForm() {
|
||||||
|
this.$refs["questionnaireForm"].validate(valid => {
|
||||||
|
if (valid) {
|
||||||
|
if (this.questionnaireForm.questionnaireId != undefined) {
|
||||||
|
updateQuestionnaire(this.questionnaireForm).then(response => {
|
||||||
|
this.$modal.msgSuccess("修改成功")
|
||||||
|
this.questionnaireOpen = false
|
||||||
|
this.resetQuestionnaireForm()
|
||||||
|
// 刷新量表列表
|
||||||
|
this.getAllScaleList() // 刷新所有量表列表
|
||||||
|
this.getList()
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("修改问卷失败:", error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/** 取消问卷编辑 */
|
||||||
|
cancelQuestionnaire() {
|
||||||
|
this.questionnaireOpen = false
|
||||||
|
this.resetQuestionnaireForm()
|
||||||
|
},
|
||||||
|
/** 重置问卷表单 */
|
||||||
|
resetQuestionnaireForm() {
|
||||||
|
this.questionnaireForm = {
|
||||||
|
questionnaireId: undefined,
|
||||||
|
questionnaireCode: undefined,
|
||||||
|
questionnaireName: undefined,
|
||||||
|
questionnaireType: "custom",
|
||||||
|
paperType: "manual",
|
||||||
|
itemCount: 0,
|
||||||
|
totalScore: undefined,
|
||||||
|
passScore: undefined,
|
||||||
|
estimatedTime: undefined,
|
||||||
|
description: undefined,
|
||||||
|
status: "0",
|
||||||
|
sortOrder: 0,
|
||||||
|
remark: undefined
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs.questionnaireForm) {
|
||||||
|
this.$refs.questionnaireForm.clearValidate()
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
/** 问卷删除按钮操作 */
|
/** 问卷删除按钮操作 */
|
||||||
handleQuestionnaireDelete(row) {
|
handleQuestionnaireDelete(row) {
|
||||||
|
|
@ -926,6 +1166,7 @@ export default {
|
||||||
this.$modal.confirm('是否确认删除问卷"' + questionnaireName + '"的数据项?').then(() => {
|
this.$modal.confirm('是否确认删除问卷"' + questionnaireName + '"的数据项?').then(() => {
|
||||||
return delQuestionnaire(questionnaireId)
|
return delQuestionnaire(questionnaireId)
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
this.getAllScaleList() // 刷新所有量表列表
|
||||||
this.getList()
|
this.getList()
|
||||||
this.$modal.msgSuccess("删除成功")
|
this.$modal.msgSuccess("删除成功")
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,12 @@
|
||||||
{{ test.description }}
|
{{ test.description }}
|
||||||
</div>
|
</div>
|
||||||
<div class="test-action">
|
<div class="test-action">
|
||||||
<el-button type="primary" size="small" @click.stop="handleStartTest(test)">开始测试</el-button>
|
<el-button
|
||||||
|
:type="hasPaused(test.scaleId) ? 'warning' : 'primary'"
|
||||||
|
size="small"
|
||||||
|
@click.stop="handleStartTest(test)">
|
||||||
|
{{ hasPaused(test.scaleId) ? '继续测试' : '开始测试' }}
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
@ -63,7 +68,7 @@
|
||||||
* 作用:显示所有开放的心理测试题,学员可以选择进行测试
|
* 作用:显示所有开放的心理测试题,学员可以选择进行测试
|
||||||
*/
|
*/
|
||||||
import { listScale } from "@/api/psychology/scale"
|
import { listScale } from "@/api/psychology/scale"
|
||||||
import { startAssessment } from "@/api/psychology/assessment"
|
import { startAssessment, pausedAssessmentList, resumeAssessment } from "@/api/psychology/assessment"
|
||||||
import { Message } from 'element-ui'
|
import { Message } from 'element-ui'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -72,7 +77,8 @@ export default {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
testList: [],
|
testList: [],
|
||||||
searchText: ""
|
searchText: "",
|
||||||
|
pausedMap: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -97,16 +103,19 @@ export default {
|
||||||
// 当路由变为当前页面时,刷新列表
|
// 当路由变为当前页面时,刷新列表
|
||||||
if (to.path === '/student/tests') {
|
if (to.path === '/student/tests') {
|
||||||
this.loadTestList()
|
this.loadTestList()
|
||||||
|
this.loadPausedList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
// 加载测试题列表
|
// 加载测试题列表
|
||||||
this.loadTestList()
|
this.loadTestList()
|
||||||
|
this.loadPausedList()
|
||||||
},
|
},
|
||||||
// 页面激活时刷新列表(从测试页面返回时)
|
// 页面激活时刷新列表(从测试页面返回时)
|
||||||
activated() {
|
activated() {
|
||||||
this.loadTestList()
|
this.loadTestList()
|
||||||
|
this.loadPausedList()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 加载测试题列表
|
// 加载测试题列表
|
||||||
|
|
@ -132,6 +141,7 @@ export default {
|
||||||
})
|
})
|
||||||
|
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
this.loadPausedList()
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error("loadTestList, 加载测试题列表失败:", error)
|
console.error("loadTestList, 加载测试题列表失败:", error)
|
||||||
Message.error("加载测试题列表失败,请稍后重试")
|
Message.error("加载测试题列表失败,请稍后重试")
|
||||||
|
|
@ -139,6 +149,33 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loadPausedList() {
|
||||||
|
pausedAssessmentList()
|
||||||
|
.then(response => {
|
||||||
|
const rows = response.data || []
|
||||||
|
const map = {}
|
||||||
|
rows.forEach(item => {
|
||||||
|
if (!item || !item.scaleId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const existing = map[item.scaleId]
|
||||||
|
if (!existing) {
|
||||||
|
map[item.scaleId] = item
|
||||||
|
} else {
|
||||||
|
const existingTime = new Date(existing.pauseTime || existing.startTime || 0).getTime()
|
||||||
|
const currentTime = new Date(item.pauseTime || item.startTime || 0).getTime()
|
||||||
|
if (currentTime > existingTime) {
|
||||||
|
map[item.scaleId] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.pausedMap = map
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("加载暂停测评失败:", error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
// 搜索处理
|
// 搜索处理
|
||||||
handleSearch() {
|
handleSearch() {
|
||||||
// 搜索逻辑在computed中处理
|
// 搜索逻辑在computed中处理
|
||||||
|
|
@ -152,6 +189,28 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pausedRecord = this.getPausedRecord(test.scaleId)
|
||||||
|
if (pausedRecord) {
|
||||||
|
// 确认是否继续暂停的测评
|
||||||
|
this.$confirm('检测到您有该量表的暂停测评,是否继续?', '提示', {
|
||||||
|
confirmButtonText: '继续测评',
|
||||||
|
cancelButtonText: '重新开始',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
this.continueAssessment(pausedRecord)
|
||||||
|
}).catch(() => {
|
||||||
|
// 用户选择重新开始,先提示确认
|
||||||
|
this.$confirm('重新开始将清空之前的答题记录,确定要重新开始吗?', '警告', {
|
||||||
|
confirmButtonText: '确定重新开始',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
this.createNewAssessment(test)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 如果是问卷,跳转到问卷开始页面
|
// 如果是问卷,跳转到问卷开始页面
|
||||||
if (test.sourceType === 'questionnaire') {
|
if (test.sourceType === 'questionnaire') {
|
||||||
const questionnaireId = test.originalId || Math.abs(test.scaleId)
|
const questionnaireId = test.originalId || Math.abs(test.scaleId)
|
||||||
|
|
@ -166,6 +225,11 @@ export default {
|
||||||
this.createAssessment(test)
|
this.createAssessment(test)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 创建新测评(用于重新开始)
|
||||||
|
createNewAssessment(test) {
|
||||||
|
this.createAssessment(test)
|
||||||
|
},
|
||||||
|
|
||||||
// 创建测评
|
// 创建测评
|
||||||
createAssessment(test) {
|
createAssessment(test) {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|
@ -203,6 +267,33 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getPausedRecord(scaleId) {
|
||||||
|
return this.pausedMap[scaleId] || null
|
||||||
|
},
|
||||||
|
|
||||||
|
hasPaused(scaleId) {
|
||||||
|
return !!this.getPausedRecord(scaleId)
|
||||||
|
},
|
||||||
|
|
||||||
|
continueAssessment(record) {
|
||||||
|
if (!record || !record.assessmentId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
resumeAssessment(record.assessmentId)
|
||||||
|
.catch(() => {
|
||||||
|
// 即使恢复失败,也允许跳转到测评页面
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false
|
||||||
|
this.$router.push({
|
||||||
|
path: '/psychology/assessment/taking',
|
||||||
|
query: { assessmentId: record.assessmentId }
|
||||||
|
})
|
||||||
|
this.loadPausedList()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
// 获取量表类型名称
|
// 获取量表类型名称
|
||||||
getScaleTypeName(type) {
|
getScaleTypeName(type) {
|
||||||
// 这里可以根据实际需求返回类型名称
|
// 这里可以根据实际需求返回类型名称
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<!-- 心理档案信息 -->
|
<!-- 心理档案信息 -->
|
||||||
<el-divider content-position="left">心理档案信息</el-divider>
|
<el-divider content-position="left">心理档案信息</el-divider>
|
||||||
<el-row>
|
<el-row>
|
||||||
|
|
@ -251,7 +251,7 @@
|
||||||
|
|
||||||
<!-- 用户导入对话框 -->
|
<!-- 用户导入对话框 -->
|
||||||
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
|
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
|
||||||
<el-upload ref="upload" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :on-error="handleFileError" :auto-upload="false" drag>
|
<el-upload ref="upload" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
|
||||||
<i class="el-icon-upload"></i>
|
<i class="el-icon-upload"></i>
|
||||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
<div class="el-upload__tip text-center" slot="tip">
|
<div class="el-upload__tip text-center" slot="tip">
|
||||||
|
|
@ -666,14 +666,6 @@ export default {
|
||||||
this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
|
this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
|
||||||
this.getList()
|
this.getList()
|
||||||
},
|
},
|
||||||
// 文件上传失败处理
|
|
||||||
handleFileError(err, file, fileList) {
|
|
||||||
this.upload.isUploading = false
|
|
||||||
if (this.$refs.upload) {
|
|
||||||
this.$refs.upload.clearFiles()
|
|
||||||
}
|
|
||||||
this.$modal.msgError((err && (err.msg || err.message)) || "文件上传失败,请稍后重试或检查网络连接。")
|
|
||||||
},
|
|
||||||
// 提交上传文件
|
// 提交上传文件
|
||||||
submitFileForm() {
|
submitFileForm() {
|
||||||
const file = this.$refs.upload.uploadFiles
|
const file = this.$refs.upload.uploadFiles
|
||||||
|
|
@ -697,4 +689,4 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ module.exports = {
|
||||||
transpileDependencies: ['quill'],
|
transpileDependencies: ['quill'],
|
||||||
// webpack-dev-server 相关配置
|
// webpack-dev-server 相关配置
|
||||||
devServer: {
|
devServer: {
|
||||||
host: '0.0.0.0',
|
host: '127.0.0.1',
|
||||||
port: port,
|
port: port,
|
||||||
open: true,
|
open: true,
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user