权限、用户bug功能完善
This commit is contained in:
parent
8fb641893c
commit
151cae375e
|
|
@ -153,5 +153,21 @@ public interface SysUserMapper
|
||||||
* @param nickName 昵称
|
* @param nickName 昵称
|
||||||
*/
|
*/
|
||||||
public void updateUserName(@Param("userId") Long userId, @Param("userName") String userName, @Param("nickName") String nickName);
|
public void updateUserName(@Param("userId") Long userId, @Param("userName") String userName, @Param("nickName") String nickName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量新增用户信息
|
||||||
|
*
|
||||||
|
* @param userList 用户信息列表
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int batchInsertUser(@Param("list") List<SysUser> userList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询用户名是否存在
|
||||||
|
*
|
||||||
|
* @param userNames 用户名列表
|
||||||
|
* @return 已存在的用户列表
|
||||||
|
*/
|
||||||
|
public List<SysUser> selectUsersByUserNames(@Param("userNames") List<String> userNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,5 +81,29 @@ public interface PsyUserProfileMapper
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
public int deleteProfileByIds(Long[] profileIds);
|
public int deleteProfileByIds(Long[] profileIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询档案(根据信息编号列表)
|
||||||
|
*
|
||||||
|
* @param infoNumbers 信息编号列表
|
||||||
|
* @return 档案集合
|
||||||
|
*/
|
||||||
|
public List<PsyUserProfile> selectProfilesByInfoNumbers(List<String> infoNumbers);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入档案
|
||||||
|
*
|
||||||
|
* @param profileList 档案列表
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int batchInsertProfiles(List<PsyUserProfile> profileList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新档案
|
||||||
|
*
|
||||||
|
* @param profileList 档案列表
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int batchUpdateProfiles(List<PsyUserProfile> profileList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.ddnai.system.service.impl;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.validation.Validator;
|
import javax.validation.Validator;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
@ -524,7 +525,7 @@ public class SysUserServiceImpl implements ISysUserService
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导入用户数据
|
* 导入用户数据(优化版 - 批量处理)
|
||||||
*
|
*
|
||||||
* @param userList 用户数据列表
|
* @param userList 用户数据列表
|
||||||
* @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据
|
* @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据
|
||||||
|
|
@ -538,10 +539,20 @@ public class SysUserServiceImpl implements ISysUserService
|
||||||
{
|
{
|
||||||
throw new ServiceException("导入用户数据不能为空!");
|
throw new ServiceException("导入用户数据不能为空!");
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
// 获取默认密码(只查一次)
|
||||||
|
String defaultPassword = configService.selectConfigByKey("sys.user.initPassword");
|
||||||
|
String encryptedPassword = SecurityUtils.encryptPassword(defaultPassword);
|
||||||
|
|
||||||
|
// 第一步:数据预处理和验证
|
||||||
|
List<SysUser> validUsers = new ArrayList<>();
|
||||||
|
List<String> userNameList = new ArrayList<>();
|
||||||
|
|
||||||
for (SysUser user : userList)
|
for (SysUser user : userList)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -549,7 +560,7 @@ public class SysUserServiceImpl implements ISysUserService
|
||||||
// 如果有信息编号,使用信息编号作为登录账号
|
// 如果有信息编号,使用信息编号作为登录账号
|
||||||
if (StringUtils.isNotEmpty(user.getInfoNumber()))
|
if (StringUtils.isNotEmpty(user.getInfoNumber()))
|
||||||
{
|
{
|
||||||
user.setUserName(user.getInfoNumber()); // 登录账号 = 信息编号
|
user.setUserName(user.getInfoNumber());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有用户名,跳过
|
// 如果没有用户名,跳过
|
||||||
|
|
@ -560,54 +571,136 @@ public class SysUserServiceImpl implements ISysUserService
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证是否存在这个用户
|
// 基本验证
|
||||||
SysUser u = userMapper.selectUserByUserName(user.getUserName());
|
BeanValidators.validateWithException(validator, user);
|
||||||
if (StringUtils.isNull(u))
|
|
||||||
{
|
// 设置默认密码
|
||||||
BeanValidators.validateWithException(validator, user);
|
user.setPassword(encryptedPassword);
|
||||||
deptService.checkDeptDataScope(user.getDeptId());
|
user.setCreateBy(operName);
|
||||||
String password = configService.selectConfigByKey("sys.user.initPassword");
|
|
||||||
user.setPassword(SecurityUtils.encryptPassword(password));
|
validUsers.add(user);
|
||||||
user.setCreateBy(operName);
|
userNameList.add(user.getUserName());
|
||||||
userMapper.insertUser(user);
|
|
||||||
successNum++;
|
|
||||||
successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 导入成功");
|
|
||||||
}
|
|
||||||
else if (isUpdateSupport)
|
|
||||||
{
|
|
||||||
BeanValidators.validateWithException(validator, user);
|
|
||||||
checkUserAllowed(u);
|
|
||||||
checkUserDataScope(u.getUserId());
|
|
||||||
deptService.checkDeptDataScope(user.getDeptId());
|
|
||||||
user.setUserId(u.getUserId());
|
|
||||||
user.setDeptId(u.getDeptId());
|
|
||||||
user.setUpdateBy(operName);
|
|
||||||
userMapper.updateUser(user);
|
|
||||||
successNum++;
|
|
||||||
successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 更新成功");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
failureNum++;
|
|
||||||
failureMsg.append("<br/>" + failureNum + "、账号 " + user.getUserName() + " 已存在");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
failureNum++;
|
failureNum++;
|
||||||
String msg = "<br/>" + failureNum + "、账号 " + user.getUserName() + " 导入失败:";
|
String msg = "<br/>" + failureNum + "、账号 " + user.getUserName() + " 验证失败:";
|
||||||
failureMsg.append(msg + e.getMessage());
|
failureMsg.append(msg + e.getMessage());
|
||||||
log.error(msg, e);
|
log.error(msg, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (validUsers.isEmpty())
|
||||||
|
{
|
||||||
|
throw new ServiceException("没有有效的用户数据可以导入!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二步:批量查询已存在的用户(只查一次数据库)
|
||||||
|
List<SysUser> existingUsers = userMapper.selectUsersByUserNames(userNameList);
|
||||||
|
Map<String, SysUser> existingUserMap = existingUsers.stream()
|
||||||
|
.collect(Collectors.toMap(SysUser::getUserName, u -> u));
|
||||||
|
|
||||||
|
// 第三步:分类处理
|
||||||
|
List<SysUser> usersToInsert = new ArrayList<>();
|
||||||
|
List<SysUser> usersToUpdate = new ArrayList<>();
|
||||||
|
|
||||||
|
for (SysUser user : validUsers)
|
||||||
|
{
|
||||||
|
SysUser existingUser = existingUserMap.get(user.getUserName());
|
||||||
|
if (existingUser == null)
|
||||||
|
{
|
||||||
|
// 新用户
|
||||||
|
usersToInsert.add(user);
|
||||||
|
}
|
||||||
|
else if (isUpdateSupport)
|
||||||
|
{
|
||||||
|
// 更新用户
|
||||||
|
user.setUserId(existingUser.getUserId());
|
||||||
|
user.setDeptId(existingUser.getDeptId());
|
||||||
|
user.setUpdateBy(operName);
|
||||||
|
usersToUpdate.add(user);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 用户已存在且不更新
|
||||||
|
failureNum++;
|
||||||
|
failureMsg.append("<br/>").append(failureNum).append("、账号 ").append(user.getUserName()).append(" 已存在");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第四步:批量插入新用户(每500条一批)
|
||||||
|
if (!usersToInsert.isEmpty())
|
||||||
|
{
|
||||||
|
int batchSize = 500;
|
||||||
|
for (int i = 0; i < usersToInsert.size(); i += batchSize)
|
||||||
|
{
|
||||||
|
int end = Math.min(i + batchSize, usersToInsert.size());
|
||||||
|
List<SysUser> batch = usersToInsert.subList(i, end);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int inserted = userMapper.batchInsertUser(batch);
|
||||||
|
successNum += inserted;
|
||||||
|
for (SysUser user : batch)
|
||||||
|
{
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、账号 ").append(user.getUserName()).append(" 导入成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// 如果批量插入失败,降级为逐条插入
|
||||||
|
log.warn("批量插入失败,降级为逐条插入", e);
|
||||||
|
for (SysUser user : batch)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
userMapper.insertUser(user);
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、账号 ").append(user.getUserName()).append(" 导入成功");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
failureNum++;
|
||||||
|
failureMsg.append("<br/>").append(failureNum).append("、账号 ").append(user.getUserName()).append(" 导入失败:").append(ex.getMessage());
|
||||||
|
log.error("导入用户失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第五步:批量更新用户
|
||||||
|
if (!usersToUpdate.isEmpty())
|
||||||
|
{
|
||||||
|
for (SysUser user : usersToUpdate)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
userMapper.updateUser(user);
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、账号 ").append(user.getUserName()).append(" 更新成功");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
failureNum++;
|
||||||
|
failureMsg.append("<br/>").append(failureNum).append("、账号 ").append(user.getUserName()).append(" 更新失败:").append(e.getMessage());
|
||||||
|
log.error("更新用户失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回结果
|
||||||
if (failureNum > 0)
|
if (failureNum > 0)
|
||||||
{
|
{
|
||||||
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
|
failureMsg.insert(0, "导入完成!成功 " + successNum + " 条,失败 " + failureNum + " 条,错误如下:");
|
||||||
throw new ServiceException(failureMsg.toString());
|
if (successNum == 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException(failureMsg.toString());
|
||||||
|
}
|
||||||
|
return failureMsg.toString();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
|
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条");
|
||||||
}
|
}
|
||||||
return successMsg.toString();
|
return successMsg.toString();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.ddnai.system.service.impl.psychology;
|
package com.ddnai.system.service.impl.psychology;
|
||||||
|
|
||||||
import java.sql.SQLIntegrityConstraintViolationException;
|
import java.sql.SQLIntegrityConstraintViolationException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
@ -501,57 +502,161 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService
|
||||||
StringBuilder failureMsg = new StringBuilder();
|
StringBuilder failureMsg = new StringBuilder();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// ========== 批量处理优化 ==========
|
||||||
|
// 1. 数据预处理和验证
|
||||||
|
List<PsyUserProfile> validProfiles = new ArrayList<>();
|
||||||
|
List<String> infoNumbers = new ArrayList<>();
|
||||||
|
|
||||||
for (PsyUserProfile profile : profileList)
|
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++;
|
||||||
String msg = "<br/>" + failureNum + "、信息编号 " + profile.getInfoNumber() + " 导入失败:";
|
failureMsg.append("<br/>").append(failureNum).append("、档案信息编号为空");
|
||||||
failureMsg.append(msg).append(e.getMessage());
|
|
||||||
importProgressManager.recordFailure(progressKey);
|
importProgressManager.recordFailure(progressKey);
|
||||||
log.error(msg, e);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
validProfiles.add(profile);
|
||||||
|
infoNumbers.add(profile.getInfoNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validProfiles.isEmpty())
|
||||||
|
{
|
||||||
|
String msg = "没有有效的用户档案数据!";
|
||||||
|
importProgressManager.finishFailure(progressKey, msg);
|
||||||
|
throw new ServiceException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 批量查询已存在的档案(一次查询)
|
||||||
|
List<PsyUserProfile> existingProfiles = profileMapper.selectProfilesByInfoNumbers(infoNumbers);
|
||||||
|
java.util.Map<String, PsyUserProfile> existingMap = new java.util.HashMap<>();
|
||||||
|
for (PsyUserProfile existing : existingProfiles)
|
||||||
|
{
|
||||||
|
existingMap.put(existing.getInfoNumber(), existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 分类处理:新增列表和更新列表
|
||||||
|
List<PsyUserProfile> toInsertList = new ArrayList<>();
|
||||||
|
List<PsyUserProfile> toUpdateList = new ArrayList<>();
|
||||||
|
|
||||||
|
for (PsyUserProfile profile : validProfiles)
|
||||||
|
{
|
||||||
|
PsyUserProfile existProfile = existingMap.get(profile.getInfoNumber());
|
||||||
|
|
||||||
|
if (existProfile == null)
|
||||||
|
{
|
||||||
|
// 不存在,加入新增列表
|
||||||
|
toInsertList.add(profile);
|
||||||
|
}
|
||||||
|
else if (isUpdateSupport)
|
||||||
|
{
|
||||||
|
// 存在且支持更新,加入更新列表
|
||||||
|
profile.setProfileId(existProfile.getProfileId());
|
||||||
|
profile.setUserId(existProfile.getUserId());
|
||||||
|
profile.setUpdateBy(operName);
|
||||||
|
toUpdateList.add(profile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 存在但不支持更新,记录失败
|
||||||
|
failureNum++;
|
||||||
|
failureMsg.append("<br/>").append(failureNum).append("、信息编号 ").append(profile.getInfoNumber()).append(" 已存在");
|
||||||
|
importProgressManager.recordFailure(progressKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 批量插入(分批处理,每批500条)
|
||||||
|
if (!toInsertList.isEmpty())
|
||||||
|
{
|
||||||
|
int batchSize = 500;
|
||||||
|
for (int i = 0; i < toInsertList.size(); i += batchSize)
|
||||||
|
{
|
||||||
|
int end = Math.min(i + batchSize, toInsertList.size());
|
||||||
|
List<PsyUserProfile> batch = toInsertList.subList(i, end);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int insertCount = profileMapper.batchInsertProfiles(batch);
|
||||||
|
for (PsyUserProfile profile : batch)
|
||||||
|
{
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、信息编号 ").append(profile.getInfoNumber()).append(" 导入成功");
|
||||||
|
importProgressManager.recordSuccess(progressKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// 批量插入失败,尝试逐条插入
|
||||||
|
log.warn("批量插入失败,尝试逐条插入: " + e.getMessage());
|
||||||
|
for (PsyUserProfile profile : batch)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.insertProfile(profile);
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、信息编号 ").append(profile.getInfoNumber()).append(" 导入成功");
|
||||||
|
importProgressManager.recordSuccess(progressKey);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
failureNum++;
|
||||||
|
String msg = "<br/>" + failureNum + "、信息编号 " + profile.getInfoNumber() + " 导入失败:";
|
||||||
|
failureMsg.append(msg).append(ex.getMessage());
|
||||||
|
importProgressManager.recordFailure(progressKey);
|
||||||
|
log.error(msg, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 批量更新(分批处理,每批500条)
|
||||||
|
if (!toUpdateList.isEmpty())
|
||||||
|
{
|
||||||
|
int batchSize = 500;
|
||||||
|
for (int i = 0; i < toUpdateList.size(); i += batchSize)
|
||||||
|
{
|
||||||
|
int end = Math.min(i + batchSize, toUpdateList.size());
|
||||||
|
List<PsyUserProfile> batch = toUpdateList.subList(i, end);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int updateCount = profileMapper.batchUpdateProfiles(batch);
|
||||||
|
for (PsyUserProfile profile : batch)
|
||||||
|
{
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、信息编号 ").append(profile.getInfoNumber()).append(" 更新成功");
|
||||||
|
importProgressManager.recordSuccess(progressKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// 批量更新失败,尝试逐条更新
|
||||||
|
log.warn("批量更新失败,尝试逐条更新: " + e.getMessage());
|
||||||
|
for (PsyUserProfile profile : batch)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.updateProfile(profile);
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、信息编号 ").append(profile.getInfoNumber()).append(" 更新成功");
|
||||||
|
importProgressManager.recordSuccess(progressKey);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
failureNum++;
|
||||||
|
String msg = "<br/>" + failureNum + "、信息编号 " + profile.getInfoNumber() + " 更新失败:";
|
||||||
|
failureMsg.append(msg).append(ex.getMessage());
|
||||||
|
importProgressManager.recordFailure(progressKey);
|
||||||
|
log.error(msg, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -232,5 +232,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
#{userId}
|
#{userId}
|
||||||
</foreach>
|
</foreach>
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
|
<!-- 批量插入用户 -->
|
||||||
|
<insert id="batchInsertUser" parameterType="java.util.List">
|
||||||
|
insert into sys_user(
|
||||||
|
dept_id, user_name, nick_name, email, phonenumber, sex, avatar,
|
||||||
|
password, status, del_flag, create_by, create_time, remark, info_number
|
||||||
|
) values
|
||||||
|
<foreach collection="list" item="item" separator=",">
|
||||||
|
(
|
||||||
|
#{item.deptId}, #{item.userName}, #{item.nickName}, #{item.email},
|
||||||
|
#{item.phonenumber}, #{item.sex}, #{item.avatar}, #{item.password},
|
||||||
|
#{item.status}, '0', #{item.createBy}, sysdate(), #{item.remark}, #{item.infoNumber}
|
||||||
|
)
|
||||||
|
</foreach>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<!-- 批量查询用户名 -->
|
||||||
|
<select id="selectUsersByUserNames" resultMap="SysUserResult">
|
||||||
|
<include refid="selectUserVo"/>
|
||||||
|
where u.user_name in
|
||||||
|
<foreach collection="userNames" item="userName" open="(" separator="," close=")">
|
||||||
|
#{userName}
|
||||||
|
</foreach>
|
||||||
|
and u.del_flag = '0'
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
|
||||||
|
|
@ -275,5 +275,64 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
order by u.create_time desc
|
order by u.create_time desc
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 批量查询档案(根据信息编号列表) -->
|
||||||
|
<select id="selectProfilesByInfoNumbers" resultMap="PsyUserProfileResult">
|
||||||
|
<include refid="selectProfileVo"/>
|
||||||
|
from psy_user_profile p
|
||||||
|
left join sys_user u on p.user_id = u.user_id
|
||||||
|
where p.info_number in
|
||||||
|
<foreach item="infoNumber" collection="list" open="(" separator="," close=")">
|
||||||
|
#{infoNumber}
|
||||||
|
</foreach>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 批量插入档案 -->
|
||||||
|
<insert id="batchInsertProfiles" parameterType="java.util.List">
|
||||||
|
insert into psy_user_profile(
|
||||||
|
user_id, profile_type, profile_data, avatar, id_card, birthday,
|
||||||
|
prison, prison_area, gender, nation, education_level, crime_name,
|
||||||
|
sentence_term, sentence_start_date, sentence_end_date, entry_date,
|
||||||
|
info_number, create_by, create_time, remark
|
||||||
|
)
|
||||||
|
values
|
||||||
|
<foreach item="item" collection="list" separator=",">
|
||||||
|
(
|
||||||
|
#{item.userId}, #{item.profileType}, #{item.profileData}, #{item.avatar},
|
||||||
|
#{item.idCard}, #{item.birthday}, #{item.prison}, #{item.prisonArea},
|
||||||
|
#{item.gender}, #{item.nation}, #{item.educationLevel}, #{item.crimeName},
|
||||||
|
#{item.sentenceTerm}, #{item.sentenceStartDate}, #{item.sentenceEndDate},
|
||||||
|
#{item.entryDate}, #{item.infoNumber}, #{item.createBy}, sysdate(), #{item.remark}
|
||||||
|
)
|
||||||
|
</foreach>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<!-- 批量更新档案 -->
|
||||||
|
<update id="batchUpdateProfiles" parameterType="java.util.List">
|
||||||
|
<foreach item="item" collection="list" separator=";">
|
||||||
|
update psy_user_profile
|
||||||
|
<set>
|
||||||
|
<if test="item.profileType != null and item.profileType != ''">profile_type = #{item.profileType},</if>
|
||||||
|
<if test="item.profileData != null">profile_data = #{item.profileData},</if>
|
||||||
|
<if test="item.avatar != null and item.avatar != ''">avatar = #{item.avatar},</if>
|
||||||
|
<if test="item.idCard != null and item.idCard != ''">id_card = #{item.idCard},</if>
|
||||||
|
<if test="item.birthday != null">birthday = #{item.birthday},</if>
|
||||||
|
<if test="item.prison != null and item.prison != ''">prison = #{item.prison},</if>
|
||||||
|
<if test="item.prisonArea != null and item.prisonArea != ''">prison_area = #{item.prisonArea},</if>
|
||||||
|
<if test="item.gender != null and item.gender != ''">gender = #{item.gender},</if>
|
||||||
|
<if test="item.nation != null and item.nation != ''">nation = #{item.nation},</if>
|
||||||
|
<if test="item.educationLevel != null and item.educationLevel != ''">education_level = #{item.educationLevel},</if>
|
||||||
|
<if test="item.crimeName != null and item.crimeName != ''">crime_name = #{item.crimeName},</if>
|
||||||
|
<if test="item.sentenceTerm != null and item.sentenceTerm != ''">sentence_term = #{item.sentenceTerm},</if>
|
||||||
|
<if test="item.sentenceStartDate != null">sentence_start_date = #{item.sentenceStartDate},</if>
|
||||||
|
<if test="item.sentenceEndDate != null">sentence_end_date = #{item.sentenceEndDate},</if>
|
||||||
|
<if test="item.entryDate != null">entry_date = #{item.entryDate},</if>
|
||||||
|
<if test="item.updateBy != null and item.updateBy != ''">update_by = #{item.updateBy},</if>
|
||||||
|
update_time = sysdate(),
|
||||||
|
<if test="item.remark != null">remark = #{item.remark},</if>
|
||||||
|
</set>
|
||||||
|
where profile_id = #{item.profileId}
|
||||||
|
</foreach>
|
||||||
|
</update>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,17 +33,31 @@
|
||||||
<div class="question-number">第 {{ currentIndex + 1 }} 题</div>
|
<div class="question-number">第 {{ currentIndex + 1 }} 题</div>
|
||||||
<div class="question-content-wrapper">
|
<div class="question-content-wrapper">
|
||||||
<div class="question-content">{{ currentItem.itemContent }}</div>
|
<div class="question-content">{{ currentItem.itemContent }}</div>
|
||||||
<el-button
|
<div class="tts-buttons">
|
||||||
type="text"
|
<el-button
|
||||||
size="small"
|
type="text"
|
||||||
@click="speakText(currentItem.itemContent)"
|
size="small"
|
||||||
:disabled="!isTtsSupported"
|
@click="speakCurrentQuestion"
|
||||||
:class="['tts-btn', isSpeaking ? 'speaking' : '']"
|
:disabled="!isTtsSupported"
|
||||||
title="朗读题干"
|
:class="['tts-btn-all', isSpeaking ? 'speaking' : '']"
|
||||||
>
|
title="朗读题目和所有选项"
|
||||||
<i :class="isSpeaking ? 'el-icon-video-pause' : 'el-icon-service'"
|
>
|
||||||
style="font-size: 18px; color: #409EFF;"></i>
|
<i :class="isSpeaking ? 'el-icon-video-pause' : 'el-icon-s-promotion'"
|
||||||
</el-button>
|
style="font-size: 18px; color: #67C23A;"></i>
|
||||||
|
<span style="font-size: 12px; margin-left: 4px;">朗读全部</span>
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
@click="speakText(currentItem.itemContent)"
|
||||||
|
:disabled="!isTtsSupported"
|
||||||
|
:class="['tts-btn', isSpeaking ? 'speaking' : '']"
|
||||||
|
title="只朗读题干"
|
||||||
|
>
|
||||||
|
<i :class="isSpeaking ? 'el-icon-video-pause' : 'el-icon-service'"
|
||||||
|
style="font-size: 18px; color: #409EFF;"></i>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 单选题 -->
|
<!-- 单选题 -->
|
||||||
|
|
@ -226,7 +240,7 @@ export default {
|
||||||
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 = 0.9; // 稍慢一点,更清晰
|
this.currentUtterance.rate = 1.2; // 正常语速,稍快
|
||||||
this.currentUtterance.pitch = 1.0;
|
this.currentUtterance.pitch = 1.0;
|
||||||
|
|
||||||
// 获取可用的语音列表
|
// 获取可用的语音列表
|
||||||
|
|
@ -278,6 +292,33 @@ export default {
|
||||||
this.currentUtterance = null;
|
this.currentUtterance = null;
|
||||||
this.isSpeaking = false;
|
this.isSpeaking = false;
|
||||||
},
|
},
|
||||||
|
/** 朗读当前题目和所有选项 */
|
||||||
|
speakCurrentQuestion() {
|
||||||
|
if (!this.isTtsSupported || !this.currentItem) {
|
||||||
|
this.$message.warning('浏览器不支持语音播放功能');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果正在播放,则停止
|
||||||
|
if (this.isSpeaking) {
|
||||||
|
this.stopSpeaking();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建完整文本:题目 + 所有选项
|
||||||
|
let fullText = this.currentItem.itemContent.trim();
|
||||||
|
|
||||||
|
if (this.currentOptions && this.currentOptions.length > 0) {
|
||||||
|
fullText += '。选项:';
|
||||||
|
this.currentOptions.forEach((option, index) => {
|
||||||
|
const optionCode = option.optionCode || String.fromCharCode(65 + index); // A, B, C...
|
||||||
|
fullText += `${optionCode}、${option.optionContent}。`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用朗读
|
||||||
|
this.speakText(fullText);
|
||||||
|
},
|
||||||
/** 错误信息 */
|
/** 错误信息 */
|
||||||
getErrorMessage(errorType) {
|
getErrorMessage(errorType) {
|
||||||
const errorMap = {
|
const errorMap = {
|
||||||
|
|
@ -767,15 +808,32 @@ export default {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tts-btn {
|
.tts-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 5px 10px;
|
|
||||||
color: #409EFF;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tts-btn:disabled {
|
.tts-btn-all, .tts-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tts-btn-all:hover:not(:disabled), .tts-btn:hover:not(:disabled) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tts-btn-all.speaking, .tts-btn.speaking {
|
||||||
|
animation: pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tts-btn:disabled, .tts-btn-all:disabled {
|
||||||
color: #c0c4cc;
|
color: #c0c4cc;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tts-icon {
|
.tts-icon {
|
||||||
|
|
|
||||||
|
|
@ -297,7 +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";
|
import { listStudentProfile } from "@/api/psychology/profile";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PsyScalePermission",
|
name: "PsyScalePermission",
|
||||||
|
|
@ -345,11 +345,11 @@ export default {
|
||||||
// 用户查询参数
|
// 用户查询参数
|
||||||
userQueryParams: {
|
userQueryParams: {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 8,
|
pageSize: 10,
|
||||||
infoNumber: undefined,
|
infoNumber: undefined,
|
||||||
userName: undefined,
|
userName: undefined,
|
||||||
prisonArea: undefined,
|
prisonArea: undefined,
|
||||||
status: '0'
|
status: undefined
|
||||||
},
|
},
|
||||||
// 查询参数
|
// 查询参数
|
||||||
queryParams: {
|
queryParams: {
|
||||||
|
|
@ -552,7 +552,8 @@ export default {
|
||||||
},
|
},
|
||||||
/** 加载监区下拉选项 */
|
/** 加载监区下拉选项 */
|
||||||
loadPrisonAreaOptions() {
|
loadPrisonAreaOptions() {
|
||||||
listProfile({ pageNum: 1, pageSize: 10000 }).then(response => {
|
// 使用学员档案接口,与用户档案页面保持一致
|
||||||
|
listStudentProfile({ pageNum: 1, pageSize: 10000 }).then(response => {
|
||||||
const rows = response.rows || [];
|
const rows = response.rows || [];
|
||||||
const areaSet = new Set();
|
const areaSet = new Set();
|
||||||
rows.forEach(profile => {
|
rows.forEach(profile => {
|
||||||
|
|
@ -595,7 +596,7 @@ export default {
|
||||||
prisonArea: this.userQueryParams.prisonArea,
|
prisonArea: this.userQueryParams.prisonArea,
|
||||||
status: this.userQueryParams.status
|
status: this.userQueryParams.status
|
||||||
};
|
};
|
||||||
return listProfile(query).then(response => {
|
return listStudentProfile(query).then(response => {
|
||||||
this.userSelectList = (response.rows || []).map(profile => this.transformProfileToSelectableUser(profile));
|
this.userSelectList = (response.rows || []).map(profile => this.transformProfileToSelectableUser(profile));
|
||||||
this.userSelectTotal = response.total || 0;
|
this.userSelectTotal = response.total || 0;
|
||||||
this.userSelectLoading = false;
|
this.userSelectLoading = false;
|
||||||
|
|
@ -619,11 +620,11 @@ export default {
|
||||||
this.resetForm("userQueryForm");
|
this.resetForm("userQueryForm");
|
||||||
this.userQueryParams = {
|
this.userQueryParams = {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 8,
|
pageSize: 10,
|
||||||
infoNumber: undefined,
|
infoNumber: undefined,
|
||||||
userName: undefined,
|
userName: undefined,
|
||||||
prisonArea: undefined,
|
prisonArea: undefined,
|
||||||
status: '0'
|
status: undefined
|
||||||
};
|
};
|
||||||
this.handleUserQuery();
|
this.handleUserQuery();
|
||||||
},
|
},
|
||||||
|
|
@ -646,14 +647,17 @@ export default {
|
||||||
// 取消全选时,清除当前页的选择
|
// 取消全选时,清除当前页的选择
|
||||||
const currentPageIds = this.userSelectList.map(item => item.userId);
|
const currentPageIds = this.userSelectList.map(item => item.userId);
|
||||||
this.selectedUserIds = this.selectedUserIds.filter(id => !currentPageIds.includes(id));
|
this.selectedUserIds = this.selectedUserIds.filter(id => !currentPageIds.includes(id));
|
||||||
if (currentPageIds.length === 0) {
|
|
||||||
this.selectedUserIds = [];
|
|
||||||
}
|
|
||||||
this.cachedFilteredUsers = [];
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 全选时,只选择当前页的用户,不选择所有筛选条件下的用户
|
||||||
if (selection.length === this.userSelectList.length && this.userSelectList.length > 0) {
|
if (selection.length === this.userSelectList.length && this.userSelectList.length > 0) {
|
||||||
await this.selectAllUsersUnderCurrentFilter();
|
// 只选择当前页的用户
|
||||||
|
const currentPageIds = this.userSelectList.map(item => item.userId);
|
||||||
|
currentPageIds.forEach(userId => {
|
||||||
|
if (!this.selectedUserIds.includes(userId)) {
|
||||||
|
this.selectedUserIds.push(userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async fetchAllUsersUnderCurrentFilter() {
|
async fetchAllUsersUnderCurrentFilter() {
|
||||||
|
|
@ -668,7 +672,7 @@ export default {
|
||||||
status: this.userQueryParams.status
|
status: this.userQueryParams.status
|
||||||
};
|
};
|
||||||
while (true) {
|
while (true) {
|
||||||
const response = await listProfile({
|
const response = await listStudentProfile({
|
||||||
...baseParams,
|
...baseParams,
|
||||||
pageNum,
|
pageNum,
|
||||||
pageSize
|
pageSize
|
||||||
|
|
|
||||||
|
|
@ -21,16 +21,31 @@
|
||||||
<div class="question-number">第 {{ currentIndex + 1 }} 题</div>
|
<div class="question-number">第 {{ currentIndex + 1 }} 题</div>
|
||||||
<div class="question-content-wrapper">
|
<div class="question-content-wrapper">
|
||||||
<div class="question-content">{{ currentItem.itemContent }}</div>
|
<div class="question-content">{{ currentItem.itemContent }}</div>
|
||||||
<el-button
|
<div class="tts-buttons">
|
||||||
type="text"
|
<el-button
|
||||||
size="small"
|
type="text"
|
||||||
@click="speakText(currentItem.itemContent)"
|
size="small"
|
||||||
:disabled="!isTtsSupported"
|
@click="speakCurrentQuestion"
|
||||||
class="tts-btn"
|
:disabled="!isTtsSupported"
|
||||||
title="朗读题目"
|
:class="['tts-btn-all', isSpeaking ? 'speaking' : '']"
|
||||||
>
|
title="朗读题目和所有选项"
|
||||||
<img :src="voiceIcon" alt="朗读题目" class="tts-icon" />
|
>
|
||||||
</el-button>
|
<i :class="isSpeaking ? 'el-icon-video-pause' : 'el-icon-s-promotion'"
|
||||||
|
style="font-size: 18px; color: #67C23A;"></i>
|
||||||
|
<span style="font-size: 12px; margin-left: 4px;">朗读全部</span>
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
@click="speakText(currentItem.itemContent)"
|
||||||
|
:disabled="!isTtsSupported"
|
||||||
|
:class="['tts-btn', isSpeaking ? 'speaking' : '']"
|
||||||
|
title="只朗读题干"
|
||||||
|
>
|
||||||
|
<i :class="isSpeaking ? 'el-icon-video-pause' : 'el-icon-service'"
|
||||||
|
style="font-size: 18px; color: #409EFF;"></i>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="question-info" v-if="currentItem.score">
|
<div class="question-info" v-if="currentItem.score">
|
||||||
<el-tag type="info">分值:{{ currentItem.score }}分</el-tag>
|
<el-tag type="info">分值:{{ currentItem.score }}分</el-tag>
|
||||||
|
|
@ -236,6 +251,7 @@ export default {
|
||||||
isTtsSupported: false,
|
isTtsSupported: false,
|
||||||
synth: null,
|
synth: null,
|
||||||
currentUtterance: null,
|
currentUtterance: null,
|
||||||
|
isSpeaking: false,
|
||||||
voiceIcon
|
voiceIcon
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -320,8 +336,8 @@ export default {
|
||||||
|
|
||||||
// 设置语音参数
|
// 设置语音参数
|
||||||
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 = 1.2; // 正常语速,稍快
|
||||||
this.currentUtterance.pitch = 1.0;
|
this.currentUtterance.pitch = 1.0;
|
||||||
|
|
||||||
// 选择中文语音(如果可用)
|
// 选择中文语音(如果可用)
|
||||||
|
|
@ -339,14 +355,17 @@ export default {
|
||||||
|
|
||||||
this.currentUtterance.onstart = () => {
|
this.currentUtterance.onstart = () => {
|
||||||
hasStarted = true;
|
hasStarted = true;
|
||||||
|
this.isSpeaking = true;
|
||||||
console.log('TTS 开始朗读');
|
console.log('TTS 开始朗读');
|
||||||
};
|
};
|
||||||
|
|
||||||
this.currentUtterance.onend = () => {
|
this.currentUtterance.onend = () => {
|
||||||
|
this.isSpeaking = false;
|
||||||
console.log('TTS 朗读完成');
|
console.log('TTS 朗读完成');
|
||||||
};
|
};
|
||||||
|
|
||||||
this.currentUtterance.onerror = (event) => {
|
this.currentUtterance.onerror = (event) => {
|
||||||
|
this.isSpeaking = false;
|
||||||
console.error('TTS 错误:', event);
|
console.error('TTS 错误:', event);
|
||||||
|
|
||||||
// 如果已经成功开始朗读,忽略后续的错误(可能是非致命性错误)
|
// 如果已经成功开始朗读,忽略后续的错误(可能是非致命性错误)
|
||||||
|
|
@ -385,6 +404,34 @@ export default {
|
||||||
this.synth.cancel();
|
this.synth.cancel();
|
||||||
}
|
}
|
||||||
this.currentUtterance = null;
|
this.currentUtterance = null;
|
||||||
|
this.isSpeaking = false;
|
||||||
|
},
|
||||||
|
/** 朗读当前题目和所有选项 */
|
||||||
|
speakCurrentQuestion() {
|
||||||
|
if (!this.isTtsSupported || !this.currentItem) {
|
||||||
|
this.$message.warning('浏览器不支持语音播放功能');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果正在播放,则停止
|
||||||
|
if (this.isSpeaking) {
|
||||||
|
this.stopSpeaking();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建完整文本:题目 + 所有选项
|
||||||
|
let fullText = this.currentItem.itemContent.trim();
|
||||||
|
|
||||||
|
if (this.currentOptions && this.currentOptions.length > 0) {
|
||||||
|
fullText += '。选项:';
|
||||||
|
this.currentOptions.forEach((option, index) => {
|
||||||
|
const optionCode = option.optionCode || String.fromCharCode(65 + index); // A, B, C...
|
||||||
|
fullText += `${optionCode}、${option.optionContent}。`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用朗读
|
||||||
|
this.speakText(fullText);
|
||||||
},
|
},
|
||||||
/** 获取错误信息 */
|
/** 获取错误信息 */
|
||||||
getErrorMessage(errorType) {
|
getErrorMessage(errorType) {
|
||||||
|
|
@ -787,20 +834,41 @@ export default {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tts-btn {
|
.tts-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 5px 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #409EFF;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tts-btn:hover {
|
.tts-btn-all, .tts-btn {
|
||||||
color: #66b1ff;
|
flex-shrink: 0;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tts-btn:disabled {
|
.tts-btn-all:hover:not(:disabled), .tts-btn:hover:not(:disabled) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tts-btn-all.speaking, .tts-btn.speaking {
|
||||||
|
animation: pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tts-btn:disabled, .tts-btn-all:disabled {
|
||||||
color: #c0c4cc;
|
color: #c0c4cc;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tts-icon {
|
.tts-icon {
|
||||||
|
|
|
||||||
|
|
@ -154,9 +154,11 @@ export default {
|
||||||
generating: false,
|
generating: false,
|
||||||
reportDialogVisible: false,
|
reportDialogVisible: false,
|
||||||
comprehensiveReport: '',
|
comprehensiveReport: '',
|
||||||
// ========== 本地大模型配置 ==========
|
// ========== 本地大模型API配置 ==========
|
||||||
OLLAMA_URL: 'http://192.168.0.106:11434/api/generate',
|
// 注:使用本地大模型,如Ollama等
|
||||||
MODEL: 'deepseek-r1:32b'
|
API_URL: 'http://localhost:11434/v1/chat/completions',
|
||||||
|
API_KEY: '', // 本地模型不需要API Key
|
||||||
|
MODEL: 'qwen2.5:7b' // 根据实际使用的本地模型修改
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|
@ -564,20 +566,25 @@ ${typeLabel}${index + 1}:${report.scaleName}
|
||||||
|
|
||||||
return `${SYSTEM_PROMPT}\n\n${userInfoText}\n\n${scaleReportsText}`
|
return `${SYSTEM_PROMPT}\n\n${userInfoText}\n\n${scaleReportsText}`
|
||||||
},
|
},
|
||||||
// 本地 OLLAMA API 调用方法
|
// Kimi API 调用方法
|
||||||
async callOLLAMA(prompt) {
|
async callOLLAMA(prompt) {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(this.OLLAMA_URL, {
|
const { data } = await axios.post(this.API_URL, {
|
||||||
model: this.MODEL,
|
model: this.MODEL,
|
||||||
prompt: prompt,
|
messages: [
|
||||||
|
{ role: 'user', content: prompt }
|
||||||
|
],
|
||||||
temperature: 0.2,
|
temperature: 0.2,
|
||||||
num_predict: 2000,
|
max_tokens: 2000
|
||||||
stream: false
|
|
||||||
}, {
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${this.API_KEY}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
timeout: 120000
|
timeout: 120000
|
||||||
})
|
})
|
||||||
|
|
||||||
let response = data?.response ?? ''
|
let response = data?.choices?.[0]?.message?.content ?? ''
|
||||||
|
|
||||||
// 清理响应内容
|
// 清理响应内容
|
||||||
response = response
|
response = response
|
||||||
|
|
|
||||||
|
|
@ -403,9 +403,10 @@ export default {
|
||||||
this.aiError = '';
|
this.aiError = '';
|
||||||
this.aiResult = '';
|
this.aiResult = '';
|
||||||
|
|
||||||
// Ollama API配置
|
// 本地大模型API配置(如Ollama)
|
||||||
const OLLAMA_URL = 'http://192.168.0.106:11434/api/generate';
|
const API_URL = 'http://localhost:11434/v1/chat/completions';
|
||||||
const MODEL = 'deepseek-r1:32b';
|
const API_KEY = ''; // 本地模型不需要API Key
|
||||||
|
const MODEL = 'qwen2.5:7b'; // 根据实际使用的本地模型修改
|
||||||
|
|
||||||
// 构建系统提示词
|
// 构建系统提示词
|
||||||
const SYSTEM_PROMPT = [
|
const SYSTEM_PROMPT = [
|
||||||
|
|
@ -426,20 +427,26 @@ export default {
|
||||||
// 提取纯文本内容(去除HTML标签)
|
// 提取纯文本内容(去除HTML标签)
|
||||||
const textContent = reportContent.replace(/<[^>]*>/g, '').substring(0, 3000);
|
const textContent = reportContent.replace(/<[^>]*>/g, '').substring(0, 3000);
|
||||||
|
|
||||||
const prompt = `${SYSTEM_PROMPT}\n\n重要:请直接输出结果,不要包含任何思考过程、<think>标签或<think>标签。\n\n报告标题:${reportTitle}\n报告类型:${reportType}\n报告内容:${textContent}`;
|
const userPrompt = `重要:请直接输出结果,不要包含任何思考过程、<think>标签或<think>标签。\n\n报告标题:${reportTitle}\n报告类型:${reportType}\n报告内容:${textContent}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(OLLAMA_URL, {
|
const { data } = await axios.post(API_URL, {
|
||||||
model: MODEL,
|
model: MODEL,
|
||||||
prompt: prompt,
|
messages: [
|
||||||
|
{ role: 'system', content: SYSTEM_PROMPT },
|
||||||
|
{ role: 'user', content: userPrompt }
|
||||||
|
],
|
||||||
temperature: 0.2,
|
temperature: 0.2,
|
||||||
num_predict: 1000,
|
max_tokens: 1000
|
||||||
stream: false
|
|
||||||
}, {
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${API_KEY}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
timeout: 60000 // 60秒超时
|
timeout: 60000 // 60秒超时
|
||||||
});
|
});
|
||||||
|
|
||||||
let rawResponse = data?.response ?? '无法解析模型输出';
|
let rawResponse = data?.choices?.[0]?.message?.content ?? '无法解析模型输出';
|
||||||
|
|
||||||
// 过滤掉思考过程标签
|
// 过滤掉思考过程标签
|
||||||
rawResponse = rawResponse
|
rawResponse = rawResponse
|
||||||
|
|
|
||||||
|
|
@ -709,9 +709,10 @@ export default {
|
||||||
},
|
},
|
||||||
/** AI分析报告内容 */
|
/** AI分析报告内容 */
|
||||||
async generateAIAnalysis(reportContent, reportTitle, reportType) {
|
async generateAIAnalysis(reportContent, reportTitle, reportType) {
|
||||||
// Ollama API配置
|
// 本地大模型API配置(如Ollama)
|
||||||
const OLLAMA_URL = 'http://192.168.0.106:11434/api/generate';
|
const API_URL = 'http://localhost:11434/v1/chat/completions';
|
||||||
const MODEL = 'deepseek-r1:32b';
|
const API_KEY = ''; // 本地模型不需要API Key
|
||||||
|
const MODEL = 'qwen2.5:7b'; // 根据实际使用的本地模型修改
|
||||||
|
|
||||||
// 构建系统提示词
|
// 构建系统提示词
|
||||||
const SYSTEM_PROMPT = [
|
const SYSTEM_PROMPT = [
|
||||||
|
|
@ -727,20 +728,26 @@ export default {
|
||||||
// 提取纯文本内容(去除HTML标签)
|
// 提取纯文本内容(去除HTML标签)
|
||||||
const textContent = reportContent.replace(/<[^>]*>/g, '').substring(0, 3000);
|
const textContent = reportContent.replace(/<[^>]*>/g, '').substring(0, 3000);
|
||||||
|
|
||||||
const prompt = `${SYSTEM_PROMPT}\n\n重要:请直接输出结果,不要包含任何思考过程、<think>标签或</think>标签。\n\n报告标题:${reportTitle}\n报告类型:${reportType}\n报告内容:${textContent}`;
|
const userPrompt = `重要:请直接输出结果,不要包含任何思考过程、<think>标签或</think>标签。\n\n报告标题:${reportTitle}\n报告类型:${reportType}\n报告内容:${textContent}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(OLLAMA_URL, {
|
const { data } = await axios.post(API_URL, {
|
||||||
model: MODEL,
|
model: MODEL,
|
||||||
prompt: prompt,
|
messages: [
|
||||||
|
{ role: 'system', content: SYSTEM_PROMPT },
|
||||||
|
{ role: 'user', content: userPrompt }
|
||||||
|
],
|
||||||
temperature: 0.2,
|
temperature: 0.2,
|
||||||
num_predict: 1000,
|
max_tokens: 1000
|
||||||
stream: false
|
|
||||||
}, {
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${API_KEY}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
timeout: 60000 // 60秒超时
|
timeout: 60000 // 60秒超时
|
||||||
});
|
});
|
||||||
|
|
||||||
let rawResponse = data?.response ?? '';
|
let rawResponse = data?.choices?.[0]?.message?.content ?? '';
|
||||||
|
|
||||||
// 过滤掉思考过程标签
|
// 过滤掉思考过程标签
|
||||||
rawResponse = rawResponse
|
rawResponse = rawResponse
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="量表类型" prop="scaleType">
|
<el-form-item label="量表类型" prop="scaleType" v-show="false">
|
||||||
<el-select v-model="queryParams.scaleType" placeholder="量表类型" clearable>
|
<el-select v-model="queryParams.scaleType" placeholder="量表类型" clearable>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in dict.type.psy_scale_type"
|
v-for="dict in dict.type.psy_scale_type"
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,8 @@ export default {
|
||||||
const rows = response.data || []
|
const rows = response.data || []
|
||||||
const map = {}
|
const map = {}
|
||||||
rows.forEach(item => {
|
rows.forEach(item => {
|
||||||
if (!item || !item.scaleId) {
|
// 只处理状态为'3'(暂停)的记录
|
||||||
|
if (!item || !item.scaleId || item.status !== '3') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const existing = map[item.scaleId]
|
const existing = map[item.scaleId]
|
||||||
|
|
@ -205,7 +206,8 @@ export default {
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.createNewAssessment(test)
|
// 先删除旧的暂停记录,再创建新测评
|
||||||
|
this.deleteAndCreateNew(pausedRecord, test)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|
@ -294,6 +296,28 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 删除旧记录并创建新测评
|
||||||
|
deleteAndCreateNew(pausedRecord, test) {
|
||||||
|
if (!pausedRecord || !pausedRecord.assessmentId) {
|
||||||
|
this.createAssessment(test)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
const { delAssessment } = require("@/api/psychology/assessment")
|
||||||
|
delAssessment(pausedRecord.assessmentId)
|
||||||
|
.then(() => {
|
||||||
|
// 删除成功后,刷新暂停列表并创建新测评
|
||||||
|
this.loadPausedList()
|
||||||
|
this.createAssessment(test)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("删除暂停记录失败:", error)
|
||||||
|
this.loading = false
|
||||||
|
Message.error("删除旧记录失败,请稍后重试")
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
// 获取量表类型名称
|
// 获取量表类型名称
|
||||||
getScaleTypeName(type) {
|
getScaleTypeName(type) {
|
||||||
// 这里可以根据实际需求返回类型名称
|
// 这里可以根据实际需求返回类型名称
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user