1127bug
This commit is contained in:
parent
ef8c79f59f
commit
ef2f7da636
|
|
@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
|||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ddnai.common.annotation.Log;
|
||||
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.IPsyScaleItemService;
|
||||
import java.util.ArrayList;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 测评记录 信息操作处理
|
||||
|
|
@ -87,10 +87,33 @@ public class PsyAssessmentController extends BaseController
|
|||
* 获取暂停的测评列表
|
||||
*/
|
||||
@GetMapping("/pausedList")
|
||||
public AjaxResult pausedList()
|
||||
public AjaxResult pausedList(@RequestParam(value = "userId", required = false) Long userId)
|
||||
{
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
List<PsyAssessment> list = assessmentService.selectPausedAssessmentList(userId);
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.ddnai.web.controller.psychology;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
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)
|
||||
{
|
||||
Set<Long> allowedScaleIds = resolveAllowedScaleIdsForCurrentUser();
|
||||
Set<Long> restrictedScaleIds = resolveRestrictedScaleIds();
|
||||
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();
|
||||
|
|
@ -119,7 +119,7 @@ public class PsyScaleController extends BaseController
|
|||
scaleList.add(scaleItem);
|
||||
}
|
||||
|
||||
scaleList = filterScaleListByPermission(scaleList, allowedScaleIds);
|
||||
scaleList = filterScaleListByPermission(scaleList, allowedScaleIds, restrictedScaleIds);
|
||||
|
||||
// 按排序顺序和创建时间排序
|
||||
scaleList.sort((a, b) -> {
|
||||
|
|
@ -157,7 +157,7 @@ public class PsyScaleController extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
scaleList = filterScaleListByPermission(scaleList, allowedScaleIds);
|
||||
scaleList = filterScaleListByPermission(scaleList, allowedScaleIds, restrictedScaleIds);
|
||||
|
||||
if (needPermissionFilter)
|
||||
{
|
||||
|
|
@ -193,7 +193,6 @@ public class PsyScaleController extends BaseController
|
|||
scale.setRemark(questionnaire.getRemark());
|
||||
return scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建手动分页结果,适用于自定义过滤后的列表
|
||||
*/
|
||||
|
|
@ -224,7 +223,7 @@ public class PsyScaleController extends BaseController
|
|||
* @param scaleList 原始列表
|
||||
* @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)
|
||||
{
|
||||
|
|
@ -238,13 +237,16 @@ public class PsyScaleController extends BaseController
|
|||
continue;
|
||||
}
|
||||
String sourceType = scale.getSourceType();
|
||||
Long scaleId = scale.getScaleId();
|
||||
if ("questionnaire".equalsIgnoreCase(sourceType))
|
||||
{
|
||||
// 问卷类型默认对所有学员开放
|
||||
boolean restricted = restrictedScaleIds != null && restrictedScaleIds.contains(scaleId);
|
||||
if (!restricted)
|
||||
{
|
||||
filtered.add(scale);
|
||||
continue;
|
||||
}
|
||||
Long scaleId = scale.getScaleId();
|
||||
}
|
||||
if (scaleId != null && allowedScaleIds.contains(scaleId))
|
||||
{
|
||||
filtered.add(scale);
|
||||
|
|
@ -586,6 +588,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")
|
||||
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.StringUtils;
|
||||
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.service.ISysDeptService;
|
||||
import com.ddnai.system.service.ISysPostService;
|
||||
|
|
@ -280,4 +281,15 @@ public class PsyUserProfileController extends BaseController
|
|||
String message = profileService.importProfile(profileList, updateSupport, operName);
|
||||
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;
|
||||
|
||||
/** 来源类型(scale/questionnaire) */
|
||||
private String sourceType;
|
||||
|
||||
/** 部门名称(关联查询字段,不存储在表中) */
|
||||
private String deptName;
|
||||
|
||||
|
|
@ -155,6 +158,16 @@ public class PsyScalePermission extends BaseEntity
|
|||
this.scaleName = scaleName;
|
||||
}
|
||||
|
||||
public String getSourceType()
|
||||
{
|
||||
return sourceType;
|
||||
}
|
||||
|
||||
public void setSourceType(String sourceType)
|
||||
{
|
||||
this.sourceType = sourceType;
|
||||
}
|
||||
|
||||
public String getDeptName()
|
||||
{
|
||||
return deptName;
|
||||
|
|
@ -202,6 +215,8 @@ public class PsyScalePermission extends BaseEntity
|
|||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("remark", getRemark())
|
||||
.append("scaleName", getScaleName())
|
||||
.append("sourceType", getSourceType())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,5 +91,12 @@ public interface PsyScalePermissionMapper
|
|||
* @return 结果
|
||||
*/
|
||||
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.system.domain.psychology.PsyScalePermission;
|
||||
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.IPsyScaleService;
|
||||
import com.ddnai.system.service.psychology.IPsyQuestionnaireService;
|
||||
|
||||
/**
|
||||
* 量表权限 服务层实现
|
||||
|
|
@ -28,6 +30,9 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
|
|||
@Autowired
|
||||
private IPsyScaleService scaleService;
|
||||
|
||||
@Autowired
|
||||
private IPsyQuestionnaireService questionnaireService;
|
||||
|
||||
/**
|
||||
* 查询量表权限信息
|
||||
*
|
||||
|
|
@ -92,15 +97,7 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
|
|||
@Override
|
||||
public int insertPermission(PsyScalePermission permission)
|
||||
{
|
||||
// 验证 scale_id 是否存在
|
||||
if (permission.getScaleId() != null)
|
||||
{
|
||||
com.ddnai.system.domain.psychology.PsyScale scale = scaleService.selectScaleById(permission.getScaleId());
|
||||
if (scale == null)
|
||||
{
|
||||
throw new RuntimeException("量表不存在,scaleId: " + permission.getScaleId());
|
||||
}
|
||||
}
|
||||
validateScaleExists(permission.getScaleId());
|
||||
|
||||
if (permission.getStatus() == null)
|
||||
{
|
||||
|
|
@ -150,15 +147,7 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
|
|||
@Override
|
||||
public int updatePermission(PsyScalePermission permission)
|
||||
{
|
||||
// 验证 scale_id 是否存在
|
||||
if (permission.getScaleId() != null)
|
||||
{
|
||||
com.ddnai.system.domain.psychology.PsyScale scale = scaleService.selectScaleById(permission.getScaleId());
|
||||
if (scale == null)
|
||||
{
|
||||
throw new RuntimeException("量表不存在,scaleId: " + permission.getScaleId());
|
||||
}
|
||||
}
|
||||
validateScaleExists(permission.getScaleId());
|
||||
|
||||
return permissionMapper.updatePermission(permission);
|
||||
}
|
||||
|
|
@ -239,12 +228,10 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
|
|||
}
|
||||
|
||||
try {
|
||||
// 验证 scale_id 是否存在
|
||||
com.ddnai.system.domain.psychology.PsyScale scale = scaleService.selectScaleById(scaleId);
|
||||
if (scale == null)
|
||||
if (!validateScaleExistsSilent(scaleId))
|
||||
{
|
||||
log.warn("量表不存在,跳过该权限分配,scaleId: {}", scaleId);
|
||||
continue; // 跳过不存在的量表
|
||||
log.warn("量表/问卷不存在,跳过该权限分配,scaleId: {}", scaleId);
|
||||
continue;
|
||||
}
|
||||
|
||||
PsyScalePermission permission = new PsyScalePermission();
|
||||
|
|
@ -267,5 +254,35 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
|
|||
}
|
||||
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.system.domain.psychology.PsyUserProfile;
|
||||
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.ISysRoleService;
|
||||
import com.ddnai.system.service.ISysUserService;
|
||||
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
|
||||
private ISysRoleService roleService;
|
||||
|
||||
@Autowired
|
||||
private ImportProgressManager importProgressManager;
|
||||
|
||||
/**
|
||||
* 查询档案信息
|
||||
*
|
||||
|
|
@ -475,11 +480,14 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService
|
|||
{
|
||||
throw new ServiceException("导入用户档案数据不能为空!");
|
||||
}
|
||||
final String progressKey = importProgressManager.buildProfileKey(operName);
|
||||
importProgressManager.start(progressKey, profileList.size());
|
||||
int successNum = 0;
|
||||
int failureNum = 0;
|
||||
StringBuilder successMsg = new StringBuilder();
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
|
||||
try
|
||||
{
|
||||
for (PsyUserProfile profile : profileList)
|
||||
{
|
||||
try
|
||||
|
|
@ -492,6 +500,7 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService
|
|||
{
|
||||
failureNum++;
|
||||
failureMsg.append("<br/>").append(failureNum).append("、档案信息编号为空");
|
||||
importProgressManager.recordFailure(progressKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -503,6 +512,7 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService
|
|||
this.insertProfile(profile);
|
||||
successNum++;
|
||||
successMsg.append("<br/>").append(successNum).append("、信息编号 ").append(profile.getInfoNumber()).append(" 导入成功");
|
||||
importProgressManager.recordSuccess(progressKey);
|
||||
}
|
||||
else if (isUpdateSupport)
|
||||
{
|
||||
|
|
@ -513,11 +523,13 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService
|
|||
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)
|
||||
|
|
@ -525,20 +537,51 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService
|
|||
failureNum++;
|
||||
String msg = "<br/>" + failureNum + "、信息编号 " + profile.getInfoNumber() + " 导入失败:";
|
||||
failureMsg.append(msg).append(e.getMessage());
|
||||
importProgressManager.recordFailure(progressKey);
|
||||
log.error(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (successNum > 0)
|
||||
{
|
||||
successMsg.insert(0, "以下数据处理成功(共 " + successNum + " 条):");
|
||||
}
|
||||
|
||||
if (failureNum > 0)
|
||||
{
|
||||
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
|
||||
if (successNum > 0)
|
||||
{
|
||||
failureMsg.append("<br/><br/>").append(successMsg);
|
||||
}
|
||||
importProgressManager.finishFailure(progressKey, failureMsg.toString());
|
||||
throw new ServiceException(failureMsg.toString());
|
||||
}
|
||||
else
|
||||
|
||||
if (successNum > 0)
|
||||
{
|
||||
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
|
||||
String successMessage = "恭喜您,数据已全部导入成功!<br/>" + successMsg.toString();
|
||||
importProgressManager.finishSuccess(progressKey, successMessage);
|
||||
return successMessage;
|
||||
}
|
||||
return successMsg.toString();
|
||||
importProgressManager.finishSuccess(progressKey, "导入完成,但未检测到需要处理的数据。");
|
||||
return "导入完成,但未检测到需要处理的数据。";
|
||||
}
|
||||
catch (RuntimeException ex)
|
||||
{
|
||||
if (!(ex instanceof ServiceException))
|
||||
{
|
||||
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);
|
||||
|
||||
/**
|
||||
* 查询所有已配置权限的量表ID
|
||||
*
|
||||
* @return 量表ID集合
|
||||
*/
|
||||
public List<Long> selectAllScaleIdsWithPermission();
|
||||
|
||||
/**
|
||||
* 批量分配用户量表权限
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.ddnai.system.service.psychology;
|
||||
|
||||
import java.util.List;
|
||||
import com.ddnai.system.domain.dto.ImportProgress;
|
||||
import com.ddnai.system.domain.psychology.PsyUserProfile;
|
||||
|
||||
/**
|
||||
|
|
@ -75,5 +76,13 @@ public interface IPsyUserProfileService
|
|||
* @return 结果
|
||||
*/
|
||||
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">
|
||||
<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
|
||||
</select>
|
||||
|
||||
|
|
@ -349,4 +354,3 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
|
|
|
|||
|
|
@ -28,9 +28,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<sql id="selectPermissionVo">
|
||||
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,
|
||||
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
|
||||
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_role r on p.role_id = r.role_id
|
||||
left join sys_user u on p.user_id = u.user_id
|
||||
|
|
@ -175,4 +178,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
delete from psy_scale_permission where scale_id = #{scaleId}
|
||||
</delete>
|
||||
|
||||
<select id="selectAllScaleIdsWithPermission" resultType="Long">
|
||||
select distinct scale_id
|
||||
from psy_scale_permission
|
||||
where status = '0'
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
|
|
|||
|
|
@ -94,6 +94,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="phone != null and phone != ''">
|
||||
AND u.phonenumber like concat('%', #{phone}, '%')
|
||||
</if>
|
||||
<if test="prison != null and prison != ''">
|
||||
AND p.prison = #{prison}
|
||||
</if>
|
||||
<if test="prisonArea != null and prisonArea != ''">
|
||||
AND p.prison_area = #{prisonArea}
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND u.status = #{status}
|
||||
</if>
|
||||
|
|
@ -196,4 +202,3 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
</delete>
|
||||
|
||||
</mapper>
|
||||
|
||||
|
|
|
|||
|
|
@ -19,10 +19,11 @@ export function myAssessmentList(query) {
|
|||
}
|
||||
|
||||
// 查询暂停的测评列表
|
||||
export function pausedAssessmentList() {
|
||||
export function pausedAssessmentList(params) {
|
||||
return request({
|
||||
url: '/psychology/assessment/pausedList',
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,3 +93,11 @@ export function delUserInProfile(userIds) {
|
|||
})
|
||||
}
|
||||
|
||||
// 查询档案导入进度
|
||||
export function getProfileImportProgress() {
|
||||
return request({
|
||||
url: '/psychology/profile/importProgress',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,11 @@
|
|||
<el-table v-loading="loading" :data="assessmentList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<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="startTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
|
|
@ -147,7 +151,9 @@
|
|||
<el-descriptions :column="2" border style="margin-bottom: 20px;">
|
||||
<el-descriptions-item label="测评ID">{{ currentAssessment.assessmentId }}</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="提交时间">
|
||||
<span v-if="currentAssessment.submitTime">{{ parseTime(currentAssessment.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
<span v-else>-</span>
|
||||
|
|
|
|||
|
|
@ -110,6 +110,11 @@ export default {
|
|||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
'form.profileId'(newVal) {
|
||||
this.refreshPausedListByProfile(newVal)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 检查URL参数中是否有scaleId和profileId/userId
|
||||
const scaleId = this.$route.query.scaleId;
|
||||
|
|
@ -130,7 +135,8 @@ export default {
|
|||
|
||||
this.loadScales();
|
||||
this.loadProfiles();
|
||||
this.loadPaused();
|
||||
const initialPausedUserId = this.targetUserId ? parseInt(this.targetUserId) : this.$store.getters.id;
|
||||
this.loadPaused(initialPausedUserId);
|
||||
},
|
||||
methods: {
|
||||
/** 加载量表列表 */
|
||||
|
|
@ -285,11 +291,30 @@ export default {
|
|||
})
|
||||
},
|
||||
/** 加载暂停的测评 */
|
||||
loadPaused() {
|
||||
pausedAssessmentList().then(response => {
|
||||
loadPaused(targetUserId) {
|
||||
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 || [];
|
||||
});
|
||||
},
|
||||
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() {
|
||||
// 检查选中的是否是问卷
|
||||
|
|
@ -375,6 +400,15 @@ export default {
|
|||
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;
|
||||
// 找到选中的用户档案
|
||||
|
|
|
|||
|
|
@ -151,20 +151,7 @@ export default {
|
|||
return this.totalItems > 0 ? Math.round((this.currentIndex + 1) / this.totalItems * 100) : 0;
|
||||
},
|
||||
answeredCount() {
|
||||
// 计算已答题数量
|
||||
return this.itemList.filter(item => {
|
||||
const answer = this.answersMap[item.itemId];
|
||||
if (!answer) {
|
||||
return false;
|
||||
}
|
||||
// 单选题需要 optionId,多选题需要 optionIds(字符串,用逗号分隔)
|
||||
if (item.itemType === 'single') {
|
||||
return answer.optionId != null;
|
||||
} else if (item.itemType === 'multiple') {
|
||||
return answer.optionIds != null && answer.optionIds.trim().length > 0;
|
||||
}
|
||||
return true;
|
||||
}).length;
|
||||
return this.itemList.filter(item => this.isItemAnswered(item, this.answersMap[item.itemId])).length;
|
||||
},
|
||||
isComplete() {
|
||||
if (this.itemList.length === 0) {
|
||||
|
|
@ -310,6 +297,7 @@ export default {
|
|||
answerScore: answer.answerScore
|
||||
};
|
||||
});
|
||||
this.currentIndex = this.determineResumeIndex();
|
||||
|
||||
// 加载所有题目的选项
|
||||
this.loadAllOptions().then(() => {
|
||||
|
|
@ -403,6 +391,34 @@ export default {
|
|||
this.loadCurrentAnswer();
|
||||
}
|
||||
},
|
||||
isItemAnswered(item, answer) {
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
if (!answer) {
|
||||
return false;
|
||||
}
|
||||
if (item.itemType === 'single') {
|
||||
return answer.optionId != null && answer.optionId !== undefined;
|
||||
}
|
||||
if (item.itemType === 'multiple') {
|
||||
return answer.optionIds != null && String(answer.optionIds).trim().length > 0;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
determineResumeIndex() {
|
||||
if (!this.itemList || this.itemList.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
for (let i = 0; i < this.itemList.length; i++) {
|
||||
const item = this.itemList[i];
|
||||
const answered = this.isItemAnswered(item, this.answersMap[item.itemId]);
|
||||
if (!answered) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return this.itemList.length - 1;
|
||||
},
|
||||
/** 加载当前题目的答案 */
|
||||
loadCurrentAnswer() {
|
||||
if (!this.currentItem) {
|
||||
|
|
|
|||
|
|
@ -6,15 +6,34 @@
|
|||
<el-option
|
||||
v-for="scale in scaleList"
|
||||
:key="scale.scaleId"
|
||||
:label="scale.scaleName"
|
||||
:label="formatScaleLabel(scale)"
|
||||
: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-select>
|
||||
</el-form-item>
|
||||
<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
|
||||
v-for="user in userList"
|
||||
v-for="user in userOptions"
|
||||
:key="user.userId"
|
||||
:label="user.nickName ? `${user.nickName}(${user.userName})` : user.userName"
|
||||
:value="Number(user.userId)">
|
||||
|
|
@ -45,7 +64,17 @@
|
|||
|
||||
<el-table v-loading="loading" :data="permissionList" @selection-change="handleSelectionChange">
|
||||
<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">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.hasAllUsers">
|
||||
|
|
@ -129,6 +158,15 @@
|
|||
</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" />
|
||||
|
|
@ -201,13 +239,13 @@
|
|||
@keyup.enter.native="handleUserQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="监区" prop="deptId">
|
||||
<el-select v-model="userQueryParams.deptId" placeholder="请选择监区" clearable style="width: 200px">
|
||||
<el-form-item label="监区" prop="prisonArea">
|
||||
<el-select v-model="userQueryParams.prisonArea" placeholder="请选择监区" clearable filterable style="width: 200px">
|
||||
<el-option
|
||||
v-for="dept in deptOptions"
|
||||
:key="dept.id"
|
||||
:label="dept.label"
|
||||
:value="dept.id">
|
||||
v-for="area in prisonAreaOptions"
|
||||
:key="area"
|
||||
:label="area"
|
||||
:value="area">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
|
@ -220,13 +258,14 @@
|
|||
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="deptName" :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>
|
||||
|
|
@ -256,7 +295,6 @@ import { allocatedUserList } from "@/api/system/role";
|
|||
import { listRole } from "@/api/system/role";
|
||||
import { getUser } from "@/api/system/user";
|
||||
import { listProfile } from "@/api/psychology/profile";
|
||||
import { deptTreeSelect } from "@/api/system/user";
|
||||
|
||||
export default {
|
||||
name: "PsyScalePermission",
|
||||
|
|
@ -297,15 +335,17 @@ export default {
|
|||
userSelectTotal: 0,
|
||||
userSelectLoading: false,
|
||||
selectedUserIds: [],
|
||||
// 部门选项
|
||||
deptOptions: [],
|
||||
cachedFilteredUsers: [],
|
||||
updatingSelection: false,
|
||||
// 监区选项
|
||||
prisonAreaOptions: [],
|
||||
// 用户查询参数
|
||||
userQueryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
pageSize: 8,
|
||||
infoNumber: undefined,
|
||||
userName: undefined,
|
||||
deptId: undefined,
|
||||
prisonArea: undefined,
|
||||
status: '0'
|
||||
},
|
||||
// 查询参数
|
||||
|
|
@ -375,6 +415,8 @@ export default {
|
|||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.cachedFilteredUsers = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -382,9 +424,16 @@ export default {
|
|||
this.getList();
|
||||
this.loadScales();
|
||||
this.loadUsers();
|
||||
this.loadDeptTree();
|
||||
this.loadPrisonAreaOptions();
|
||||
},
|
||||
methods: {
|
||||
formatScaleLabel(scale) {
|
||||
if (!scale) {
|
||||
return ""
|
||||
}
|
||||
const prefix = scale.sourceType === 'questionnaire' ? '[问卷]' : '[量表]'
|
||||
return `${prefix} ${scale.scaleName || ''}`
|
||||
},
|
||||
/** 查询权限列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
|
|
@ -472,11 +521,16 @@ export default {
|
|||
},
|
||||
/** 加载量表列表(显示所有量表,不包含问卷) */
|
||||
loadScales() {
|
||||
// 不按状态过滤,并一次性拉取足够数量,确保所有量表都能配置权限
|
||||
listScale({ includeQuestionnaire: false, pageNum: 1, pageSize: 1000 }).then(response => {
|
||||
// 过滤掉问卷,只保留量表
|
||||
this.scaleList = (response.rows || [])
|
||||
.filter(scale => !scale.sourceType || scale.sourceType === 'scale');
|
||||
// 不按状态过滤,并一次性拉取足够数量,确保所有量表/问卷都能配置权限
|
||||
listScale({ includeQuestionnaire: true, pageNum: 1, pageSize: 1000 }).then(response => {
|
||||
this.scaleList = (response.rows || []).filter(scale => {
|
||||
if (!scale) return false
|
||||
// 问卷需要有题目
|
||||
if (scale.sourceType === 'questionnaire') {
|
||||
return true
|
||||
}
|
||||
return true
|
||||
})
|
||||
});
|
||||
},
|
||||
/** 加载用户列表(只加载学员角色的用户)- 改为远程搜索模式,不预加载 */
|
||||
|
|
@ -493,10 +547,19 @@ export default {
|
|||
console.error("获取学员角色ID失败:", error);
|
||||
});
|
||||
},
|
||||
/** 加载部门树 */
|
||||
loadDeptTree() {
|
||||
deptTreeSelect().then(response => {
|
||||
this.deptOptions = response.data || [];
|
||||
/** 加载监区下拉选项 */
|
||||
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);
|
||||
});
|
||||
},
|
||||
/** 量表行点击 */
|
||||
|
|
@ -507,6 +570,16 @@ export default {
|
|||
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;
|
||||
|
|
@ -516,20 +589,16 @@ export default {
|
|||
pageSize: this.userQueryParams.pageSize,
|
||||
infoNumber: this.userQueryParams.infoNumber,
|
||||
userName: this.userQueryParams.userName,
|
||||
deptId: this.userQueryParams.deptId,
|
||||
prisonArea: this.userQueryParams.prisonArea,
|
||||
status: this.userQueryParams.status
|
||||
};
|
||||
return listProfile(query).then(response => {
|
||||
this.userSelectList = (response.rows || []).map(profile => ({
|
||||
userId: profile.userId,
|
||||
infoNumber: profile.infoNumber,
|
||||
userName: profile.userName,
|
||||
userAccount: profile.infoNumber, // 使用信息编号作为账号显示
|
||||
deptName: profile.deptName,
|
||||
status: profile.status || '0'
|
||||
}));
|
||||
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;
|
||||
|
|
@ -539,6 +608,7 @@ export default {
|
|||
/** 用户查询 */
|
||||
handleUserQuery() {
|
||||
this.userQueryParams.pageNum = 1;
|
||||
this.cachedFilteredUsers = [];
|
||||
this.getUserSelectList();
|
||||
},
|
||||
/** 重置用户查询 */
|
||||
|
|
@ -546,17 +616,99 @@ export default {
|
|||
this.resetForm("userQueryForm");
|
||||
this.userQueryParams = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
pageSize: 8,
|
||||
infoNumber: undefined,
|
||||
userName: undefined,
|
||||
deptId: undefined,
|
||||
prisonArea: undefined,
|
||||
status: '0'
|
||||
};
|
||||
this.handleUserQuery();
|
||||
},
|
||||
/** 用户选择改变 */
|
||||
handleUserSelectionChange(selection) {
|
||||
this.selectedUserIds = selection.map(item => item.userId);
|
||||
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() {
|
||||
|
|
@ -564,7 +716,9 @@ export default {
|
|||
this.form.userIds = [...this.selectedUserIds];
|
||||
// 确保选中的用户在 userOptions 中
|
||||
this.selectedUserIds.forEach(userId => {
|
||||
const user = this.userSelectList.find(u => u.userId === 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,
|
||||
|
|
@ -574,6 +728,22 @@ export default {
|
|||
}
|
||||
});
|
||||
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;
|
||||
});
|
||||
},
|
||||
/** 搜索用户(远程搜索) */
|
||||
searchUsers(keyword) {
|
||||
|
|
@ -777,6 +947,7 @@ export default {
|
|||
};
|
||||
this.scaleSearchKeyword = "";
|
||||
this.selectedUserIds = [];
|
||||
this.cachedFilteredUsers = [];
|
||||
this.resetForm("form");
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
|
|
|
|||
|
|
@ -9,15 +9,6 @@
|
|||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- 用户ID已隐藏,只使用信息编号 -->
|
||||
<el-form-item label="手机号码" prop="phone">
|
||||
<el-input
|
||||
v-model="queryParams.phone"
|
||||
placeholder="请输入手机号码"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="档案状态" clearable>
|
||||
<el-option label="在押" value="0" />
|
||||
|
|
@ -26,12 +17,36 @@
|
|||
<el-option label="假释" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="档案类型" prop="profileType">
|
||||
<el-select v-model="queryParams.profileType" placeholder="档案类型" clearable>
|
||||
<el-option label="标准" value="standard" />
|
||||
<el-option label="儿童" value="child" />
|
||||
<el-option label="成人" value="adult" />
|
||||
<el-option label="老年" value="senior" />
|
||||
<el-form-item label="监狱" prop="prison">
|
||||
<el-select
|
||||
v-model="queryParams.prison"
|
||||
placeholder="请选择监狱"
|
||||
clearable
|
||||
filterable
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in filterPrisonOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="监区" prop="prisonArea">
|
||||
<el-select
|
||||
v-model="queryParams.prisonArea"
|
||||
placeholder="请选择监区"
|
||||
clearable
|
||||
filterable
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in filterPrisonAreaOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="信息编号" prop="infoNumber">
|
||||
|
|
@ -392,27 +407,39 @@
|
|||
|
||||
<!-- 用户档案导入对话框 -->
|
||||
<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" :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>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<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>
|
||||
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
|
||||
</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>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="upload.open = false">取 消</el-button>
|
||||
<el-button type="primary" :loading="upload.isUploading" @click="submitFileForm">确 定</el-button>
|
||||
<el-button :disabled="upload.isUploading" @click="upload.open = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listProfile, getProfile, getProfileByUserId, delProfile, addProfile, updateProfile, getUserInfo, addUserInProfile, getUserInfoById, updateUserInProfile, delUserInProfile } from "@/api/psychology/profile"
|
||||
import { listProfile, getProfile, getProfileByUserId, delProfile, addProfile, updateProfile, getUserInfo, addUserInProfile, getUserInfoById, updateUserInProfile, delUserInProfile, getProfileImportProgress } from "@/api/psychology/profile"
|
||||
import { deptTreeSelect } from "@/api/system/user"
|
||||
import { allocatedUserList } from "@/api/system/role"
|
||||
import { listRole } from "@/api/system/role"
|
||||
|
|
@ -458,6 +485,9 @@ export default {
|
|||
postOptions: [],
|
||||
// 角色选项
|
||||
roleOptions: [],
|
||||
// 监狱/监区筛选项
|
||||
filterPrisonOptions: [],
|
||||
filterPrisonAreaOptions: [],
|
||||
// 默认密码
|
||||
initPassword: undefined,
|
||||
// 用户表单参数
|
||||
|
|
@ -468,12 +498,12 @@ export default {
|
|||
pageSize: 10,
|
||||
userName: undefined,
|
||||
userId: undefined,
|
||||
phone: undefined,
|
||||
status: undefined,
|
||||
profileType: undefined,
|
||||
idCard: undefined,
|
||||
infoNumber: undefined,
|
||||
deptId: undefined
|
||||
deptId: undefined,
|
||||
prison: undefined,
|
||||
prisonArea: undefined
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
|
|
@ -538,13 +568,23 @@ export default {
|
|||
// 设置上传的请求头部
|
||||
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() {
|
||||
this.getStudentRoleId().then(() => {
|
||||
this.getList()
|
||||
this.loadFilterOptions()
|
||||
})
|
||||
this.getDeptTree()
|
||||
this.getConfigKey("sys.user.initPassword").then(response => {
|
||||
|
|
@ -691,6 +731,41 @@ export default {
|
|||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 加载监狱/监区筛选项 */
|
||||
loadFilterOptions() {
|
||||
const params = {
|
||||
pageNum: 1,
|
||||
pageSize: 10000
|
||||
}
|
||||
listProfile(params).then(response => {
|
||||
const rows = response.rows || []
|
||||
const handleRows = filteredRows => {
|
||||
const prisonSet = new Set()
|
||||
const prisonAreaSet = new Set()
|
||||
filteredRows.forEach(row => {
|
||||
if (row && row.prison) {
|
||||
prisonSet.add(row.prison)
|
||||
}
|
||||
if (row && row.prisonArea) {
|
||||
prisonAreaSet.add(row.prisonArea)
|
||||
}
|
||||
})
|
||||
this.filterPrisonOptions = Array.from(prisonSet)
|
||||
this.filterPrisonAreaOptions = Array.from(prisonAreaSet)
|
||||
}
|
||||
if (this.studentRoleId) {
|
||||
this.filterStudentUsers(rows).then(filteredRows => {
|
||||
handleRows(filteredRows)
|
||||
}).catch(() => {
|
||||
handleRows(rows)
|
||||
})
|
||||
} else {
|
||||
handleRows(rows)
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error("加载监狱筛选项失败:", error)
|
||||
})
|
||||
},
|
||||
/** 过滤学员用户 */
|
||||
filterStudentUsers(rows) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
@ -1160,6 +1235,7 @@ export default {
|
|||
/** 导入按钮操作 */
|
||||
handleImport() {
|
||||
this.upload.title = "用户档案导入"
|
||||
this.resetUploadProgress()
|
||||
this.upload.open = true
|
||||
},
|
||||
/** 下载模板操作 */
|
||||
|
|
@ -1167,16 +1243,52 @@ export default {
|
|||
window.location.href = process.env.VUE_APP_BASE_API + '/psychology/profile/importTemplate'
|
||||
},
|
||||
// 文件上传中处理
|
||||
handleFileUploadProgress(event, file, fileList) {
|
||||
handleFileUploadProgress(event) {
|
||||
if (this.upload.stage !== "uploading") {
|
||||
return
|
||||
}
|
||||
this.upload.isUploading = true
|
||||
this.upload.showProgress = true
|
||||
const percent = Math.round(event.percent || 0)
|
||||
this.upload.progress = Math.min(Math.max(percent, 0), 100)
|
||||
this.upload.statusText = this.upload.progress < 100 ? "正在上传文件..." : "上传完成,正在提交数据..."
|
||||
},
|
||||
// 文件上传成功处理
|
||||
handleFileSuccess(response, file, fileList) {
|
||||
this.upload.open = false
|
||||
handleFileSuccess(response) {
|
||||
const result = this.normalizeUploadResponse(response)
|
||||
this.upload.progress = 100
|
||||
this.upload.statusText = "导入完成,正在刷新列表..."
|
||||
this.upload.isUploading = false
|
||||
this.upload.open = false
|
||||
this.stopImportProgressPolling()
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.upload) {
|
||||
this.$refs.upload.clearFiles()
|
||||
this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
|
||||
}
|
||||
})
|
||||
const message = result.msg || result.message || "导入完成"
|
||||
this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + message + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
|
||||
this.resetUploadProgress()
|
||||
this.getList()
|
||||
this.loadFilterOptions()
|
||||
},
|
||||
// 文件上传失败处理
|
||||
handleFileError(err, file) {
|
||||
const responsePayload = (file && file.response) || (err && err.message)
|
||||
const result = this.normalizeUploadResponse(responsePayload)
|
||||
const message = result.msg || result.message || (typeof result === 'string' ? result : "导入失败,请稍后重试")
|
||||
this.upload.isUploading = false
|
||||
this.upload.statusText = "导入失败"
|
||||
this.upload.open = false
|
||||
this.stopImportProgressPolling()
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.upload) {
|
||||
this.$refs.upload.clearFiles()
|
||||
}
|
||||
})
|
||||
this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + message + "</div>", "导入结果", { dangerouslyUseHTMLString: true, type: 'error' })
|
||||
this.resetUploadProgress()
|
||||
this.loadFilterOptions()
|
||||
},
|
||||
// 提交上传文件
|
||||
submitFileForm() {
|
||||
|
|
@ -1185,7 +1297,78 @@ export default {
|
|||
this.$modal.msgError('请选择后缀为 "xls"或"xlsx"的文件。')
|
||||
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.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) {
|
||||
|
|
@ -1320,5 +1503,15 @@ export default {
|
|||
.app-container {
|
||||
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>
|
||||
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@
|
|||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</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">
|
||||
<el-button
|
||||
size="mini"
|
||||
|
|
|
|||
|
|
@ -3,33 +3,30 @@
|
|||
<el-card class="user-selector" shadow="never">
|
||||
<el-form :inline="true" size="small">
|
||||
<el-form-item label="选择用户">
|
||||
<el-select
|
||||
v-model="selectedUserId"
|
||||
style="width: 280px"
|
||||
<el-autocomplete
|
||||
v-model="userSearchKeyword"
|
||||
style="width: 320px"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="searchUsers"
|
||||
:loading="userOptionsLoading"
|
||||
placeholder="输入姓名或账号搜索"
|
||||
@change="handleUserChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.userId"
|
||||
:label="buildUserLabel(item)"
|
||||
:value="item.userId"
|
||||
:fetch-suggestions="searchUsers"
|
||||
placeholder="输入姓名或信息编号搜索"
|
||||
value-key="value"
|
||||
:trigger-on-focus="false"
|
||||
:debounce="400"
|
||||
@select="handleUserSelect"
|
||||
>
|
||||
<template slot-scope="{ item }">
|
||||
<div class="user-option">
|
||||
<span class="name">{{ item.nickName || item.userName }}</span>
|
||||
<span class="dept">{{ item.deptName || '未分配单位' }}</span>
|
||||
<span class="dept">{{ item.infoNumber ? `编号:${item.infoNumber}` : '' }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-autocomplete>
|
||||
</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 icon="el-icon-refresh" @click="resetSelection">重置</el-button>
|
||||
|
|
@ -76,7 +73,17 @@
|
|||
empty-text="暂无可用的测评报告"
|
||||
>
|
||||
<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 label="测评时间" width="200" align="center">
|
||||
<template slot-scope="scope">
|
||||
|
|
@ -107,6 +114,12 @@
|
|||
<div v-else class="empty-report">报告生成中,请稍候...</div>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<el-button
|
||||
type="success"
|
||||
icon="el-icon-printer"
|
||||
:disabled="!comprehensiveReport"
|
||||
@click="printReport"
|
||||
>打印报告</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
|
|
@ -120,8 +133,8 @@
|
|||
|
||||
<script>
|
||||
import { getUserAssessmentSummary, getStudentOptions } from '@/api/psychology/assessment'
|
||||
import { getProfileByUserId } from '@/api/psychology/profile'
|
||||
import { getReport } from '@/api/psychology/report'
|
||||
import { getProfileByUserId, listProfile } from '@/api/psychology/profile'
|
||||
import { getReport, listReport } from '@/api/psychology/report'
|
||||
import { parseTime } from '@/utils/ruoyi'
|
||||
import axios from 'axios'
|
||||
|
||||
|
|
@ -130,8 +143,9 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
selectedUserId: undefined,
|
||||
userOptions: [],
|
||||
userOptionsLoading: false,
|
||||
userSearchKeyword: '',
|
||||
userSearchLoading: false,
|
||||
cachedUserOptions: [],
|
||||
userProfile: null,
|
||||
userSummary: null,
|
||||
reportOptions: [],
|
||||
|
|
@ -145,17 +159,40 @@ export default {
|
|||
}
|
||||
},
|
||||
created() {
|
||||
this.searchUsers('')
|
||||
},
|
||||
methods: {
|
||||
searchUsers(query) {
|
||||
this.userOptionsLoading = true
|
||||
getStudentOptions({ keyword: query, limit: 20 })
|
||||
.then((res) => {
|
||||
this.userOptions = res.data || []
|
||||
searchUsers(query, cb) {
|
||||
this.fetchUserOptions(query)
|
||||
.then((list) => cb(list))
|
||||
.catch(() => cb([]))
|
||||
},
|
||||
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(() => [])
|
||||
}
|
||||
return Promise.all([studentPromise, profilePromise])
|
||||
.then(([studentList, profileList]) => {
|
||||
const merged = this.mergeUserOptions([...studentList, ...profileList])
|
||||
this.cachedUserOptions = merged
|
||||
return merged
|
||||
})
|
||||
.finally(() => {
|
||||
this.userOptionsLoading = false
|
||||
this.userSearchLoading = false
|
||||
})
|
||||
},
|
||||
buildUserLabel(option) {
|
||||
|
|
@ -163,13 +200,72 @@ export default {
|
|||
return ''
|
||||
}
|
||||
const name = option.nickName || option.userName || ''
|
||||
const info = option.infoNumber ? `(编号:${option.infoNumber})` : ''
|
||||
const dept = option.deptName ? ` - ${option.deptName}` : ''
|
||||
return name + dept
|
||||
return `${name}${info}${dept}`
|
||||
},
|
||||
handleUserChange() {
|
||||
if (!this.selectedUserId) {
|
||||
this.resetSelection()
|
||||
handleUserSelect(option) {
|
||||
if (!option || !option.userId) {
|
||||
return
|
||||
}
|
||||
this.selectedUserId = option.userId
|
||||
this.userSearchKeyword = this.buildUserLabel(option)
|
||||
},
|
||||
handleManualSearch() {
|
||||
const keyword = (this.userSearchKeyword || '').trim()
|
||||
if (!keyword) {
|
||||
this.$message.warning('请输入姓名或信息编号')
|
||||
return
|
||||
}
|
||||
this.fetchUserOptions(keyword).then((list) => {
|
||||
if (!list.length) {
|
||||
this.$message.warning('未找到匹配的用户')
|
||||
return
|
||||
}
|
||||
if (list.length === 1) {
|
||||
this.handleUserSelect(list[0])
|
||||
} else {
|
||||
this.$message.info('找到多条记录,请从下拉列表选择具体用户')
|
||||
}
|
||||
})
|
||||
},
|
||||
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)
|
||||
}
|
||||
})
|
||||
return Array.from(map.values())
|
||||
},
|
||||
async loadUserData() {
|
||||
if (!this.selectedUserId) {
|
||||
|
|
@ -184,7 +280,7 @@ export default {
|
|||
|
||||
// 转换量表数据
|
||||
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 reportRows = attempts
|
||||
.filter((attempt) => attempt && attempt.reportId)
|
||||
|
|
@ -198,10 +294,18 @@ export default {
|
|||
submitTime: attempt.submitTime || attempt.startTime,
|
||||
totalScore: attempt.totalScore,
|
||||
summary: attempt.reportSummary || '',
|
||||
status: attempt.status
|
||||
status: attempt.status,
|
||||
sourceType: 'assessment'
|
||||
}))
|
||||
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.$nextTick(() => {
|
||||
if (this.$refs.reportTable && this.$refs.reportTable.doLayout) {
|
||||
|
|
@ -226,12 +330,44 @@ export default {
|
|||
},
|
||||
resetSelection() {
|
||||
this.selectedUserId = undefined
|
||||
this.userSearchKeyword = ''
|
||||
this.userProfile = null
|
||||
this.userSummary = null
|
||||
this.reportOptions = []
|
||||
this.selectedReports = []
|
||||
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) {
|
||||
if (!value) return '-'
|
||||
return parseTime(value)
|
||||
|
|
@ -294,18 +430,20 @@ export default {
|
|||
continue
|
||||
}
|
||||
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) {
|
||||
reports.push({
|
||||
scaleName: row.scaleName,
|
||||
scaleName: row.scaleName || row.reportTitle || '问卷报告',
|
||||
submitTime: row.submitTime,
|
||||
totalScore: row.totalScore,
|
||||
summary: response.data.summary || row.summary || '',
|
||||
content: response.data.reportContent || ''
|
||||
content: response.data.reportContent || '',
|
||||
sourceType
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`获取量表 ${row.scaleName} 的报告失败:`, error)
|
||||
console.warn(`获取${row.sourceType === 'questionnaire' ? '问卷' : '量表'} ${row.scaleName} 的报告失败:`, error)
|
||||
}
|
||||
}
|
||||
return reports
|
||||
|
|
@ -357,12 +495,13 @@ export default {
|
|||
|
||||
const scaleReportsText = scaleReports
|
||||
.map((report, index) => {
|
||||
const typeLabel = report.sourceType === 'questionnaire' ? '问卷' : '量表'
|
||||
const contentText = report.content
|
||||
.replace(/<[^>]*>/g, '')
|
||||
.replace(/ /g, ' ')
|
||||
.substring(0, 500)
|
||||
return `
|
||||
量表${index + 1}:${report.scaleName}
|
||||
${typeLabel}${index + 1}:${report.scaleName}
|
||||
测评时间:${this.formatDateTime(report.submitTime)}
|
||||
总分:${report.totalScore}
|
||||
报告摘要:${report.summary || '无'}
|
||||
|
|
@ -449,7 +588,8 @@ export default {
|
|||
</div>
|
||||
|
||||
<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;">被评估人____________________</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
|
@ -477,13 +617,8 @@ export default {
|
|||
|
||||
return html
|
||||
},
|
||||
exportReport(format) {
|
||||
if (!this.comprehensiveReport) {
|
||||
this.$message.warning('报告内容为空')
|
||||
return
|
||||
}
|
||||
|
||||
const reportHtml = `
|
||||
buildReportHtml() {
|
||||
return `
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
|
@ -505,6 +640,14 @@ export default {
|
|||
</body>
|
||||
</html>
|
||||
`
|
||||
},
|
||||
exportReport(format) {
|
||||
if (!this.comprehensiveReport) {
|
||||
this.$message.warning('报告内容为空')
|
||||
return
|
||||
}
|
||||
|
||||
const reportHtml = this.buildReportHtml()
|
||||
|
||||
if (format === 'word') {
|
||||
const blob = new Blob(['\ufeff', reportHtml], { type: 'application/msword' })
|
||||
|
|
@ -518,6 +661,15 @@ export default {
|
|||
window.URL.revokeObjectURL(link.href)
|
||||
this.$message.success('导出成功')
|
||||
} else {
|
||||
this.printReport()
|
||||
}
|
||||
},
|
||||
printReport() {
|
||||
if (!this.comprehensiveReport) {
|
||||
this.$message.warning('报告内容为空')
|
||||
return
|
||||
}
|
||||
const reportHtml = this.buildReportHtml()
|
||||
const printWindow = window.open('', '_blank')
|
||||
if (!printWindow) {
|
||||
this.$message.error('无法打开打印窗口,请检查浏览器是否阻止了弹窗')
|
||||
|
|
@ -529,7 +681,6 @@ export default {
|
|||
printWindow.print()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="状态" clearable>
|
||||
<el-option
|
||||
v-for="dict in dict.type.psy_scale_status"
|
||||
v-for="dict in scaleStatusOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
|
|
@ -513,6 +513,17 @@ export default {
|
|||
seen.add(dict.value);
|
||||
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" }
|
||||
];
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,12 @@
|
|||
{{ test.description }}
|
||||
</div>
|
||||
<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>
|
||||
</el-card>
|
||||
|
|
@ -63,7 +68,7 @@
|
|||
* 作用:显示所有开放的心理测试题,学员可以选择进行测试
|
||||
*/
|
||||
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'
|
||||
|
||||
export default {
|
||||
|
|
@ -72,7 +77,8 @@ export default {
|
|||
return {
|
||||
loading: false,
|
||||
testList: [],
|
||||
searchText: ""
|
||||
searchText: "",
|
||||
pausedMap: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -97,16 +103,19 @@ export default {
|
|||
// 当路由变为当前页面时,刷新列表
|
||||
if (to.path === '/student/tests') {
|
||||
this.loadTestList()
|
||||
this.loadPausedList()
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 加载测试题列表
|
||||
this.loadTestList()
|
||||
this.loadPausedList()
|
||||
},
|
||||
// 页面激活时刷新列表(从测试页面返回时)
|
||||
activated() {
|
||||
this.loadTestList()
|
||||
this.loadPausedList()
|
||||
},
|
||||
methods: {
|
||||
// 加载测试题列表
|
||||
|
|
@ -132,6 +141,7 @@ export default {
|
|||
})
|
||||
|
||||
this.loading = false
|
||||
this.loadPausedList()
|
||||
}).catch(error => {
|
||||
console.error("loadTestList, 加载测试题列表失败:", 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() {
|
||||
// 搜索逻辑在computed中处理
|
||||
|
|
@ -152,6 +189,12 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
const pausedRecord = this.getPausedRecord(test.scaleId)
|
||||
if (pausedRecord) {
|
||||
this.continueAssessment(pausedRecord)
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是问卷,跳转到问卷开始页面
|
||||
if (test.sourceType === 'questionnaire') {
|
||||
const questionnaireId = test.originalId || Math.abs(test.scaleId)
|
||||
|
|
@ -203,6 +246,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) {
|
||||
// 这里可以根据实际需求返回类型名称
|
||||
|
|
|
|||
|
|
@ -118,13 +118,13 @@
|
|||
<el-input v-model="form.nickName" placeholder="请输入用户昵称/姓名" maxlength="30" @input="handleNickNameInput" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" v-if="!simpleCreateMode">
|
||||
<el-form-item label="信息编号" prop="infoNumber">
|
||||
<el-input v-model="form.infoNumber" placeholder="请输入信息编号(仅数字)" @input="handleInfoNumberInput" maxlength="20" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-row v-if="!simpleCreateMode">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号码" prop="phonenumber">
|
||||
<el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" />
|
||||
|
|
@ -137,14 +137,14 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" v-if="!simpleCreateMode">
|
||||
<el-form-item label="性别" prop="sex">
|
||||
<el-select v-model="form.sex" placeholder="请选择性别">
|
||||
<el-option v-for="dict in dict.type.sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="simpleCreateMode ? 24 : 12">
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio v-for="dict in dict.type.sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
|
||||
|
|
@ -152,7 +152,15 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="归属部门" prop="deptId">
|
||||
<treeselect v-model="form.deptId" :options="enabledDeptOptions" :show-count="true" placeholder="请选择归属部门" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<template v-if="!simpleCreateMode">
|
||||
<!-- 心理档案信息 -->
|
||||
<el-divider content-position="left">心理档案信息</el-divider>
|
||||
<el-row>
|
||||
|
|
@ -197,13 +205,6 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="归属部门" prop="deptId">
|
||||
<treeselect v-model="form.deptId" :options="enabledDeptOptions" :show-count="true" placeholder="请选择归属部门" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="地址" prop="address">
|
||||
<el-input v-model="form.address" type="textarea" :rows="2" placeholder="请输入地址" />
|
||||
</el-form-item>
|
||||
|
|
@ -221,18 +222,19 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<!-- 系统权限 -->
|
||||
<el-divider content-position="left">系统权限</el-divider>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" v-if="!simpleCreateMode">
|
||||
<el-form-item label="岗位">
|
||||
<el-select v-model="form.postIds" multiple placeholder="请选择岗位">
|
||||
<el-option v-for="item in postOptions" :key="item.postId" :label="item.postName" :value="item.postId" :disabled="item.status == 1" ></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="simpleCreateMode ? 24 : 12">
|
||||
<el-form-item label="角色">
|
||||
<el-select v-model="form.roleIds" multiple placeholder="请选择角色">
|
||||
<el-option v-for="item in roleOptions" :key="item.roleId" :label="item.roleName" :value="item.roleId" :disabled="item.status == 1"></el-option>
|
||||
|
|
@ -240,7 +242,7 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-row v-if="!simpleCreateMode">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
|
||||
|
|
@ -323,6 +325,8 @@ export default {
|
|||
roleOptions: [],
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 简易创建模式标识
|
||||
simpleCreateMode: false,
|
||||
defaultProps: {
|
||||
children: "children",
|
||||
label: "label"
|
||||
|
|
@ -486,6 +490,7 @@ export default {
|
|||
emergencyContact: undefined,
|
||||
emergencyPhone: undefined
|
||||
}
|
||||
this.simpleCreateMode = false
|
||||
this.resetForm("form")
|
||||
},
|
||||
// 处理信息编号输入,只允许数字
|
||||
|
|
@ -535,6 +540,7 @@ export default {
|
|||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.simpleCreateMode = true
|
||||
getUser().then(response => {
|
||||
this.postOptions = response.posts
|
||||
this.roleOptions = response.roles
|
||||
|
|
@ -546,6 +552,7 @@ export default {
|
|||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
this.simpleCreateMode = false
|
||||
const userId = row && row.userId ? row.userId : (this.ids && this.ids.length === 1 ? this.ids[0] : null)
|
||||
if (!userId) {
|
||||
this.$modal.msgError("请选择要修改的用户")
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ module.exports = {
|
|||
transpileDependencies: ['quill'],
|
||||
// webpack-dev-server 相关配置
|
||||
devServer: {
|
||||
host: '0.0.0.0',
|
||||
host: '127.0.0.1',
|
||||
port: port,
|
||||
open: true,
|
||||
proxy: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user