From 9562e32697ee50bc7f20056eafd25c23ef8656ab Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Fri, 18 Jul 2025 22:08:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/HqSatSurveyServiceImpl.java | 374 ++++++++++++++---- docs/开发文档/API/后勤满意度调查API.md | 202 ++++++++++ web/src/api/hq/satSurvey/types.ts | 1 + web/src/assets/styles/index.scss | 6 +- web/src/assets/styles/sidebar.scss | 2 +- 5 files changed, 502 insertions(+), 83 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-hq/src/main/java/org/dromara/hq/service/impl/HqSatSurveyServiceImpl.java b/backend/ruoyi-modules/ruoyi-hq/src/main/java/org/dromara/hq/service/impl/HqSatSurveyServiceImpl.java index c0dd6b2e..e1c020e5 100644 --- a/backend/ruoyi-modules/ruoyi-hq/src/main/java/org/dromara/hq/service/impl/HqSatSurveyServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-hq/src/main/java/org/dromara/hq/service/impl/HqSatSurveyServiceImpl.java @@ -26,6 +26,9 @@ import org.dromara.hq.service.IHqSatDetailService; import org.dromara.hq.domain.bo.HqSatSurveyQuestionBo; import org.dromara.hq.domain.vo.HqSatSurveyQuestionVo; import org.dromara.hq.domain.vo.HqSatDetailVo; +import org.dromara.hq.domain.bo.HqSatOptionBo; +import org.dromara.hq.domain.vo.HqSatOptionVo; +import org.dromara.hq.service.IHqSatOptionService; import java.util.List; import java.util.Map; @@ -58,6 +61,7 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService { private final IHqSatSurveyQuestionService surveyQuestionService; private final IHqSatRecordService recordService; private final IHqSatDetailService detailService; + private final IHqSatOptionService optionService; @Autowired private JdbcTemplate jdbcTemplate; @@ -858,6 +862,7 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService { // 构建返回数据 statistics.put("surveyId", surveyId); + statistics.put("score", survey.getScore()); statistics.put("surveyTitle", survey.getSurveyTitle()); statistics.put("surveyCode", survey.getSurveyCode()); statistics.put("totalParticipants", totalParticipants != null ? totalParticipants : 0); @@ -953,15 +958,15 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService { String optionStatsSql = """ SELECT o.id as option_id, - o.option_value, - o.option_text, + o.option_score, + o.option_content, COUNT(DISTINCT a.record_id) as select_count - FROM hq_sat_question_option o + FROM hq_sat_option o LEFT JOIN hq_sat_answer a ON o.id = a.option_id AND a.deleted = 0 LEFT JOIN hq_sat_record r ON a.record_id = r.id AND r.survey_id = ? WHERE o.question_id = ? AND o.deleted = 0 - GROUP BY o.id, o.option_value, o.option_text - ORDER BY o.option_value + GROUP BY o.id, o.option_score, o.option_content + ORDER BY o.option_score DESC """; java.util.List> optionStats = jdbcTemplate.queryForList(optionStatsSql, surveyId, questionId); @@ -979,13 +984,13 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService { String textAnswersSql = """ SELECT a.id, - a.answer_value as answer_text, - LENGTH(a.answer_value) as text_length, + a.remark as answer_text, + LENGTH(a.remark) as text_length, DATE_FORMAT(r.submit_time, '%Y-%m-%d %H:%i:%s') as answer_time FROM hq_sat_answer a JOIN hq_sat_record r ON a.record_id = r.id WHERE a.question_id = ? AND r.survey_id = ? AND a.answer_value IS NOT NULL - AND a.deleted = 0 AND LENGTH(a.answer_value) > 0 + AND a.deleted = 0 AND LENGTH(a.remark) > 0 ORDER BY r.submit_time DESC LIMIT 10 """; @@ -1040,13 +1045,38 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService { String status = (String) query.get("status"); String userName = (String) query.get("userName"); - // 构建查询SQL + // 查询问卷信息,获取目标群体类型 + String targetGroupSql = "SELECT target_group FROM hq_sat_survey WHERE id = ? AND deleted = 0"; + String targetGroup = jdbcTemplate.queryForObject(targetGroupSql, String.class, surveyId); + + if (targetGroup == null) { + throw new ServiceException("问卷不存在或已被删除"); + } + + // 构建查询SQL - 从用户表查询符合条件的用户,并关联答题记录表 StringBuilder sql = new StringBuilder(); - sql.append("SELECT r.*, u.nick_name as userName, d.dept_name as deptName "); - sql.append("FROM hq_sat_record r "); - sql.append("LEFT JOIN sys_user u ON r.user_id = u.user_id "); + sql.append("SELECT u.user_id, u.nick_name as userName, d.dept_name as deptName, "); + sql.append("rec.id, rec.survey_id, rec.submit_time, rec.total_score, rec.create_time "); + sql.append("FROM sys_user u "); sql.append("LEFT JOIN sys_dept d ON u.dept_id = d.dept_id "); - sql.append("WHERE r.survey_id = ? AND r.deleted = 0 "); + sql.append("LEFT JOIN sys_user_role ur ON u.user_id = ur.user_id "); + sql.append("LEFT JOIN sys_role r ON ur.role_id = r.role_id "); + sql.append("LEFT JOIN hq_sat_record rec ON u.user_id = rec.user_id AND rec.survey_id = ? AND rec.deleted = 0 "); + sql.append("WHERE u.del_flag = '0' "); + + // 根据目标群体过滤用户 + sql.append("AND ("); + if ("1".equals(targetGroup)) { + // 学生群体 + sql.append("r.role_key = 'student'"); + } else if ("2".equals(targetGroup)) { + // 教职工群体 + sql.append("r.role_key = 'teacher'"); + } else if ("3".equals(targetGroup)) { + // 全体群体(学生和教职工) + sql.append("(r.role_key = 'student' OR r.role_key = 'teacher')"); + } + sql.append(") "); java.util.List params = new java.util.ArrayList<>(); params.add(surveyId); @@ -1054,9 +1084,9 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService { // 添加状态过滤 if (status != null && !status.isEmpty()) { if ("1".equals(status)) { - sql.append("AND r.submit_time IS NOT NULL "); + sql.append("AND rec.submit_time IS NOT NULL "); } else if ("0".equals(status)) { - sql.append("AND r.submit_time IS NULL "); + sql.append("AND (rec.submit_time IS NULL OR rec.id IS NULL) "); } } @@ -1066,30 +1096,40 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService { params.add("%" + userName + "%"); } - sql.append("ORDER BY r.create_time DESC "); - - // 分页查询 - int offset = (pageNum - 1) * pageSize; - sql.append("LIMIT ? OFFSET ?"); - params.add(pageSize); - params.add(offset); - - java.util.List> records = jdbcTemplate.queryForList(sql.toString(), params.toArray()); + sql.append("GROUP BY u.user_id, rec.id "); + sql.append("ORDER BY CASE WHEN rec.submit_time IS NOT NULL THEN 0 ELSE 1 END, rec.submit_time DESC, u.user_id "); // 查询总数 StringBuilder countSql = new StringBuilder(); - countSql.append("SELECT COUNT(*) FROM hq_sat_record r "); - countSql.append("LEFT JOIN sys_user u ON r.user_id = u.user_id "); - countSql.append("WHERE r.survey_id = ? AND r.deleted = 0 "); + countSql.append("SELECT COUNT(DISTINCT u.user_id) "); + countSql.append("FROM sys_user u "); + countSql.append("LEFT JOIN sys_user_role ur ON u.user_id = ur.user_id "); + countSql.append("LEFT JOIN sys_role r ON ur.role_id = r.role_id "); + countSql.append("LEFT JOIN hq_sat_record rec ON u.user_id = rec.user_id AND rec.survey_id = ? AND rec.deleted = 0 "); + countSql.append("WHERE u.del_flag = '0' "); + + // 根据目标群体过滤用户 + countSql.append("AND ("); + if ("1".equals(targetGroup)) { + // 学生群体 + countSql.append("r.role_key = 'student'"); + } else if ("2".equals(targetGroup)) { + // 教职工群体 + countSql.append("r.role_key = 'teacher'"); + } else if ("3".equals(targetGroup)) { + // 全体群体(学生和教职工) + countSql.append("(r.role_key = 'student' OR r.role_key = 'teacher')"); + } + countSql.append(") "); java.util.List countParams = new java.util.ArrayList<>(); countParams.add(surveyId); if (status != null && !status.isEmpty()) { if ("1".equals(status)) { - countSql.append("AND r.submit_time IS NOT NULL "); + countSql.append("AND rec.submit_time IS NOT NULL "); } else if ("0".equals(status)) { - countSql.append("AND r.submit_time IS NULL "); + countSql.append("AND (rec.submit_time IS NULL OR rec.id IS NULL) "); } } @@ -1100,13 +1140,42 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService { Integer total = jdbcTemplate.queryForObject(countSql.toString(), Integer.class, countParams.toArray()); - // 处理记录数据,添加状态和持续时间字段 - for (java.util.Map record : records) { - // 设置状态 - record.put("status", record.get("submit_time") != null ? "1" : "0"); + // 分页查询 + int offset = (pageNum - 1) * pageSize; + sql.append("LIMIT ? OFFSET ?"); + params.add(pageSize); + params.add(offset); - // 计算持续时间(暂时使用随机值,后续可以实现真实计算) - record.put("duration", 300 + (int)(Math.random() * 600)); // 5-15分钟 + java.util.List> records = jdbcTemplate.queryForList(sql.toString(), params.toArray()); + + // 查询问卷题目信息,用于前端显示 + String questionsSql = "SELECT q.id, q.question_title, q.question_type FROM hq_sat_question q " + + "JOIN hq_sat_survey_question sq ON q.id = sq.question_id " + + "WHERE sq.survey_id = ? AND q.deleted = 0 AND sq.deleted = 0 " + + "ORDER BY sq.sort_order"; + + List> questionsList = jdbcTemplate.queryForList(questionsSql, surveyId); + Map> questionsMap = new HashMap<>(); + + // 构建题目ID到题目信息的映射 + for (Map question : questionsList) { + Long questionId = Long.valueOf(question.get("id").toString()); + questionsMap.put(questionId, question); + } + + // 处理记录数据,添加状态、持续时间和答题详情字段 + for (java.util.Map record : records) { + // 设置状态 - 已提交或未提交 + boolean hasSubmitted = record.get("submit_time") != null; + record.put("status", hasSubmitted ? "1" : "0"); + + // 设置得分 - 已提交显示实际得分,未提交显示0 + if (!hasSubmitted) { + record.put("total_score", 0); + } + + // 计算持续时间(已提交的才计算,未提交显示0) + record.put("duration", hasSubmitted ? (300 + (int)(Math.random() * 600)) : 0); // 已提交的记录显示5-15分钟,未提交的显示0 // 格式化时间 if (record.get("create_time") != null) { @@ -1115,18 +1184,114 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService { if (record.get("submit_time") != null) { record.put("endTime", record.get("submit_time").toString()); } + + // 获取答题详情(仅对已提交的记录) + if (hasSubmitted && record.get("id") != null) { + Long recordId = Long.valueOf(record.get("id").toString()); + List> answerDetails = getAnswersFromSatAnswerTable(recordId); + + // 将答案与题目信息关联,组织成前端需要的格式 + List> formattedAnswers = new ArrayList<>(); + Map>> questionAnswers = new HashMap<>(); + + // 先按题目ID分组答案 + for (Map answer : answerDetails) { + Long questionId = Long.valueOf(answer.get("questionId").toString()); + + if (!questionAnswers.containsKey(questionId)) { + questionAnswers.put(questionId, new ArrayList<>()); + } + + questionAnswers.get(questionId).add(answer); + } + + // 为每个题目创建一个答案记录 + for (Long questionId : questionAnswers.keySet()) { + List> answers = questionAnswers.get(questionId); + + // 获取题目基本信息 + Map questionInfo = questionsMap.get(questionId); + if (questionInfo == null) continue; + + Map formattedAnswer = new HashMap<>(); + formattedAnswer.put("id", questionId); + formattedAnswer.put("questionId", questionId); + formattedAnswer.put("questionTitle", questionInfo.get("question_title")); + formattedAnswer.put("questionType", String.valueOf(questionInfo.get("question_type"))); + + // 设置分数(汇总所有选项的分数) + double totalScore = 0; + for (Map answer : answers) { + String scoreStr = (String)answer.get("answerScore"); + if (scoreStr != null) { + try { + totalScore += Double.parseDouble(scoreStr); + } catch (NumberFormatException e) { + log.warn("解析答题分数失败: {}", scoreStr); + } + } + } + formattedAnswer.put("score", totalScore); + + String questionType = (String)formattedAnswer.get("questionType"); + + // 处理问答题 + if ("3".equals(questionType)) { + // 取第一个答案的值作为问答题答案 + if (!answers.isEmpty()) { + formattedAnswer.put("answerValue", answers.get(0).get("remark")); + } + } + // 处理单选题和多选题 + else if ("1".equals(questionType) || "2".equals(questionType)) { + List> options = new ArrayList<>(); + + // 获取所有选中的选项 + for (Map answer : answers) { + // 只处理有选项ID的答案 + if (answer.get("optionId") != null) { + Map option = new HashMap<>(); + log.info("answer-zlwu12: {}", answer); + option.put("optionId", answer.get("optionId")); + option.put("optionValue", answer.get("optionValue")); + option.put("optionText", answer.get("optionText")); + // option.put("optionScore", answer.get("answerScore")); + option.put("isSelected", true); + options.add(option); + } + } + + formattedAnswer.put("options", options); + + // 设置answerValue为所有选中选项值的拼接 + StringBuilder answerValue = new StringBuilder(); + for (Map option : options) { + if (answerValue.length() > 0) { + answerValue.append(","); + } + answerValue.append(option.get("optionValue")); + } + formattedAnswer.put("answerValue", answerValue.toString()); + } + + formattedAnswers.add(formattedAnswer); + } + + // 将答题详情添加到记录中 + record.put("answers", formattedAnswers); + } } java.util.Map result = new java.util.HashMap<>(); result.put("rows", records); result.put("total", total != null ? total : 0); - log.info("获取答题记录完成,总数: {}, 当前页: {}", total, records.size()); + log.info("获取符合问卷面向群体的所有用户信息完成,总数: {}, 当前页: {}", total, records.size()); return result; } catch (Exception e) { - log.error("获取答题记录失败,问卷ID: {}, 错误: {}", surveyId, e.getMessage(), e); - throw new ServiceException("获取答题记录失败: " + e.getMessage()); + log.error("获取问卷用户记录失败,问卷ID: {}, 错误: {}", surveyId, e.getMessage(), e); + throw new ServiceException("获取问卷用户记录失败: " + e.getMessage()); } } @@ -1139,7 +1304,6 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService { @Override public java.util.Map getAnswerRecordDetail(Long recordId) { log.info("获取答题记录详情,记录ID: {}", recordId); - try { // 查询答题记录基本信息 String recordSql = "SELECT r.*, u.nick_name as userName, d.dept_name as deptName " + @@ -1147,57 +1311,107 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService { "LEFT JOIN sys_user u ON r.user_id = u.user_id " + "LEFT JOIN sys_dept d ON u.dept_id = d.dept_id " + "WHERE r.id = ? AND r.deleted = 0"; - java.util.List> recordList = jdbcTemplate.queryForList(recordSql, recordId); if (recordList.isEmpty()) { throw new ServiceException("答题记录不存在", 404); } - java.util.Map record = recordList.get(0); - - // 查询答题详情 + Long surveyId = record.get("survey_id") != null ? Long.valueOf(record.get("survey_id").toString()) : null; + // 1. 获取问卷题目顺序 + List questionVos = surveyQuestionService.getSurveyQuestionsWithDetail(surveyId); + // 题目顺序Map + Map questionOrderMap = new HashMap<>(); + for (int i = 0; i < questionVos.size(); i++) { + questionOrderMap.put(questionVos.get(i).getQuestionId(), i); + } + // 2. 查询所有选项顺序 + Map> questionOptionsMap = new HashMap<>(); + for (HqSatSurveyQuestionVo qvo : questionVos) { + HqSatOptionBo bo = new HqSatOptionBo(); + bo.setQuestionId(qvo.getQuestionId()); + bo.setDeleted("0"); + List options = optionService.queryList(bo); + options.sort((a, b) -> { + if (a.getOptionOrder() == null && b.getOptionOrder() == null) return 0; + if (a.getOptionOrder() == null) return 1; + if (b.getOptionOrder() == null) return -1; + return a.getOptionOrder().compareTo(b.getOptionOrder()); + }); + questionOptionsMap.put(qvo.getQuestionId(), options); + } + // 3. 查询答题详情 String answerSql = "SELECT a.question_id, a.question_type, a.answer_value, " + - "a.option_id, a.option_value, a.option_text, a.answer_score, " + + "a.option_id, a.option_value, a.option_text, a.answer_score, a.remark, " + "q.question_title " + "FROM hq_sat_answer a " + "LEFT JOIN hq_sat_question q ON a.question_id = q.id " + - "WHERE a.record_id = ? AND a.deleted = 0 " + - "ORDER BY a.question_id, a.option_id"; - + "WHERE a.record_id = ? AND a.deleted = 0 "; java.util.List> answers = jdbcTemplate.queryForList(answerSql, recordId); - - // 按题目分组答案 - java.util.Map> questionAnswerMap = new java.util.HashMap<>(); - for (java.util.Map answer : answers) { - Long questionId = Long.valueOf(answer.get("question_id").toString()); - - if (!questionAnswerMap.containsKey(questionId)) { - java.util.Map questionAnswer = new java.util.HashMap<>(); - questionAnswer.put("id", questionId); - questionAnswer.put("questionId", questionId); - questionAnswer.put("questionTitle", answer.get("question_title")); - questionAnswer.put("questionType", answer.get("question_type")); - questionAnswer.put("answerValue", answer.get("answer_value")); - questionAnswer.put("score", answer.get("answer_score")); - questionAnswer.put("options", new java.util.ArrayList>()); - questionAnswerMap.put(questionId, questionAnswer); + // 按题目分组 + Map>> answerGroup = new HashMap<>(); + for (Map answer : answers) { + Long qid = Long.valueOf(answer.get("question_id").toString()); + answerGroup.computeIfAbsent(qid, k -> new ArrayList<>()).add(answer); + } + // 组装answers,按题目顺序 + List> resultAnswers = new ArrayList<>(); + for (HqSatSurveyQuestionVo qvo : questionVos) { + Long qid = qvo.getQuestionId(); + List> answerList = answerGroup.getOrDefault(qid, new ArrayList<>()); + Map answerObj = new HashMap<>(); + answerObj.put("id", qid); + answerObj.put("questionId", qid); + answerObj.put("questionTitle", qvo.getQuestionTitle()); + answerObj.put("questionType", qvo.getQuestionType()); + double totalScore = 0; + for (Map a : answerList) { + Object scoreStr = a.get("answer_score"); + if (scoreStr != null) { + try { totalScore += Double.parseDouble(scoreStr.toString()); } catch (Exception ignore) {} + } } - - // 如果是选择题,添加选项信息 - if (answer.get("option_id") != null) { - java.util.Map option = new java.util.HashMap<>(); - option.put("optionId", answer.get("option_id")); - option.put("optionValue", answer.get("option_value")); - option.put("optionText", answer.get("option_text")); - option.put("isSelected", true); // 查询出来的都是已选择的选项 - - @SuppressWarnings("unchecked") - java.util.List> options = - (java.util.List>) questionAnswerMap.get(questionId).get("options"); - options.add(option); + answerObj.put("score", totalScore); + // 问答题 + if ("3".equals(qvo.getQuestionType())) { + String remark = answerList.isEmpty() ? "" : (answerList.get(0).get("remark") != null ? answerList.get(0).get("remark").toString() : ""); + answerObj.put("answerValue", remark); + answerObj.put("options", new ArrayList<>()); + } else if ("1".equals(qvo.getQuestionType()) || "2".equals(qvo.getQuestionType())) { + // 选择题,组装选项,按optionOrder排序 + List allOptions = questionOptionsMap.getOrDefault(qid, new ArrayList<>()); + List> options = new ArrayList<>(); + for (HqSatOptionVo opt : allOptions) { + Map optMap = new HashMap<>(); + optMap.put("optionId", opt.getId()); + optMap.put("optionValue", opt.getOptionContent()); + optMap.put("optionText", opt.getOptionContent()); + optMap.put("optionScore", opt.getOptionScore()); + optMap.put("isSelected", false); + // 检查是否被选中 + for (Map a : answerList) { + if (a.get("option_id") != null && opt.getId().toString().equals(a.get("option_id").toString())) { + optMap.put("isSelected", true); + break; + } + } + options.add(optMap); + } + answerObj.put("options", options); + // answerValue为所有选中optionValue的拼接 + StringBuilder answerValue = new StringBuilder(); + for (Map opt : options) { + if (Boolean.TRUE.equals(opt.get("isSelected"))) { + if (answerValue.length() > 0) answerValue.append(","); + answerValue.append(opt.get("optionValue")); + } + } + answerObj.put("answerValue", answerValue.toString()); + } else { + answerObj.put("options", new ArrayList<>()); + answerObj.put("answerValue", ""); } + resultAnswers.add(answerObj); } - // 构建返回结果 java.util.Map result = new java.util.HashMap<>(); result.put("id", record.get("id")); @@ -1210,11 +1424,9 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService { result.put("duration", 300 + (int)(Math.random() * 600)); // 暂时使用随机值 result.put("totalScore", record.get("total_score")); result.put("status", record.get("submit_time") != null ? "1" : "0"); - result.put("answers", new java.util.ArrayList<>(questionAnswerMap.values())); - - log.info("获取答题记录详情完成,记录ID: {}, 答案数量: {}", recordId, questionAnswerMap.size()); + result.put("answers", resultAnswers); + log.info("获取答题记录详情完成,记录ID: {}, 答案数量: {}", recordId, resultAnswers.size()); return result; - } catch (Exception e) { log.error("获取答题记录详情失败,记录ID: {}, 错误: {}", recordId, e.getMessage(), e); throw new ServiceException("获取答题记录详情失败: " + e.getMessage()); diff --git a/docs/开发文档/API/后勤满意度调查API.md b/docs/开发文档/API/后勤满意度调查API.md index 4a97f550..6f1de537 100644 --- a/docs/开发文档/API/后勤满意度调查API.md +++ b/docs/开发文档/API/后勤满意度调查API.md @@ -450,6 +450,8 @@ await Promise.all(promises); | 1.0.1 | 2025-07-17 | 优化题目选项获取接口,增强前端题目选项显示功能 | Cursor | | 1.0.2 | 2025-07-17 | 修复选项排序值保存后实时更新功能,优化用户体验 | wuzilong | | 1.0.3 | 2025-07-17 | 添加选项保存防重复提交机制,解决幂等性控制冲突 | wuzilong | +| 1.0.4 | 2025-07-18 | 优化问答题文本回答展示功能,提高Vue渲染稳定性 | wuzilong | +| 1.0.5 | 2025-07-18 | 增强问卷答题记录接口功能,返回答题详情数据 | wuzilong | ## 附录 @@ -465,7 +467,207 @@ await Promise.all(promises); - 总分(total_score) = 所有题目的得分之和 - 平均分(avg_score) = 总分 ÷ 有分值的题目数量(不包括问答题) +### 问答题文本回答数据结构 + +**统计分析接口中问答题文本回答数据结构**: + +问卷统计分析接口(`/hq/satSurvey/{id}/statistics`)返回的问答题文本回答数据结构如下: + +```json +"textAnswers": [ + { + "id": 393, + "answer_text": "用户回答内容示例", + "text_length": 12, + "answer_time": "2025-07-18 10:40:35" + } +] +``` + +**字段说明**: + +| 字段名 | 类型 | 描述 | +|-------|-----|------| +| id | Integer | 回答ID,唯一标识 | +| answer_text | String | 回答文本内容 | +| text_length | Integer | 文本长度(字符数) | +| answer_time | String | 回答时间,格式:yyyy-MM-dd HH:mm:ss | + +**前端使用注意事项**: +- 在Vue循环渲染textAnswers数组时,应使用answer.id作为key值,而非索引,确保Vue的虚拟DOM正确更新 +- 示例:`
` +- 2025-07-18修复:解决了文本回答因key不唯一导致的渲染问题,详见`docs/变更管理/code-adjust-record-2025-07-18-wuzilong.md` + +#### 2025-07-18 接口返回顺序与问答题答案优化 + +- `/hq/satSurvey/record/{recordId}` 接口返回参数调整: + 1. `answers` 数组顺序严格按照问卷题目顺序返回。 + 2. `answers.options` 选项数组顺序严格按照题目原始选项顺序返回。 + 3. 问答题(`questionType=3`)的 `answerValue` 字段取自 `remark` 字段,真实还原用户填写内容。 + +> 以上调整保证了前端展示与问卷设计一致,提升了数据准确性。 + +### 获取问卷的所有答题记录列表 + +**请求方式**:GET + +**请求路径**:`/hq/satSurvey/{surveyId}/records` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 描述 | +|-------|-----|------|------| +| surveyId | Long | 是 | 问卷ID,路径参数 | +| status | String | 否 | 答题状态(1-已提交,0-未提交) | +| userName | String | 否 | 用户姓名,模糊查询 | +| dateRange | String[] | 否 | 日期范围 | +| pageNum | Integer | 否 | 页码,默认1 | +| pageSize | Integer | 否 | 每页条数,默认10 | + +**请求示例**: +``` +GET /hq/satSurvey/1945836064483778562/records?pageNum=1&pageSize=10&surveyId=1945836064483778562 +``` + +**响应示例**: +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + "total": 5, + "rows": [ + { + "user_id": 2008, + "userName": "智控学院郑十", + "deptName": "智能控制学院", + "id": "1946035501017587714", + "survey_id": "1945836064483778562", + "submit_time": "2025-07-18 10:33:14", + "total_score": "24.00", + "create_time": "2025-07-18 10:33:14", + "status": "1", + "duration": 313, + "startTime": "2025-07-18T10:33:14", + "endTime": "2025-07-18T10:33:14", + "answers": [ + { + "id": 1945836064483778563, + "questionId": 1945836064483778563, + "questionTitle": "您对后勤服务总体满意度如何?", + "questionType": "1", + "score": 4.0, + "answerValue": "B", + "options": [ + { + "optionId": 1945836064483778569, + "optionValue": "B", + "optionText": "满意", + "isSelected": true + } + ] + }, + { + "id": 1945836064483778564, + "questionId": 1945836064483778564, + "questionTitle": "您认为校园环境有哪些需要改善的地方?", + "questionType": "2", + "score": 11.0, + "answerValue": "A,C,D", + "options": [ + { + "optionId": 1945836064483778570, + "optionValue": "A", + "optionText": "绿化维护", + "isSelected": true + }, + { + "optionId": 1945836064483778572, + "optionValue": "C", + "optionText": "卫生状况", + "isSelected": true + }, + { + "optionId": 1945836064483778573, + "optionValue": "D", + "optionText": "基础设施", + "isSelected": true + } + ] + }, + { + "id": 1945836064483778565, + "questionId": 1945836064483778565, + "questionTitle": "您对校园后勤服务的建议", + "questionType": "3", + "score": 0, + "answerValue": "希望增加自习室的开放时间,目前晚上关门太早" + } + ] + }, + { + "user_id": 4, + "userName": "仅本人 密码666666", + "deptName": "行政单位", + "id": null, + "survey_id": null, + "submit_time": null, + "total_score": 0, + "create_time": null, + "status": "0", + "duration": 0 + } + ] + } +} +``` + +**特殊说明**: +1. **功能强化(2025-07-18)**:接口现在返回已提交记录的答题详情(`answers`字段),无需额外请求 +2. 每条记录(已提交状态)包含完整的答题详情数据,包括: + - 题目信息:`questionId`、`questionTitle`、`questionType` + - 答案内容:`score`、`answerValue` + - 选项数据(单选/多选题):`options`数组,包含所有选中的选项 +3. 问题类型(`questionType`): + - `1`:单选题 + - `2`:多选题 + - `3`:问答题 +4. 未提交的记录(`status="0"`)不包含答题详情 + +**最佳实践**: +- 前端可直接使用返回的`answers`数据渲染答题详情,无需额外请求 +- 单选题和多选题显示选项,问答题显示文本内容 + ### 相关文件 - 数据库表结构:`docs/设计文档/database/hzdyjs_xg.sql` - 变更记录:`docs/变更管理/code-adjust-record-2025-07-01-cursor.md` +- 文本回答展示修复:`docs/变更管理/code-adjust-record-2025-07-18-wuzilong.md` +- 答题详情功能优化:`docs/变更管理/code-adjust-record-2025-07-18-wuzilong.md` + +## FAQ 常见问题解答 + +### 1. 问卷答题记录查询报错 "Unknown column 'u.deleted' in 'where clause'" + +**问题描述**: +获取问卷答题记录列表时报错:`java.sql.SQLSyntaxErrorException: Unknown column 'u.deleted' in 'where clause'` + +**原因分析**: +在SQL查询中使用了 `u.deleted = 0` 条件,但实际上 `sys_user` 表中逻辑删除字段名是 `del_flag` 而非 `deleted`。这是系统表与业务表之间的字段命名差异导致的。 + +**解决方案**: +修改SQL查询,将 `WHERE u.deleted = 0` 改为 `WHERE u.del_flag = '0'`(注意类型也从数字改为了字符串)。 + +**相关代码**: +```java +// 修改前 +sql.append("WHERE u.deleted = 0 "); + +// 修改后 +sql.append("WHERE u.del_flag = '0' "); +``` + +**注意事项**: +- 系统表(如sys_user, sys_role等)使用 `del_flag` 作为逻辑删除字段,类型为char(1) +- 业务表(如hq_sat_survey等)使用 `deleted` 作为逻辑删除字段,类型为tinyint(1) +- 在跨表关联查询时,务必注意这一差异,避免类似错误 \ No newline at end of file diff --git a/web/src/api/hq/satSurvey/types.ts b/web/src/api/hq/satSurvey/types.ts index dcd68896..9062bd6d 100644 --- a/web/src/api/hq/satSurvey/types.ts +++ b/web/src/api/hq/satSurvey/types.ts @@ -526,6 +526,7 @@ export interface SatAnswerVO { optionValue: string; optionText: string; isSelected: boolean; + optionScore: string | number; }>; } diff --git a/web/src/assets/styles/index.scss b/web/src/assets/styles/index.scss index c204fc23..511e2055 100644 --- a/web/src/assets/styles/index.scss +++ b/web/src/assets/styles/index.scss @@ -51,7 +51,11 @@ html.dark svg { #app .sidebar-container .sub-menu-title-noDropdown, #app .sidebar-container .el-sub-menu__title { - margin: 0px 8px 0 0 !important; + margin: 0px 0px 0 0 !important; +} + +#app .sidebar-container .nest-menu .el-sub-menu > .el-sub-menu__title, #app .sidebar-container .el-sub-menu .el-menu-item{ + margin: 0px 0px 0 0 !important; } *, diff --git a/web/src/assets/styles/sidebar.scss b/web/src/assets/styles/sidebar.scss index 542ff210..ec84f465 100644 --- a/web/src/assets/styles/sidebar.scss +++ b/web/src/assets/styles/sidebar.scss @@ -235,7 +235,7 @@ // 嵌套菜单项样式 .nest-menu .el-sub-menu > .el-sub-menu__title, .el-sub-menu .el-menu-item { - max-width: calc(100% - 16px) !important; + max-width: calc(100%) !important; width: auto !important; border-radius: var(--border-radius-xs) !important; margin: 1px 8px !important;