用户档案、大模型bug修改

This commit is contained in:
xiao12feng 2025-12-02 17:09:22 +08:00
parent 2931e0e1c0
commit 5456739b22
16 changed files with 341 additions and 94 deletions

View File

@ -186,6 +186,7 @@ public class PsyAssessmentController extends BaseController
/** /**
* 开始测评 * 开始测评
* 允许管理员和学员访问 * 允许管理员和学员访问
* 每个用户对同一量表只能有一条未完成的测评记录
*/ */
@PostMapping("/start") @PostMapping("/start")
public AjaxResult start(@Validated @RequestBody AssessmentStartVO startVO) public AjaxResult start(@Validated @RequestBody AssessmentStartVO startVO)
@ -200,35 +201,53 @@ public class PsyAssessmentController extends BaseController
currentUsername = SecurityUtils.getUsername(); currentUsername = SecurityUtils.getUsername();
} catch (Exception e) { } catch (Exception e) {
logger.warn("获取用户信息失败,可能是匿名访问: {}", e.getMessage()); logger.warn("获取用户信息失败,可能是匿名访问: {}", e.getMessage());
// 如果是匿名访问userId username 可以为 null
} }
// 确定实际的测评用户ID // 确定实际的测评用户ID
// 如果传入了targetUserId管理员代替用户测评使用targetUserId
// 否则使用当前登录用户ID
Long assessmentUserId = startVO.getTargetUserId() != null ? startVO.getTargetUserId() : currentUserId; Long assessmentUserId = startVO.getTargetUserId() != null ? startVO.getTargetUserId() : currentUserId;
// 检查是否已存在该用户对该量表的未完成测评记录
if (assessmentUserId != null && startVO.getScaleId() != null)
{
// 使用不过滤的查询方法获取所有测评记录
List<PsyAssessment> existingList = assessmentService.selectAssessmentByUserAndScale(assessmentUserId, startVO.getScaleId());
// 查找未完成的测评状态为进行中或已暂停
for (PsyAssessment existing : existingList)
{
if ("0".equals(existing.getStatus()) || "3".equals(existing.getStatus()))
{
// 已存在未完成的测评直接返回该测评ID
// 不在这里改变状态让前端在进入答题页面后调用 resume API
logger.info("复用已存在的测评记录 - assessmentId: {}, status: {}",
existing.getAssessmentId(), existing.getStatus());
return success(existing.getAssessmentId());
}
}
}
// 没有未完成的测评创建新的测评记录
// 初始状态为"已暂停"等用户真正进入答题页面后再改为"进行中"
PsyAssessment assessment = new PsyAssessment(); PsyAssessment assessment = new PsyAssessment();
assessment.setScaleId(startVO.getScaleId()); assessment.setScaleId(startVO.getScaleId());
assessment.setAssesseeName(startVO.getAssesseeName()); assessment.setAssesseeName(startVO.getAssesseeName());
assessment.setAssesseeGender(startVO.getAssesseeGender()); assessment.setAssesseeGender(startVO.getAssesseeGender());
assessment.setAssesseeAge(startVO.getAssesseeAge()); assessment.setAssesseeAge(startVO.getAssesseeAge());
assessment.setAssesseePhone(startVO.getAssesseePhone()); assessment.setAssesseePhone(startVO.getAssesseePhone());
assessment.setStatus("0"); assessment.setStatus("3"); // 初始状态为"已暂停"
assessment.setStartTime(new Date()); assessment.setStartTime(new Date());
assessment.setIpAddress(IpUtils.getIpAddr()); assessment.setIpAddress(IpUtils.getIpAddr());
assessment.setUserAgent(ServletUtils.getRequest().getHeader("User-Agent")); assessment.setUserAgent(ServletUtils.getRequest().getHeader("User-Agent"));
// 设置用户ID优先使用targetUserId
if (assessmentUserId != null) { if (assessmentUserId != null) {
assessment.setUserId(assessmentUserId); assessment.setUserId(assessmentUserId);
} }
// 设置创建者记录是谁创建的测评
if (currentUsername != null && !currentUsername.isEmpty()) { if (currentUsername != null && !currentUsername.isEmpty()) {
assessment.setCreateBy(currentUsername); assessment.setCreateBy(currentUsername);
} else { } else {
assessment.setCreateBy("system"); // 默认值 assessment.setCreateBy("system");
} }
logger.info("创建测评 - scaleId: {}, targetUserId: {}, assessmentUserId: {}, currentUserId: {}, assesseeName: {}", logger.info("创建测评 - scaleId: {}, targetUserId: {}, assessmentUserId: {}, currentUserId: {}, assesseeName: {}",
@ -252,6 +271,53 @@ public class PsyAssessmentController extends BaseController
} }
} }
/**
* 重置测评删除未完成的测评记录重新开始
* 允许管理员和学员访问学员只能重置自己的测评
*/
@PostMapping("/reset")
public AjaxResult reset(@RequestBody AssessmentStartVO startVO)
{
try {
Long currentUserId = SecurityUtils.getUserId();
Long assessmentUserId = startVO.getTargetUserId() != null ? startVO.getTargetUserId() : currentUserId;
if (assessmentUserId == null || startVO.getScaleId() == null)
{
return error("参数不完整");
}
// 检查权限学员只能重置自己的测评
boolean hasManagePerm = false;
try {
hasManagePerm = SecurityUtils.hasPermi("psychology:assessment:edit");
} catch (Exception ignored) {}
if (!hasManagePerm && !assessmentUserId.equals(currentUserId)) {
return error("无权重置其他用户的测评");
}
// 查找该用户对该量表的所有测评记录
List<PsyAssessment> existingList = assessmentService.selectAssessmentByUserAndScale(assessmentUserId, startVO.getScaleId());
// 删除所有未完成的测评
for (PsyAssessment existing : existingList)
{
if ("0".equals(existing.getStatus()) || "3".equals(existing.getStatus()))
{
assessmentService.deleteAssessmentByIds(new Long[]{existing.getAssessmentId()});
logger.info("删除未完成的测评记录 - assessmentId: {}", existing.getAssessmentId());
}
}
// 创建新的测评记录
return start(startVO);
} catch (Exception e) {
logger.error("重置测评异常", e);
return error("重置测评失败:" + e.getMessage());
}
}
/** /**
* 修改测评 * 修改测评
*/ */

View File

@ -49,6 +49,15 @@ public interface PsyAssessmentMapper
*/ */
public List<PsyAssessment> selectPausedAssessmentList(Long userId); public List<PsyAssessment> selectPausedAssessmentList(Long userId);
/**
* 查询用户对某个量表的所有测评记录不过滤用于内部逻辑
*
* @param userId 用户ID
* @param scaleId 量表ID
* @return 测评集合
*/
public List<PsyAssessment> selectAssessmentByUserAndScale(@Param("userId") Long userId, @Param("scaleId") Long scaleId);
/** /**
* 新增测评 * 新增测评
* *

View File

@ -85,6 +85,12 @@ public class PsyAssessmentServiceImpl implements IPsyAssessmentService
return assessmentMapper.selectPausedAssessmentList(userId); return assessmentMapper.selectPausedAssessmentList(userId);
} }
@Override
public List<PsyAssessment> selectAssessmentByUserAndScale(Long userId, Long scaleId)
{
return assessmentMapper.selectAssessmentByUserAndScale(userId, scaleId);
}
@Override @Override
public int insertAssessment(PsyAssessment assessment) public int insertAssessment(PsyAssessment assessment)
{ {

View File

@ -141,16 +141,10 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService
profile.setInfoNumber(infoNumber); profile.setInfoNumber(infoNumber);
validateInfoNumberUnique(infoNumber, null); validateInfoNumberUnique(infoNumber, null);
// 验证姓名如果提供只能包含汉字和数字 // 处理姓名去除首尾空格
if (StringUtils.isNotEmpty(profile.getUserName())) if (StringUtils.isNotEmpty(profile.getUserName()))
{ {
String userName = profile.getUserName().trim(); profile.setUserName(profile.getUserName().trim());
if (!userName.matches("^[\\u4e00-\\u9fa5\\d]+$"))
{
log.error("创建用户档案失败姓名格式错误只能输入汉字和数字userName: {}", userName);
throw new ServiceException("姓名只能输入汉字和数字");
}
profile.setUserName(userName);
} }
Long userId = profile.getUserId(); Long userId = profile.getUserId();
@ -311,15 +305,10 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService
profile.setInfoNumber(infoNumber); profile.setInfoNumber(infoNumber);
validateInfoNumberUnique(infoNumber, profile.getProfileId()); validateInfoNumberUnique(infoNumber, profile.getProfileId());
// 验证姓名如果提供只能包含汉字和数字 // 处理姓名去除首尾空格并同步到用户表
if (StringUtils.isNotEmpty(profile.getUserName())) if (StringUtils.isNotEmpty(profile.getUserName()))
{ {
String userName = profile.getUserName().trim(); String userName = profile.getUserName().trim();
if (!userName.matches("^[\\u4e00-\\u9fa5\\d]+$"))
{
log.error("修改用户档案失败姓名格式错误只能输入汉字和数字userName: {}", userName);
throw new ServiceException("姓名只能输入汉字和数字");
}
profile.setUserName(userName); profile.setUserName(userName);
syncUserName(profile.getUserId(), userName); syncUserName(profile.getUserId(), userName);
} }

View File

@ -48,6 +48,15 @@ public interface IPsyAssessmentService
*/ */
public List<PsyAssessment> selectPausedAssessmentList(Long userId); public List<PsyAssessment> selectPausedAssessmentList(Long userId);
/**
* 查询用户对某个量表的所有测评记录不过滤用于内部逻辑
*
* @param userId 用户ID
* @param scaleId 量表ID
* @return 测评集合
*/
public List<PsyAssessment> selectAssessmentByUserAndScale(Long userId, Long scaleId);
/** /**
* 新增测评 * 新增测评
* *

View File

@ -34,7 +34,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectAssessmentVo"> <sql id="selectAssessmentVo">
select a.assessment_id, a.scale_id, s.scale_name, a.user_id, a.assessee_name, a.assessee_gender, a.assessee_age, select a.assessment_id, a.scale_id, s.scale_name, a.user_id,
COALESCE(u.nick_name, a.assessee_name) as assessee_name,
a.assessee_gender, a.assessee_age,
a.assessee_id_card, a.assessee_phone, a.assessee_email, a.start_time, a.pause_time, a.assessee_id_card, a.assessee_phone, a.assessee_email, a.start_time, a.pause_time,
a.resume_time, a.pause_count, a.submit_time, a.complete_time, a.total_score, a.status, a.resume_time, a.pause_count, a.submit_time, a.complete_time, a.total_score, a.status,
a.ip_address, a.user_agent, a.ip_address, a.user_agent,
@ -43,6 +45,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
from psy_assessment a from psy_assessment a
left join psy_scale s on a.scale_id = s.scale_id left join psy_scale s on a.scale_id = s.scale_id
left join psy_assessment_report r on a.assessment_id = r.assessment_id left join psy_assessment_report r on a.assessment_id = r.assessment_id
left join sys_user u on a.user_id = u.user_id
</sql> </sql>
<select id="selectAssessmentById" parameterType="Long" resultMap="PsyAssessmentResult"> <select id="selectAssessmentById" parameterType="Long" resultMap="PsyAssessmentResult">
@ -53,6 +56,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectAssessmentList" parameterType="com.ddnai.system.domain.psychology.PsyAssessment" resultMap="PsyAssessmentResult"> <select id="selectAssessmentList" parameterType="com.ddnai.system.domain.psychology.PsyAssessment" resultMap="PsyAssessmentResult">
<include refid="selectAssessmentVo"/> <include refid="selectAssessmentVo"/>
<where> <where>
<!-- 只显示每个用户对每个量表的最新记录 -->
a.assessment_id IN (
SELECT MAX(t.assessment_id)
FROM psy_assessment t
GROUP BY t.user_id, t.scale_id
)
<if test="scaleId != null"> <if test="scaleId != null">
AND a.scale_id = #{scaleId} AND a.scale_id = #{scaleId}
</if> </if>
@ -66,13 +75,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND a.status = #{status} AND a.status = #{status}
</if> </if>
</where> </where>
order by a.create_time desc order by a.start_time desc
</select> </select>
<select id="selectAssessmentListByUserId" parameterType="Long" resultMap="PsyAssessmentResult"> <select id="selectAssessmentListByUserId" parameterType="Long" resultMap="PsyAssessmentResult">
<include refid="selectAssessmentVo"/> <include refid="selectAssessmentVo"/>
where a.user_id = #{userId} where a.user_id = #{userId}
order by a.create_time desc <!-- 只显示每个量表的最新记录 -->
AND a.assessment_id IN (
SELECT MAX(t.assessment_id)
FROM psy_assessment t
WHERE t.user_id = #{userId}
GROUP BY t.scale_id
)
order by a.start_time desc
</select> </select>
<select id="selectPausedAssessmentList" parameterType="Long" resultMap="PsyAssessmentResult"> <select id="selectPausedAssessmentList" parameterType="Long" resultMap="PsyAssessmentResult">
@ -86,6 +102,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
order by a.pause_time desc order by a.pause_time desc
</select> </select>
<!-- 查询用户对某个量表的所有测评记录(不过滤,用于内部逻辑) -->
<select id="selectAssessmentByUserAndScale" resultMap="PsyAssessmentResult">
<include refid="selectAssessmentVo"/>
where a.user_id = #{userId} and a.scale_id = #{scaleId}
order by a.assessment_id desc
</select>
<insert id="insertAssessment" parameterType="com.ddnai.system.domain.psychology.PsyAssessment" useGeneratedKeys="true" keyProperty="assessmentId"> <insert id="insertAssessment" parameterType="com.ddnai.system.domain.psychology.PsyAssessment" useGeneratedKeys="true" keyProperty="assessmentId">
insert into psy_assessment ( insert into psy_assessment (
<if test="scaleId != null">scale_id, </if> <if test="scaleId != null">scale_id, </if>
@ -130,6 +153,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="assesseeGender != null">assessee_gender = #{assesseeGender}, </if> <if test="assesseeGender != null">assessee_gender = #{assesseeGender}, </if>
<if test="assesseeAge != null">assessee_age = #{assesseeAge}, </if> <if test="assesseeAge != null">assessee_age = #{assesseeAge}, </if>
<if test="totalScore != null">total_score = #{totalScore}, </if> <if test="totalScore != null">total_score = #{totalScore}, </if>
<if test="startTime != null">start_time = #{startTime}, </if>
<if test="submitTime != null">submit_time = #{submitTime}, </if> <if test="submitTime != null">submit_time = #{submitTime}, </if>
<if test="completeTime != null">complete_time = #{completeTime}, </if> <if test="completeTime != null">complete_time = #{completeTime}, </if>
<if test="status != null">status = #{status}, </if> <if test="status != null">status = #{status}, </if>
@ -155,6 +179,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
update psy_assessment update psy_assessment
<set> <set>
resume_time = sysdate(), resume_time = sysdate(),
start_time = sysdate(),
status = '0', status = '0',
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = sysdate() update_time = sysdate()

View File

@ -40,7 +40,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
p.prison, p.prison_area, p.gender, p.nation, p.education_level, p.crime_name, p.prison, p.prison_area, p.gender, p.nation, p.education_level, p.crime_name,
p.sentence_term, p.sentence_start_date, p.sentence_end_date, p.entry_date, p.sentence_term, p.sentence_start_date, p.sentence_end_date, p.entry_date,
p.info_number, p.create_by, p.create_time, p.update_by, p.update_time, p.remark, p.info_number, p.create_by, p.create_time, p.update_by, p.update_time, p.remark,
u.user_name, u.phonenumber as phone, u.nick_name, u.email, u.sex, u.status, u.dept_id, u.create_time as user_create_time u.nick_name as user_name, u.phonenumber as phone, u.nick_name, u.email, u.sex, u.status, u.dept_id, u.create_time as user_create_time
</sql> </sql>
<select id="selectProfileById" parameterType="Long" resultMap="PsyUserProfileResult"> <select id="selectProfileById" parameterType="Long" resultMap="PsyUserProfileResult">
@ -69,7 +69,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<!-- 查询用户档案列表,从 sys_user 开始LEFT JOIN psy_user_profile与系统用户管理查询一致 --> <!-- 查询用户档案列表,从 sys_user 开始LEFT JOIN psy_user_profile与系统用户管理查询一致 -->
<select id="selectProfileList" parameterType="com.ddnai.system.domain.psychology.PsyUserProfile" resultMap="PsyUserProfileResult"> <select id="selectProfileList" parameterType="com.ddnai.system.domain.psychology.PsyUserProfile" resultMap="PsyUserProfileResult">
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar as user_avatar, u.phonenumber as phone, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by as user_create_by, u.create_time as user_create_time, u.remark as user_remark, select u.user_id, u.dept_id, u.nick_name, u.nick_name as user_name, u.email, u.avatar as user_avatar, u.phonenumber as phone, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by as user_create_by, u.create_time as user_create_time, u.remark as user_remark,
d.dept_name, d.leader, d.dept_name, d.leader,
p.profile_id, p.profile_type, p.profile_data, p.avatar, p.id_card, p.birthday, p.profile_id, p.profile_type, p.profile_data, p.avatar, p.id_card, p.birthday,
p.prison, p.prison_area, p.gender, p.nation, p.education_level, p.crime_name, p.prison, p.prison_area, p.gender, p.nation, p.education_level, p.crime_name,
@ -83,7 +83,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND u.user_id = #{userId} AND u.user_id = #{userId}
</if> </if>
<if test="userName != null and userName != ''"> <if test="userName != null and userName != ''">
AND u.user_name like concat('%', #{userName}, '%') AND u.nick_name like concat('%', #{userName}, '%')
</if> </if>
<if test="profileType != null and profileType != ''"> <if test="profileType != null and profileType != ''">
AND p.profile_type = #{profileType} AND p.profile_type = #{profileType}
@ -215,7 +215,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<!-- 查询学员用户档案列表:只返回拥有学员角色的用户 --> <!-- 查询学员用户档案列表:只返回拥有学员角色的用户 -->
<select id="selectStudentProfileList" parameterType="com.ddnai.system.domain.psychology.PsyUserProfile" resultMap="PsyUserProfileResult"> <select id="selectStudentProfileList" parameterType="com.ddnai.system.domain.psychology.PsyUserProfile" resultMap="PsyUserProfileResult">
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar as user_avatar, u.phonenumber as phone, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by as user_create_by, u.create_time as user_create_time, u.remark as user_remark, select u.user_id, u.dept_id, u.nick_name, u.nick_name as user_name, u.email, u.avatar as user_avatar, u.phonenumber as phone, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by as user_create_by, u.create_time as user_create_time, u.remark as user_remark,
d.dept_name, d.leader, d.dept_name, d.leader,
p.profile_id, p.profile_type, p.profile_data, p.avatar, p.id_card, p.birthday, p.profile_id, p.profile_type, p.profile_data, p.avatar, p.id_card, p.birthday,
p.prison, p.prison_area, p.gender, p.nation, p.education_level, p.crime_name, p.prison, p.prison_area, p.gender, p.nation, p.education_level, p.crime_name,
@ -236,7 +236,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND u.user_id = #{userId} AND u.user_id = #{userId}
</if> </if>
<if test="userName != null and userName != ''"> <if test="userName != null and userName != ''">
AND u.user_name like concat('%', #{userName}, '%') AND u.nick_name like concat('%', #{userName}, '%')
</if> </if>
<if test="profileType != null and profileType != ''"> <if test="profileType != null and profileType != ''">
AND p.profile_type = #{profileType} AND p.profile_type = #{profileType}

View File

@ -63,7 +63,10 @@ export function saveAnswer(data) {
return request({ return request({
url: '/psychology/assessment/answer', url: '/psychology/assessment/answer',
method: 'post', method: 'post',
data: data data: data,
headers: {
repeatSubmit: false // 跳过防重复提交检查,允许快速保存答案
}
}) })
} }
@ -71,7 +74,10 @@ export function saveAnswer(data) {
export function pauseAssessment(assessmentId) { export function pauseAssessment(assessmentId) {
return request({ return request({
url: '/psychology/assessment/pause/' + assessmentId, url: '/psychology/assessment/pause/' + assessmentId,
method: 'post' method: 'post',
headers: {
repeatSubmit: false // 跳过防重复提交检查
}
}) })
} }
@ -79,7 +85,10 @@ export function pauseAssessment(assessmentId) {
export function resumeAssessment(assessmentId) { export function resumeAssessment(assessmentId) {
return request({ return request({
url: '/psychology/assessment/resume/' + assessmentId, url: '/psychology/assessment/resume/' + assessmentId,
method: 'post' method: 'post',
headers: {
repeatSubmit: false // 跳过防重复提交检查
}
}) })
} }
@ -145,3 +154,12 @@ export function getScaleDeptStats(data) {
}) })
} }
// 重置测评(删除未完成的测评记录,重新开始)
export function resetAssessment(data) {
return request({
url: '/psychology/assessment/reset',
method: 'post',
data: data
})
}

View File

@ -104,6 +104,15 @@
v-if="scope.row.status === '0' || scope.row.status === '3'" v-if="scope.row.status === '0' || scope.row.status === '3'"
v-hasPermi="['psychology:assessment:edit']" v-hasPermi="['psychology:assessment:edit']"
>继续答题</el-button> >继续答题</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh"
style="color: #E6A23C;"
@click="handleReset(scope.row)"
v-if="scope.row.status === '0' || scope.row.status === '3'"
v-hasPermi="['psychology:assessment:edit']"
>重新开始</el-button>
<el-button <el-button
size="mini" size="mini"
type="text" type="text"
@ -190,7 +199,7 @@
</template> </template>
<script> <script>
import { listAssessment, getAssessment, delAssessment, pausedAssessmentList, getAssessmentAnswers, getAssessmentItems } from "@/api/psychology/assessment"; import { listAssessment, getAssessment, delAssessment, pausedAssessmentList, getAssessmentAnswers, getAssessmentItems, resetAssessment } from "@/api/psychology/assessment";
import { listScale } from "@/api/psychology/scale"; import { listScale } from "@/api/psychology/scale";
import { listOption } from "@/api/psychology/option"; import { listOption } from "@/api/psychology/option";
@ -317,6 +326,31 @@ export default {
this.$modal.msgSuccess("删除成功"); this.$modal.msgSuccess("删除成功");
}).catch(() => {}); }).catch(() => {});
}, },
/** 重置测评(删除当前记录,重新开始) */
handleReset(row) {
this.$modal.confirm('确定要重新开始测评吗?当前的答题记录将被清除。').then(() => {
const resetData = {
scaleId: row.scaleId,
targetUserId: row.userId,
assesseeName: row.assesseeName,
assesseeGender: row.assesseeGender || '0',
assesseeAge: row.assesseeAge || 0,
assesseePhone: row.assesseePhone,
anonymous: false
};
resetAssessment(resetData).then(response => {
if (response.code === 200) {
this.$modal.msgSuccess("测评已重置");
const assessmentId = response.data;
this.$router.push({ path: '/psychology/assessment/taking', query: { assessmentId: assessmentId } });
}
}).catch(error => {
console.error('重置测评失败:', error);
this.$modal.msgError("重置测评失败,请重试");
});
});
},
/** 查看答题详情 */ /** 查看答题详情 */
handleViewAnswers(row) { handleViewAnswers(row) {
this.currentAssessment = row; this.currentAssessment = row;

View File

@ -67,7 +67,7 @@
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span> <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" width="150"> <el-table-column label="操作" align="center" width="200">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button <el-button
size="mini" size="mini"
@ -75,6 +75,13 @@
icon="el-icon-edit-outline" icon="el-icon-edit-outline"
@click="handleContinue(scope.row)" @click="handleContinue(scope.row)"
>继续测评</el-button> >继续测评</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh"
style="color: #E6A23C;"
@click="handleReset(scope.row)"
>重新开始</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -83,7 +90,7 @@
</template> </template>
<script> <script>
import { startAssessment, pausedAssessmentList, resumeAssessment } from "@/api/psychology/assessment"; import { startAssessment, pausedAssessmentList, resumeAssessment, resetAssessment } from "@/api/psychology/assessment";
import { listScale } from "@/api/psychology/scale"; import { listScale } from "@/api/psychology/scale";
import { listStudentProfile } from "@/api/psychology/profile"; import { listStudentProfile } from "@/api/psychology/profile";
@ -459,6 +466,36 @@ export default {
this.$router.push({ path: '/psychology/assessment/taking', query: { assessmentId: row.assessmentId } }); this.$router.push({ path: '/psychology/assessment/taking', query: { assessmentId: row.assessmentId } });
} }
}, },
/** 重置测评(删除当前记录,重新开始) */
handleReset(row) {
this.$modal.confirm('确定要重新开始测评吗?当前的答题记录将被清除。').then(() => {
this.loading = true;
//
const profile = this.profileList.find(p => p.userId === row.userId);
const resetData = {
scaleId: row.scaleId,
targetUserId: row.userId,
assesseeName: profile ? profile.userName : row.assesseeName,
assesseeGender: '0',
assesseeAge: 0,
assesseePhone: profile ? profile.phone : null,
anonymous: false
};
resetAssessment(resetData).then(response => {
if (response.code === 200) {
this.$modal.msgSuccess("测评已重置");
const assessmentId = response.data;
this.$router.push({ path: '/psychology/assessment/taking', query: { assessmentId: assessmentId } });
}
this.loading = false;
}).catch(error => {
console.error('重置测评失败:', error);
this.$modal.msgError("重置测评失败,请重试");
this.loading = false;
});
});
},
/** 返回 */ /** 返回 */
handleBack() { handleBack() {
this.$router.push('assessment'); this.$router.push('assessment');

View File

@ -67,7 +67,7 @@
v-for="option in currentOptions" v-for="option in currentOptions"
:key="option.optionId" :key="option.optionId"
class="option-item" class="option-item"
@click="selectedOption = option.optionId" @click="selectSingleOption(option.optionId)"
> >
<el-radio :label="option.optionId">{{ option.optionCode }}. {{ option.optionContent }}</el-radio> <el-radio :label="option.optionId">{{ option.optionCode }}. {{ option.optionContent }}</el-radio>
<el-button <el-button
@ -139,7 +139,7 @@
</template> </template>
<script> <script>
import { getAssessment, pauseAssessment, getAssessmentItems, submitAssessment, getAssessmentAnswers } from "@/api/psychology/assessment"; import { getAssessment, pauseAssessment, resumeAssessment, getAssessmentItems, submitAssessment, getAssessmentAnswers } from "@/api/psychology/assessment";
import { listOption } from "@/api/psychology/option"; import { listOption } from "@/api/psychology/option";
import { saveAnswer } from "@/api/psychology/assessment"; import { saveAnswer } from "@/api/psychology/assessment";
import voiceIcon from '@/assets/images/voice.png'; import voiceIcon from '@/assets/images/voice.png';
@ -158,6 +158,7 @@ export default {
answersMap: {}, answersMap: {},
loading: false, loading: false,
saveTimer: null, // saveTimer: null, //
hasResumed: false, // resume API
// TTS // TTS
isTtsSupported: false, isTtsSupported: false,
useAndroidTts: false, useAndroidTts: false,
@ -184,13 +185,15 @@ export default {
answeredCount() { answeredCount() {
// //
return this.itemList.filter(item => { return this.itemList.filter(item => {
const answer = this.answersMap[item.itemId]; // 使 key
const itemIdStr = String(item.itemId);
const answer = this.answersMap[item.itemId] || this.answersMap[itemIdStr];
if (!answer) { if (!answer) {
return false; return false;
} }
// optionId optionIds // optionId optionIds
if (item.itemType === 'single') { if (item.itemType === 'single' || item.itemType === 'matrix') {
return answer.optionId != null; return answer.optionId != null && answer.optionId !== '';
} else if (item.itemType === 'multiple') { } else if (item.itemType === 'multiple') {
return answer.optionIds != null && answer.optionIds.trim().length > 0; return answer.optionIds != null && answer.optionIds.trim().length > 0;
} }
@ -409,20 +412,36 @@ export default {
// //
const savedAnswers = answersRes.data || []; const savedAnswers = answersRes.data || [];
console.log('从服务器加载的答案数量:', savedAnswers.length);
savedAnswers.forEach(answer => { savedAnswers.forEach(answer => {
this.answersMap[answer.itemId] = { // 使 $set 使 itemId key
const itemIdKey = answer.itemId;
this.$set(this.answersMap, itemIdKey, {
assessmentId: answer.assessmentId, assessmentId: answer.assessmentId,
itemId: answer.itemId, itemId: answer.itemId,
optionId: answer.optionId, optionId: answer.optionId,
optionIds: answer.optionIds, optionIds: answer.optionIds,
answerScore: answer.answerScore answerScore: answer.answerScore
}; });
}); });
console.log('加载后的 answersMap:', JSON.stringify(this.answersMap));
// //
this.loadAllOptions().then(() => { this.loadAllOptions().then(() => {
// //
this.loadCurrentAnswer(); this.loadCurrentAnswer();
// resume API ""
//
if (!this.hasResumed) {
this.hasResumed = true;
resumeAssessment(this.assessmentId).then(() => {
console.log('测评状态已更新为进行中');
}).catch(error => {
console.warn('更新测评状态失败:', error);
//
});
}
}).catch(error => { }).catch(error => {
console.error('加载选项失败:', error); console.error('加载选项失败:', error);
this.$modal.msgError("加载题目选项失败,请刷新重试"); this.$modal.msgError("加载题目选项失败,请刷新重试");
@ -454,14 +473,14 @@ export default {
handleAnswerChange() { handleAnswerChange() {
const itemId = this.currentItem.itemId; const itemId = this.currentItem.itemId;
const answer = { const answer = {
assessmentId: this.assessmentId, assessmentId: parseInt(this.assessmentId),
itemId: itemId, itemId: parseInt(itemId),
optionId: null, optionId: null,
optionIds: null, optionIds: null,
answerScore: 0 answerScore: 0
}; };
if (this.currentItem.itemType === 'single') { if (this.currentItem.itemType === 'single' || this.currentItem.itemType === 'matrix') {
answer.optionId = this.selectedOption; answer.optionId = this.selectedOption;
// //
const selectedOpt = this.currentOptions.find(opt => opt.optionId === this.selectedOption); const selectedOpt = this.currentOptions.find(opt => opt.optionId === this.selectedOption);
@ -470,6 +489,7 @@ export default {
} }
// 使 $set // 使 $set
this.$set(this.answersMap, itemId, answer); this.$set(this.answersMap, itemId, answer);
console.log('单选题答案已保存到本地 - itemId:', itemId, 'optionId:', answer.optionId, 'answersMap:', JSON.stringify(this.answersMap));
} else if (this.currentItem.itemType === 'multiple') { } else if (this.currentItem.itemType === 'multiple') {
answer.optionIds = this.selectedOptions.length > 0 ? this.selectedOptions.join(',') : null; answer.optionIds = this.selectedOptions.length > 0 ? this.selectedOptions.join(',') : null;
// //
@ -483,6 +503,7 @@ export default {
answer.answerScore = totalScore; answer.answerScore = totalScore;
// 使 $set // 使 $set
this.$set(this.answersMap, itemId, answer); this.$set(this.answersMap, itemId, answer);
console.log('多选题答案已保存到本地 - itemId:', itemId, 'optionIds:', answer.optionIds);
} }
// //
@ -501,11 +522,13 @@ export default {
saveAnswerToServer(answer) { saveAnswerToServer(answer) {
// //
if (!answer.optionId && !answer.optionIds) { if (!answer.optionId && !answer.optionIds) {
console.log('答案无效,跳过保存 - optionId:', answer.optionId, 'optionIds:', answer.optionIds);
return; // return; //
} }
saveAnswer(answer).then(() => { console.log('正在保存答案到服务器:', JSON.stringify(answer));
// saveAnswer(answer).then((response) => {
console.log('答案保存成功:', response);
}).catch(error => { }).catch(error => {
console.error('保存答案失败:', error); console.error('保存答案失败:', error);
// //
@ -529,6 +552,14 @@ export default {
this.loadCurrentAnswer(); this.loadCurrentAnswer();
} }
}, },
/** 选择单选选项 */
selectSingleOption(optionId) {
//
if (this.selectedOption !== optionId) {
this.selectedOption = optionId;
this.handleAnswerChange();
}
},
/** 切换多选选项 */ /** 切换多选选项 */
toggleMultipleOption(optionId) { toggleMultipleOption(optionId) {
const index = this.selectedOptions.indexOf(optionId); const index = this.selectedOptions.indexOf(optionId);
@ -537,6 +568,8 @@ export default {
} else { } else {
this.selectedOptions.push(optionId); this.selectedOptions.push(optionId);
} }
//
this.handleAnswerChange();
}, },
/** 加载当前题目的答案 */ /** 加载当前题目的答案 */
loadCurrentAnswer() { loadCurrentAnswer() {
@ -544,10 +577,13 @@ export default {
return; return;
} }
const itemId = this.currentItem.itemId; const itemId = this.currentItem.itemId;
const answer = this.answersMap[itemId]; // key
const answer = this.answersMap[itemId] || this.answersMap[String(itemId)];
if (this.currentItem.itemType === 'single') { console.log('加载题目答案 - itemId:', itemId, 'itemType:', this.currentItem.itemType, 'answer:', answer);
this.selectedOption = answer && answer.optionId ? answer.optionId : null;
if (this.currentItem.itemType === 'single' || this.currentItem.itemType === 'matrix') {
this.selectedOption = answer && answer.optionId != null ? answer.optionId : null;
this.selectedOptions = []; this.selectedOptions = [];
} else if (this.currentItem.itemType === 'multiple') { } else if (this.currentItem.itemType === 'multiple') {
this.selectedOption = null; this.selectedOption = null;
@ -795,6 +831,7 @@ export default {
this.answersMap = {}; this.answersMap = {};
this.loading = false; this.loading = false;
this.isTtsSupported = false; this.isTtsSupported = false;
this.hasResumed = false; // resume
if (this.saveTimer) { if (this.saveTimer) {
clearTimeout(this.saveTimer); clearTimeout(this.saveTimer);
this.saveTimer = null; this.saveTimer = null;

View File

@ -213,8 +213,7 @@
<el-form-item label="罪犯姓名" prop="userName"> <el-form-item label="罪犯姓名" prop="userName">
<el-input <el-input
v-model="form.userName" v-model="form.userName"
placeholder="请输入姓名(仅汉字)" placeholder="请输入姓名"
@input="handleUserNameInput"
maxlength="50" maxlength="50"
/> />
</el-form-item> </el-form-item>
@ -549,8 +548,7 @@ export default {
{ pattern: /^\d+$/, message: "信息编号只能输入数字", trigger: "blur" } { pattern: /^\d+$/, message: "信息编号只能输入数字", trigger: "blur" }
], ],
userName: [ userName: [
{ required: true, message: "罪犯姓名不能为空", trigger: "blur" }, { required: true, message: "罪犯姓名不能为空", trigger: "blur" }
{ pattern: /^[\u4e00-\u9fa5\d]+$/, message: "姓名只能输入汉字和数字", trigger: "blur" }
], ],
prisonArea: [ prisonArea: [
{ required: true, message: "监区不能为空", trigger: "blur" } { required: true, message: "监区不能为空", trigger: "blur" }
@ -718,11 +716,7 @@ export default {
const numericValue = value.replace(/\D/g, '') const numericValue = value.replace(/\D/g, '')
this.form.infoNumber = numericValue this.form.infoNumber = numericValue
}, },
//
handleUserNameInput(value) {
//
this.form.userName = value.replace(/[^\u4e00-\u9fa5\d]/g, '')
},
/** 搜索按钮操作 */ /** 搜索按钮操作 */
handleQuery() { handleQuery() {
this.queryParams.pageNum = 1 this.queryParams.pageNum = 1

View File

@ -151,15 +151,15 @@ export default {
generating: false, generating: false,
reportDialogVisible: false, reportDialogVisible: false,
comprehensiveReport: '', comprehensiveReport: '',
// ========== Ollama ========== // ========== Kimi API ==========
API_URL: 'http://192.168.0.106:11434/api/chat', API_URL: 'https://api.moonshot.cn/v1/chat/completions',
API_KEY: '', // API Key API_KEY: 'sk-U9fdriPxwBcrpWW0Ite3N0eVtX7VxnqqqYUIBAdWd1hgEA9m',
MODEL: 'deepseek-r1:32b', MODEL: 'moonshot-v1-32k',
// ========== Kimi API========== // ========== Ollama==========
// API_URL: 'https://api.moonshot.cn/v1/chat/completions', // API_URL: 'http://192.168.0.106:11434/api/chat',
// API_KEY: 'sk-U9fdriPxwBcrpWW0Ite3N0eVtX7VxnqqqYUIBAdWd1hgEA9m', // API_KEY: '', // API Key
// MODEL: 'moonshot-v1-32k' // MODEL: 'deepseek-r1:32b'
} }
}, },
created() { created() {

View File

@ -403,14 +403,14 @@ export default {
this.aiError = ''; this.aiError = '';
this.aiResult = ''; this.aiResult = '';
// Ollama // Kimi API
const API_URL = 'http://192.168.0.106:11434/api/chat'; const API_URL = 'https://api.moonshot.cn/v1/chat/completions';
const API_KEY = ''; // API Key const API_KEY = 'sk-U9fdriPxwBcrpWW0Ite3N0eVtX7VxnqqqYUIBAdWd1hgEA9m';
const MODEL = 'deepseek-r1:32b'; const MODEL = 'moonshot-v1-32k';
// Kimi API // Ollama
// const API_URL = 'https://api.moonshot.cn/v1/chat/completions'; // const API_URL = 'http://192.168.0.106:11434/api/chat';
// const API_KEY = 'sk-U9fdriPxwBcrpWW0Ite3N0eVtX7VxnqqqYUIBAdWd1hgEA9m'; // const API_KEY = ''; // API Key
// const MODEL = 'moonshot-v1-32k'; // const MODEL = 'deepseek-r1:32b';
// //
const SYSTEM_PROMPT = [ const SYSTEM_PROMPT = [
@ -451,7 +451,8 @@ export default {
timeout: 60000 // 60 timeout: 60000 // 60
}); });
let rawResponse = data?.choices?.[0]?.message?.content ?? '无法解析模型输出'; // Kimi API (OpenAI ) Ollama
let rawResponse = data?.choices?.[0]?.message?.content ?? data?.message?.content ?? '无法解析模型输出';
// //
rawResponse = rawResponse rawResponse = rawResponse

View File

@ -709,14 +709,14 @@ export default {
}, },
/** AI分析报告内容 */ /** AI分析报告内容 */
async generateAIAnalysis(reportContent, reportTitle, reportType) { async generateAIAnalysis(reportContent, reportTitle, reportType) {
// Ollama // Kimi API
const API_URL = 'http://192.168.0.106:11434/api/chat'; const API_URL = 'https://api.moonshot.cn/v1/chat/completions';
const API_KEY = ''; // API Key const API_KEY = 'sk-U9fdriPxwBcrpWW0Ite3N0eVtX7VxnqqqYUIBAdWd1hgEA9m';
const MODEL = 'deepseek-r1:32b'; const MODEL = 'moonshot-v1-32k';
// Kimi API // Ollama
// const API_URL = 'https://api.moonshot.cn/v1/chat/completions'; // const API_URL = 'http://192.168.0.106:11434/api/chat';
// const API_KEY = 'sk-U9fdriPxwBcrpWW0Ite3N0eVtX7VxnqqqYUIBAdWd1hgEA9m'; // const API_KEY = ''; // API Key
// const MODEL = 'moonshot-v1-32k'; // const MODEL = 'deepseek-r1:32b';
// //
const SYSTEM_PROMPT = [ const SYSTEM_PROMPT = [
@ -752,7 +752,10 @@ export default {
timeout: 60000 // 60 timeout: 60000 // 60
}); });
let rawResponse = data?.choices?.[0]?.message?.content ?? ''; console.log('AI响应数据:', data);
// Kimi API (OpenAI ) Ollama
let rawResponse = data?.choices?.[0]?.message?.content ?? data?.message?.content ?? '';
// //
rawResponse = rawResponse rawResponse = rawResponse

View File

@ -68,7 +68,7 @@
* 作用显示所有开放的心理测试题学员可以选择进行测试 * 作用显示所有开放的心理测试题学员可以选择进行测试
*/ */
import { listScale } from "@/api/psychology/scale" import { listScale } from "@/api/psychology/scale"
import { startAssessment, pausedAssessmentList, resumeAssessment } from "@/api/psychology/assessment" import { startAssessment, pausedAssessmentList, resumeAssessment, resetAssessment } from "@/api/psychology/assessment"
import { Message } from 'element-ui' import { Message } from 'element-ui'
export default { export default {
@ -304,17 +304,36 @@ export default {
} }
this.loading = true this.loading = true
const { delAssessment } = require("@/api/psychology/assessment") // 使 resetAssessment API
delAssessment(pausedRecord.assessmentId) const resetData = {
.then(() => { scaleId: pausedRecord.scaleId || test.scaleId,
// targetUserId: pausedRecord.userId,
this.loadPausedList() assesseeName: pausedRecord.assesseeName,
this.createAssessment(test) assesseeGender: pausedRecord.assesseeGender || '0',
assesseeAge: pausedRecord.assesseeAge || 0,
assesseePhone: pausedRecord.assesseePhone,
anonymous: false
}
resetAssessment(resetData)
.then(response => {
if (response.code === 200) {
//
this.loadPausedList()
const assessmentId = response.data
this.$router.push({
path: '/psychology/assessment/taking',
query: { assessmentId: assessmentId }
})
} else {
Message.error(response.msg || "重置测评失败")
}
this.loading = false
}) })
.catch(error => { .catch(error => {
console.error("删除暂停记录失败:", error) console.error("重置测评失败:", error)
this.loading = false this.loading = false
Message.error("删除旧记录失败,请稍后重试") Message.error("重置测评失败,请稍后重试")
}) })
}, },