优化问卷回答界面及功能
This commit is contained in:
1 parent
1ab9f9c458
commit
ed5eb9ca52
13 files changed
+6122
-86
No files matched your search
+156
-4
@@ -26,7 +26,8 @@ docs/
|
||||
│ │ └── README.md # 数据库设计说明
|
||||
│ └── [模块设计]/ # 其他设计文档
|
||||
├── development/ # 开发文档
|
||||
│ └── README.md # 开发指南
|
||||
│ ├── README.md # 开发指南
|
||||
│ └── API.md # API接口文档(完整的后端接口说明)
|
||||
├── testing/ # 测试文档
|
||||
│ └── README.md # 测试计划和用例
|
||||
├── deployment/ # 部署文档
|
||||
@@ -86,6 +87,20 @@ docs/
|
||||
### ⚠️ 强制要求
|
||||
**所有通过Cursor AI进行的代码调整都必须在 `docs/code-adjust-record.md` 中留下记录,这是项目管理的强制性要求。**
|
||||
|
||||
### 🔴 特别强调:API接口变更的强制性文档更新规则
|
||||
|
||||
**⚠️ 超级重要:任何API接口的新增、修改、删除操作都必须立即同步更新 `docs/development/API.md` 文档!**
|
||||
|
||||
#### API变更必须执行的步骤:
|
||||
1. **代码修改完成** → 2. **立即更新API.md文档** → 3. **记录代码调整日志** → 4. **验证文档准确性**
|
||||
|
||||
#### API接口变更涉及的文档更新:
|
||||
- ✅ **新增API接口**:在API.md中添加完整的接口文档(地址、参数、响应、权限等)
|
||||
- ✅ **修改API接口**:更新API.md中对应接口的参数、响应格式、权限要求
|
||||
- ✅ **删除API接口**:在API.md中标记接口为已废弃,说明替代方案
|
||||
- ✅ **修改响应格式**:更新API.md中的响应示例和字段说明
|
||||
- ✅ **修改权限要求**:更新API.md中的权限要求说明
|
||||
|
||||
## 3. 文档同步更新规则
|
||||
|
||||
### 📋 核心原则:代码与文档同步更新
|
||||
@@ -93,7 +108,7 @@ docs/
|
||||
|
||||
### 强制更新规则
|
||||
每次代码调整必须同步更新相关文档:
|
||||
- **API文档**: 接口变更时更新相关文档
|
||||
- **API文档**: 接口变更时更新 `docs/development/API.md` 中的相关接口信息
|
||||
- **数据库文档**: 表结构变更时更新 `docs/design/database/` 下的所有相关文档
|
||||
- **配置文档**: 配置项变更时更新 `docs/middleware/` 下的配置说明
|
||||
- **部署文档**: 部署流程变更时更新 `docs/deployment/` 下的部署手册
|
||||
@@ -110,7 +125,11 @@ docs/
|
||||
- [ ] **UI设计文档**: 界面样式重大变更时,更新设计规范文档
|
||||
|
||||
#### 后端代码调整时需更新的文档:
|
||||
- [ ] **接口文档**: API接口变更时,更新接口文档和示例
|
||||
- [ ] **API接口文档**: API接口变更时,必须更新 `docs/development/API.md` 中的相关接口信息,包括:
|
||||
- 新增接口:添加完整的接口说明、参数、响应示例
|
||||
- 修改接口:更新参数说明、响应格式、错误码
|
||||
- 删除接口:在文档中标记为已废弃,并说明替代方案
|
||||
- 权限变更:更新权限要求说明
|
||||
- [ ] **数据库文档**: 表结构变更时,更新数据库设计文档
|
||||
- [ ] **业务逻辑文档**: 业务规则变更时,更新业务流程文档
|
||||
- [ ] **配置文档**: 系统配置变更时,更新配置说明文档
|
||||
@@ -121,13 +140,119 @@ docs/
|
||||
- [ ] **用户手册**: 用户操作流程变更时,更新操作指南
|
||||
|
||||
### 🔄 文档更新工作流程
|
||||
1. **代码调整完成** → 2. **记录代码调整日志** → 3. **识别影响的文档** → 4. **更新相应文档** → 5. **验证文档完整性**
|
||||
|
||||
#### 标准流程:
|
||||
1. **代码调整完成** → 2. **立即更新相关文档** → 3. **记录代码调整日志** → 4. **验证文档完整性**
|
||||
|
||||
#### 🔴 API接口变更的特殊流程(强制性):
|
||||
1. **API代码开发完成**
|
||||
2. **立即更新 `docs/development/API.md`** (不得延后)
|
||||
3. **记录代码调整日志,标注API文档已更新**
|
||||
4. **验证API文档与实际接口的一致性**
|
||||
5. **提交代码和文档变更**
|
||||
|
||||
#### ⚠️ 重要原则:
|
||||
- **API变更 = 必须更新API.md文档**
|
||||
- **文档更新 = 代码开发的一部分,不是可选项**
|
||||
- **未更新文档 = 开发任务未完成**
|
||||
|
||||
### 版本管理
|
||||
- **文档版本**: 与代码版本保持同步
|
||||
- **变更记录**: 每次变更都要记录变更内容和原因
|
||||
- **向后兼容**: 接口变更需要考虑向后兼容性
|
||||
|
||||
## 4. API文档管理规范 (docs/development/API.md)
|
||||
|
||||
### 📄 API文档维护原则
|
||||
**⚠️ 重要规则:任何后端API接口的新增、修改、删除都必须同步更新 `docs/development/API.md` 文档。**
|
||||
|
||||
### API文档更新时机
|
||||
- **新增Controller**: 添加新的Controller时,必须在API.md中添加对应的接口说明
|
||||
- **新增接口方法**: 在现有Controller中添加新方法时,更新对应模块的接口列表
|
||||
- **修改接口参数**: 接口请求或响应参数变更时,更新参数说明表格
|
||||
- **修改权限要求**: 接口权限变更时,更新权限要求说明
|
||||
- **修改响应格式**: 响应数据结构变更时,更新响应示例
|
||||
- **接口废弃**: 接口不再使用时,在文档中标记为已废弃并说明替代方案
|
||||
|
||||
### API文档标准格式
|
||||
每个接口都必须包含以下信息:
|
||||
```markdown
|
||||
#### 接口标题
|
||||
|
||||
**接口地址**: `HTTP方法 /接口路径`
|
||||
|
||||
**接口描述**: 接口功能的详细描述
|
||||
|
||||
**权限要求**: 权限代码或"需要登录"
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
### API文档分类规范
|
||||
- **按业务模块分类**: 问卷管理、答题管理、题目管理等
|
||||
- **按功能分组**: 基础CRUD操作、业务逻辑操作、状态查询等
|
||||
- **统一命名规范**: 接口名称要与Controller方法名保持一致
|
||||
|
||||
### API文档版本控制
|
||||
- **版本记录**: 在API.md底部维护版本更新记录
|
||||
- **变更说明**: 每次更新都要记录具体的变更内容
|
||||
- **兼容性说明**: 重大变更需要说明向后兼容性影响
|
||||
|
||||
### ⚠️ 强制执行规则
|
||||
**所有通过Cursor AI进行的后端API开发都必须同时更新API文档,这是代码审查的强制性检查项。**
|
||||
|
||||
### 🚨 API文档更新检查清单
|
||||
|
||||
#### 当进行以下操作时,必须更新 `docs/development/API.md`:
|
||||
|
||||
**Controller层变更**:
|
||||
- [ ] 新增Controller类 → 在API.md中添加新的模块章节
|
||||
- [ ] 新增@RequestMapping方法 → 添加完整的接口文档
|
||||
- [ ] 修改@RequestMapping路径 → 更新接口地址
|
||||
- [ ] 修改HTTP方法(GET/POST/PUT/DELETE) → 更新接口方法
|
||||
- [ ] 删除接口方法 → 标记为已废弃并说明替代方案
|
||||
|
||||
**参数和响应变更**:
|
||||
- [ ] 新增请求参数 → 更新请求参数表格
|
||||
- [ ] 修改参数类型或必填属性 → 更新参数说明
|
||||
- [ ] 修改响应数据结构 → 更新响应示例
|
||||
- [ ] 新增错误码 → 更新错误码说明表
|
||||
|
||||
**权限和安全变更**:
|
||||
- [ ] 新增@SaCheckPermission注解 → 更新权限要求
|
||||
- [ ] 修改权限代码 → 更新权限说明
|
||||
- [ ] 修改接口访问级别 → 更新权限要求说明
|
||||
|
||||
#### 强制性文档更新格式要求:
|
||||
```markdown
|
||||
#### 接口名称
|
||||
**接口地址**: `HTTP方法 /完整路径`
|
||||
**接口描述**: 清晰描述接口功能
|
||||
**权限要求**: 具体权限代码或"需要登录"
|
||||
**请求参数**: 完整参数表格
|
||||
**响应示例**: 真实的JSON响应示例
|
||||
**错误码**: 可能的错误情况
|
||||
**更新记录**: 接口变更历史
|
||||
```
|
||||
|
||||
### 🔴 违规检查机制
|
||||
- **代码审查时检查**:每次PR必须检查API文档是否同步更新
|
||||
- **自动化提醒**:接口变更后立即更新文档,不得延误
|
||||
- **文档版本控制**:API文档版本必须与代码版本保持一致
|
||||
|
||||
### ⚠️ 重要提醒
|
||||
**不更新API文档的代码变更将被视为不完整的开发任务,必须补齐文档后才能认为任务完成!**
|
||||
|
||||
---
|
||||
|
||||
# 第二部分:Web前端开发规范 (web/)
|
||||
@@ -416,6 +541,33 @@ backend/
|
||||
@SaCheckPermission("system:user:list")
|
||||
```
|
||||
|
||||
### 🔴 API开发强制性工作流程
|
||||
|
||||
#### 步骤1:编写后端代码
|
||||
- 创建Controller、Service、Mapper等
|
||||
- 实现业务逻辑和数据处理
|
||||
- 添加权限控制和参数验证
|
||||
|
||||
#### 步骤2:立即更新API文档(强制性)
|
||||
**⚠️ 代码写完后立即执行,不得延后!**
|
||||
- 打开 `docs/development/API.md` 文件
|
||||
- 按照标准格式添加或更新接口文档
|
||||
- 包含完整的接口信息(地址、参数、响应、权限等)
|
||||
- 添加真实的请求和响应示例
|
||||
|
||||
#### 步骤3:记录代码调整日志
|
||||
- 在 `docs/code-adjust-record.md` 中记录本次变更
|
||||
- 特别说明API接口的变更内容
|
||||
- 标注文档已同步更新
|
||||
|
||||
#### 步骤4:验证和测试
|
||||
- 测试API接口功能是否正常
|
||||
- 验证API文档的准确性
|
||||
- 确保文档示例与实际响应一致
|
||||
|
||||
#### 🚨 违规后果
|
||||
**如果发现API变更未同步更新文档,该代码变更将被视为不完整,必须立即补齐文档。**
|
||||
|
||||
## 4. 标准代码模板
|
||||
|
||||
### Controller模板
|
||||
|
||||
+47
-1
@@ -19,8 +19,10 @@ import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.zhxy.domain.vo.HqSatSurveyVo;
|
||||
import org.dromara.zhxy.domain.bo.HqSatSurveyBo;
|
||||
import org.dromara.zhxy.domain.bo.HqSatAnswerSubmitBo;
|
||||
import org.dromara.zhxy.service.IHqSatSurveyService;
|
||||
import org.dromara.zhxy.domain.vo.HqSatSurveyQuestionVo;
|
||||
import org.dromara.zhxy.domain.vo.HqSatRecordVo;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
|
||||
/**
|
||||
@@ -130,7 +132,7 @@ public class HqSatSurveyController extends BaseController {
|
||||
*
|
||||
* @param surveyId 问卷ID
|
||||
*/
|
||||
@SaCheckPermission("hq:satSurvey:question")
|
||||
// @SaCheckPermission("hq:satSurvey:question")
|
||||
@GetMapping("/{surveyId}/questions")
|
||||
public R<List<HqSatSurveyQuestionVo>> getSurveyQuestions(
|
||||
@NotNull(message = "问卷ID不能为空") @PathVariable Long surveyId) {
|
||||
@@ -177,4 +179,48 @@ public class HqSatSurveyController extends BaseController {
|
||||
public R<Void> unpublishSurvey(@NotNull(message = "问卷ID不能为空") @PathVariable Long id) {
|
||||
return toAjax(hqSatSurveyService.unpublishSurvey(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交问卷答案
|
||||
*
|
||||
* @param submitBo 答题提交数据
|
||||
*/
|
||||
@Log(title = "提交问卷答案", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/answer/submit")
|
||||
public R<HqSatRecordVo> submitSurveyAnswer(@Validated @RequestBody HqSatAnswerSubmitBo submitBo) {
|
||||
return R.ok(hqSatSurveyService.submitSurveyAnswer(submitBo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存问卷答案草稿
|
||||
*
|
||||
* @param submitBo 答题提交数据
|
||||
*/
|
||||
@Log(title = "保存问卷答案草稿", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/answer/draft")
|
||||
public R<HqSatRecordVo> saveSurveyDraft(@Validated @RequestBody HqSatAnswerSubmitBo submitBo) {
|
||||
return R.ok(hqSatSurveyService.saveSurveyDraft(submitBo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否已答过问卷
|
||||
*
|
||||
* @param surveyId 问卷ID
|
||||
*/
|
||||
@GetMapping("/{surveyId}/answered")
|
||||
public R<Boolean> checkUserAnswered(@NotNull(message = "问卷ID不能为空") @PathVariable Long surveyId) {
|
||||
return R.ok(hqSatSurveyService.checkUserAnswered(surveyId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户问卷答题记录
|
||||
*
|
||||
* @param surveyId 问卷ID
|
||||
*/
|
||||
@GetMapping("/{surveyId}/answer")
|
||||
public R<HqSatRecordVo> getUserSurveyAnswer(@NotNull(message = "问卷ID不能为空") @PathVariable Long surveyId) {
|
||||
return R.ok(hqSatSurveyService.getUserSurveyAnswer(surveyId));
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package org.dromara.zhxy.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 答题提交BO
|
||||
*
|
||||
* @author wuzilong
|
||||
* @date 2025-01-26
|
||||
*/
|
||||
@Data
|
||||
public class HqSatAnswerSubmitBo {
|
||||
|
||||
/**
|
||||
* 问卷ID
|
||||
*/
|
||||
@NotNull(message = "问卷ID不能为空")
|
||||
private Long surveyId;
|
||||
|
||||
/**
|
||||
* 答题开始时间
|
||||
*/
|
||||
@NotNull(message = "答题开始时间不能为空")
|
||||
private String startTime;
|
||||
|
||||
/**
|
||||
* 答题结束时间
|
||||
*/
|
||||
@NotNull(message = "答题结束时间不能为空")
|
||||
private String endTime;
|
||||
|
||||
/**
|
||||
* 是否匿名(1是 0否)
|
||||
*/
|
||||
@NotNull(message = "是否匿名不能为空")
|
||||
private Integer isAnonymous;
|
||||
|
||||
/**
|
||||
* 答案列表
|
||||
*/
|
||||
@NotEmpty(message = "答案列表不能为空")
|
||||
private List<AnswerItem> answers;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 单个答案项
|
||||
*/
|
||||
@Data
|
||||
public static class AnswerItem {
|
||||
/**
|
||||
* 题目ID
|
||||
*/
|
||||
@NotNull(message = "题目ID不能为空")
|
||||
private Long questionId;
|
||||
|
||||
/**
|
||||
* 题目类型(1单选题 2多选题 3问答题)
|
||||
*/
|
||||
@NotNull(message = "题目类型不能为空")
|
||||
private String questionType;
|
||||
|
||||
/**
|
||||
* 答案值
|
||||
*/
|
||||
@NotNull(message = "答案值不能为空")
|
||||
private String answerValue;
|
||||
|
||||
/**
|
||||
* 选项详情(单选题和多选题需要)
|
||||
*/
|
||||
private List<OptionDetail> options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 选项详情
|
||||
*/
|
||||
@Data
|
||||
public static class OptionDetail {
|
||||
/**
|
||||
* 选项ID
|
||||
*/
|
||||
private Long optionId;
|
||||
|
||||
/**
|
||||
* 选项值
|
||||
*/
|
||||
private String optionValue;
|
||||
|
||||
/**
|
||||
* 选项文本
|
||||
*/
|
||||
private String optionText;
|
||||
|
||||
/**
|
||||
* 是否选中
|
||||
*/
|
||||
private Boolean isSelected;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package org.dromara.zhxy.service;
|
||||
|
||||
import org.dromara.zhxy.domain.vo.HqSatSurveyVo;
|
||||
import org.dromara.zhxy.domain.bo.HqSatSurveyBo;
|
||||
import org.dromara.zhxy.domain.bo.HqSatAnswerSubmitBo;
|
||||
import org.dromara.zhxy.domain.vo.HqSatSurveyQuestionVo;
|
||||
import org.dromara.zhxy.domain.vo.HqSatRecordVo;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
|
||||
@@ -99,4 +101,36 @@ public interface IHqSatSurveyService {
|
||||
* @return 是否撤销成功
|
||||
*/
|
||||
Boolean unpublishSurvey(Long id);
|
||||
|
||||
/**
|
||||
* 提交问卷答案
|
||||
*
|
||||
* @param submitBo 答题提交数据
|
||||
* @return 答题记录
|
||||
*/
|
||||
HqSatRecordVo submitSurveyAnswer(HqSatAnswerSubmitBo submitBo);
|
||||
|
||||
/**
|
||||
* 保存问卷答案草稿
|
||||
*
|
||||
* @param submitBo 答题提交数据
|
||||
* @return 答题记录
|
||||
*/
|
||||
HqSatRecordVo saveSurveyDraft(HqSatAnswerSubmitBo submitBo);
|
||||
|
||||
/**
|
||||
* 检查用户是否已答过问卷
|
||||
*
|
||||
* @param surveyId 问卷ID
|
||||
* @return 是否已答过
|
||||
*/
|
||||
Boolean checkUserAnswered(Long surveyId);
|
||||
|
||||
/**
|
||||
* 获取用户问卷答题记录
|
||||
*
|
||||
* @param surveyId 问卷ID
|
||||
* @return 答题记录
|
||||
*/
|
||||
HqSatRecordVo getUserSurveyAnswer(Long surveyId);
|
||||
}
|
||||
+664
@@ -12,17 +12,33 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.dromara.zhxy.domain.bo.HqSatSurveyBo;
|
||||
import org.dromara.zhxy.domain.bo.HqSatAnswerSubmitBo;
|
||||
import org.dromara.zhxy.domain.bo.HqSatRecordBo;
|
||||
import org.dromara.zhxy.domain.bo.HqSatDetailBo;
|
||||
import org.dromara.zhxy.domain.vo.HqSatSurveyVo;
|
||||
import org.dromara.zhxy.domain.vo.HqSatRecordVo;
|
||||
import org.dromara.zhxy.domain.HqSatSurvey;
|
||||
import org.dromara.zhxy.mapper.HqSatSurveyMapper;
|
||||
import org.dromara.zhxy.service.IHqSatSurveyService;
|
||||
import org.dromara.zhxy.service.IHqSatSurveyQuestionService;
|
||||
import org.dromara.zhxy.service.IHqSatRecordService;
|
||||
import org.dromara.zhxy.service.IHqSatDetailService;
|
||||
import org.dromara.zhxy.domain.bo.HqSatSurveyQuestionBo;
|
||||
import org.dromara.zhxy.domain.vo.HqSatSurveyQuestionVo;
|
||||
import org.dromara.zhxy.domain.vo.HqSatDetailVo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* 问卷Service业务层处理
|
||||
@@ -37,6 +53,11 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService {
|
||||
|
||||
private final HqSatSurveyMapper baseMapper;
|
||||
private final IHqSatSurveyQuestionService surveyQuestionService;
|
||||
private final IHqSatRecordService recordService;
|
||||
private final IHqSatDetailService detailService;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
/**
|
||||
* 查询问卷
|
||||
@@ -271,4 +292,647 @@ public class HqSatSurveyServiceImpl implements IHqSatSurveyService {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交问卷答案
|
||||
*
|
||||
* @param submitBo 答题提交数据
|
||||
* @return 答题记录
|
||||
*/
|
||||
@Override
|
||||
public HqSatRecordVo submitSurveyAnswer(HqSatAnswerSubmitBo submitBo) {
|
||||
log.info("=== 收到前端答题提交请求 ===");
|
||||
log.info("问卷ID: {}, 答案数量: {}", submitBo.getSurveyId(),
|
||||
submitBo.getAnswers() != null ? submitBo.getAnswers().size() : 0);
|
||||
|
||||
if (submitBo.getAnswers() != null) {
|
||||
for (int i = 0; i < submitBo.getAnswers().size(); i++) {
|
||||
HqSatAnswerSubmitBo.AnswerItem answer = submitBo.getAnswers().get(i);
|
||||
log.info("答案{}: 题目ID={}, 题目类型={}, 答案值={}, 选项数量={}",
|
||||
i + 1, answer.getQuestionId(), answer.getQuestionType(), answer.getAnswerValue(),
|
||||
answer.getOptions() != null ? answer.getOptions().size() : 0);
|
||||
|
||||
if (answer.getOptions() != null) {
|
||||
for (int j = 0; j < answer.getOptions().size(); j++) {
|
||||
HqSatAnswerSubmitBo.OptionDetail option = answer.getOptions().get(j);
|
||||
log.info(" 选项{}: ID={}, 值={}, 文本={}, 选中={}",
|
||||
j + 1, option.getOptionId(), option.getOptionValue(),
|
||||
option.getOptionText(), option.getIsSelected());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return saveAnswerInternal(submitBo, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存问卷答案草稿
|
||||
*
|
||||
* @param submitBo 答题提交数据
|
||||
* @return 答题记录
|
||||
*/
|
||||
@Override
|
||||
public HqSatRecordVo saveSurveyDraft(HqSatAnswerSubmitBo submitBo) {
|
||||
return saveAnswerInternal(submitBo, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部方法:保存答题数据
|
||||
*
|
||||
* @param submitBo 答题提交数据
|
||||
* @param isDraft 是否为草稿
|
||||
* @return 答题记录
|
||||
*/
|
||||
private HqSatRecordVo saveAnswerInternal(HqSatAnswerSubmitBo submitBo, boolean isDraft) {
|
||||
// 验证问卷是否存在且可以答题
|
||||
HqSatSurveyVo survey = queryById(submitBo.getSurveyId());
|
||||
if (survey == null) {
|
||||
throw new ServiceException("问卷不存在");
|
||||
}
|
||||
|
||||
// 检查问卷状态
|
||||
if (!isDraft && !"1".equals(survey.getStatus())) {
|
||||
throw new ServiceException("问卷未在进行中状态,无法提交答案");
|
||||
}
|
||||
|
||||
// 获取当前用户信息
|
||||
Long userId = LoginHelper.getUserId();
|
||||
String userTypeString = LoginHelper.getLoginUser().getUserType();
|
||||
|
||||
// 将角色字符串转换为数字编码
|
||||
String userType = convertUserTypeToCode(userTypeString);
|
||||
|
||||
// 检查用户提交次数是否超过限制(非草稿模式下)
|
||||
if (!isDraft) {
|
||||
int submitCount = getUserSubmitCount(submitBo.getSurveyId(), userId);
|
||||
int maxSubmitTimes = survey.getMaxSubmitTimes() != null ? survey.getMaxSubmitTimes().intValue() : 1;
|
||||
|
||||
log.info("用户提交次数检查: 用户ID={}, 问卷ID={}, 已提交次数={}, 最大允许次数={}",
|
||||
userId, submitBo.getSurveyId(), submitCount, maxSubmitTimes);
|
||||
|
||||
// maxSubmitTimes为0表示不限制提交次数
|
||||
if (maxSubmitTimes > 0 && submitCount >= maxSubmitTimes) {
|
||||
throw new ServiceException("您已达到该问卷的最大提交次数限制(" + maxSubmitTimes + "次)");
|
||||
}
|
||||
}
|
||||
|
||||
// 在保存新答题数据之前,将用户之前的答题记录标记为已删除
|
||||
markPreviousAnswersAsDeleted(submitBo.getSurveyId(), userId);
|
||||
|
||||
// 创建答题记录
|
||||
HqSatRecordBo recordBo = new HqSatRecordBo();
|
||||
recordBo.setSurveyId(submitBo.getSurveyId());
|
||||
recordBo.setUserId(userId);
|
||||
recordBo.setUserType(userType);
|
||||
recordBo.setSubmitTime(new Date());
|
||||
recordBo.setTotalScore(0L); // 暂时设为0,后续可以计算
|
||||
recordBo.setAvgScore(0L);
|
||||
recordBo.setRemark(submitBo.getRemark());
|
||||
|
||||
// 保存答题记录
|
||||
boolean recordSaved = recordService.insertByBo(recordBo);
|
||||
if (!recordSaved) {
|
||||
throw new ServiceException("保存答题记录失败");
|
||||
}
|
||||
|
||||
// 保存答题详情
|
||||
for (HqSatAnswerSubmitBo.AnswerItem answerItem : submitBo.getAnswers()) {
|
||||
HqSatDetailBo detailBo = new HqSatDetailBo();
|
||||
detailBo.setRecordId(recordBo.getId());
|
||||
detailBo.setQuestionId(answerItem.getQuestionId());
|
||||
detailBo.setAnswerScore(0L); // 暂时设为0,后续可以计算分数
|
||||
|
||||
// 将答案内容临时存储在remark字段中
|
||||
String answerContent = buildAnswerContent(answerItem);
|
||||
detailBo.setRemark(answerContent);
|
||||
|
||||
boolean detailSaved = detailService.insertByBo(detailBo);
|
||||
if (!detailSaved) {
|
||||
log.error("保存答题详情失败,题目ID: {}", answerItem.getQuestionId());
|
||||
}
|
||||
|
||||
// 保存具体的答案信息到 hq_sat_answer 表
|
||||
saveAnswerDetails(recordBo.getId(), answerItem);
|
||||
}
|
||||
|
||||
// 返回答题记录
|
||||
return recordService.queryById(recordBo.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将用户角色字符串转换为数字编码
|
||||
*
|
||||
* @param userTypeString 用户角色字符串
|
||||
* @return 数字编码字符串
|
||||
*/
|
||||
private String convertUserTypeToCode(String userTypeString) {
|
||||
if (userTypeString == null) {
|
||||
return "1"; // 默认为学生
|
||||
}
|
||||
|
||||
// 转换为小写进行比较
|
||||
String lowerCaseType = userTypeString.toLowerCase();
|
||||
|
||||
// 学生角色判断
|
||||
if (lowerCaseType.contains("student") || lowerCaseType.contains("学生") || lowerCaseType.contains("stu")) {
|
||||
return "1";
|
||||
}
|
||||
|
||||
// 教师角色判断
|
||||
if (lowerCaseType.contains("teacher") || lowerCaseType.contains("老师") ||
|
||||
lowerCaseType.contains("教师") || lowerCaseType.contains("教职工") || lowerCaseType.contains("tea")) {
|
||||
return "2";
|
||||
}
|
||||
|
||||
// 管理员角色判断
|
||||
if (lowerCaseType.contains("admin") || lowerCaseType.contains("管理员") ||
|
||||
lowerCaseType.contains("超级管理员") || lowerCaseType.contains("administrator")) {
|
||||
return "3";
|
||||
}
|
||||
|
||||
// 默认返回学生
|
||||
log.warn("未识别的用户角色类型: {}, 默认设置为学生角色", userTypeString);
|
||||
return "1";
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建答案内容字符串
|
||||
*
|
||||
* @param answerItem 答案项
|
||||
* @return 答案内容字符串
|
||||
*/
|
||||
private String buildAnswerContent(HqSatAnswerSubmitBo.AnswerItem answerItem) {
|
||||
StringBuilder content = new StringBuilder();
|
||||
content.append("题目类型:").append(answerItem.getQuestionType()).append(";");
|
||||
content.append("答案值:").append(answerItem.getAnswerValue()).append(";");
|
||||
|
||||
if (answerItem.getOptions() != null && !answerItem.getOptions().isEmpty()) {
|
||||
content.append("选项详情:");
|
||||
for (HqSatAnswerSubmitBo.OptionDetail option : answerItem.getOptions()) {
|
||||
if (Boolean.TRUE.equals(option.getIsSelected())) {
|
||||
content.append(option.getOptionValue()).append("(")
|
||||
.append(option.getOptionText()).append("),");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将用户之前的答题记录标记为已删除
|
||||
*
|
||||
* @param surveyId 问卷ID
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
private void markPreviousAnswersAsDeleted(Long surveyId, Long userId) {
|
||||
try {
|
||||
log.info("=== 开始标记用户之前的答题记录为已删除 ===");
|
||||
log.info("问卷ID: {}, 用户ID: {}", surveyId, userId);
|
||||
|
||||
// 1. 查找该用户对该问卷的所有历史答题记录ID
|
||||
String findRecordsSql = """
|
||||
SELECT id FROM hq_sat_record
|
||||
WHERE survey_id = ? AND user_id = ? AND deleted = 0
|
||||
""";
|
||||
|
||||
List<Map<String, Object>> recordResults = jdbcTemplate.queryForList(findRecordsSql, surveyId, userId);
|
||||
|
||||
if (recordResults.isEmpty()) {
|
||||
log.info("用户没有历史答题记录,无需标记删除");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("找到 {} 条历史答题记录", recordResults.size());
|
||||
|
||||
// 2. 标记答题记录表为已删除
|
||||
String updateRecordSql = """
|
||||
UPDATE hq_sat_record
|
||||
SET deleted = 1, update_by = ?, update_time = NOW()
|
||||
WHERE survey_id = ? AND user_id = ? AND deleted = 0
|
||||
""";
|
||||
|
||||
int recordUpdated = jdbcTemplate.update(updateRecordSql,
|
||||
LoginHelper.getUserId(), surveyId, userId);
|
||||
log.info("标记答题记录为已删除,影响行数: {}", recordUpdated);
|
||||
|
||||
// 3. 标记答题详情表为已删除(通过record_id关联)
|
||||
for (Map<String, Object> record : recordResults) {
|
||||
Long recordId = Long.valueOf(record.get("id").toString());
|
||||
|
||||
String updateDetailSql = """
|
||||
UPDATE hq_sat_detail
|
||||
SET deleted = 1, update_by = ?, update_time = NOW()
|
||||
WHERE record_id = ? AND deleted = 0
|
||||
""";
|
||||
|
||||
int detailUpdated = jdbcTemplate.update(updateDetailSql,
|
||||
LoginHelper.getUserId(), recordId);
|
||||
log.info("标记答题详情为已删除,记录ID: {}, 影响行数: {}", recordId, detailUpdated);
|
||||
|
||||
// 4. 标记答案表为已删除(通过record_id关联)
|
||||
String updateAnswerSql = """
|
||||
UPDATE hq_sat_answer
|
||||
SET deleted = 1, update_by = ?, update_time = NOW()
|
||||
WHERE record_id = ? AND deleted = 0
|
||||
""";
|
||||
|
||||
int answerUpdated = jdbcTemplate.update(updateAnswerSql,
|
||||
LoginHelper.getUserId(), recordId);
|
||||
log.info("标记答案数据为已删除,记录ID: {}, 影响行数: {}", recordId, answerUpdated);
|
||||
}
|
||||
|
||||
log.info("=== 用户历史答题记录标记删除完成 ===");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("标记用户历史答题记录为已删除失败,问卷ID: {}, 用户ID: {}, 错误详情: {}",
|
||||
surveyId, userId, e.getMessage(), e);
|
||||
// 这里不抛出异常,避免影响新答题数据的保存
|
||||
log.warn("历史数据删除失败不影响新答题数据保存,继续执行后续流程");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否已答过问卷
|
||||
*
|
||||
* @param surveyId 问卷ID
|
||||
* @return 是否已答过
|
||||
*/
|
||||
@Override
|
||||
public Boolean checkUserAnswered(Long surveyId) {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
return getUserSubmitCount(surveyId, userId) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户对指定问卷的提交次数(只统计未删除的记录)
|
||||
*
|
||||
* @param surveyId 问卷ID
|
||||
* @param userId 用户ID
|
||||
* @return 提交次数
|
||||
*/
|
||||
private int getUserSubmitCount(Long surveyId, Long userId) {
|
||||
try {
|
||||
// 使用SQL直接查询,确保只统计未删除的记录
|
||||
String countSql = """
|
||||
SELECT COUNT(*) FROM hq_sat_record
|
||||
WHERE survey_id = ? AND user_id = ? AND deleted = 0
|
||||
""";
|
||||
|
||||
Integer count = jdbcTemplate.queryForObject(countSql, Integer.class, surveyId, userId);
|
||||
int submitCount = count != null ? count : 0;
|
||||
|
||||
log.debug("用户{}对问卷{}的有效提交次数: {}", userId, surveyId, submitCount);
|
||||
return submitCount;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("查询用户提交次数失败,问卷ID: {}, 用户ID: {}, 错误: {}",
|
||||
surveyId, userId, e.getMessage(), e);
|
||||
|
||||
// 发生异常时使用原有方式查询作为降级方案
|
||||
log.warn("使用降级方案查询提交次数");
|
||||
HqSatRecordBo queryBo = new HqSatRecordBo();
|
||||
queryBo.setSurveyId(surveyId);
|
||||
queryBo.setUserId(userId);
|
||||
|
||||
List<HqSatRecordVo> records = recordService.queryList(queryBo);
|
||||
int count = records.size();
|
||||
|
||||
log.debug("降级方案统计用户{}对问卷{}的提交次数: {}", userId, surveyId, count);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户问卷答题记录(只返回未删除的最新记录)
|
||||
*
|
||||
* @param surveyId 问卷ID
|
||||
* @return 答题记录(包含答题详情)
|
||||
*/
|
||||
@Override
|
||||
public HqSatRecordVo getUserSurveyAnswer(Long surveyId) {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
HqSatRecordVo record = null;
|
||||
|
||||
try {
|
||||
// 使用SQL直接查询,确保只获取未删除的最新记录
|
||||
String findLatestRecordSql = """
|
||||
SELECT * FROM hq_sat_record
|
||||
WHERE survey_id = ? AND user_id = ? AND deleted = 0
|
||||
ORDER BY create_time DESC
|
||||
LIMIT 1
|
||||
""";
|
||||
|
||||
List<Map<String, Object>> recordResults = jdbcTemplate.queryForList(findLatestRecordSql, surveyId, userId);
|
||||
|
||||
if (recordResults.isEmpty()) {
|
||||
throw new ServiceException("未找到有效的答题记录", 404);
|
||||
}
|
||||
|
||||
Map<String, Object> recordMap = recordResults.get(0);
|
||||
Long recordId = Long.valueOf(recordMap.get("id").toString());
|
||||
|
||||
log.info("找到用户最新有效答题记录,记录ID: {}", recordId);
|
||||
|
||||
// 通过recordId获取完整的记录对象
|
||||
record = recordService.queryById(recordId);
|
||||
if (record == null) {
|
||||
throw new ServiceException("答题记录数据异常", 500);
|
||||
}
|
||||
|
||||
} catch (ServiceException e) {
|
||||
throw e; // 重新抛出业务异常
|
||||
} catch (Exception e) {
|
||||
log.error("查询用户答题记录失败,使用降级方案,问卷ID: {}, 用户ID: {}, 错误: {}",
|
||||
surveyId, userId, e.getMessage(), e);
|
||||
|
||||
// 降级方案:使用原有方式查询
|
||||
HqSatRecordBo queryBo = new HqSatRecordBo();
|
||||
queryBo.setSurveyId(surveyId);
|
||||
queryBo.setUserId(userId);
|
||||
|
||||
List<HqSatRecordVo> records = recordService.queryList(queryBo);
|
||||
if (records.isEmpty()) {
|
||||
throw new ServiceException("未找到答题记录", 404);
|
||||
}
|
||||
|
||||
// 获取最新的一条记录
|
||||
record = records.get(0);
|
||||
}
|
||||
|
||||
// 直接从 hq_sat_answer 表查询答案详情(更准确的数据源)
|
||||
List<Map<String, Object>> answers = getAnswersFromSatAnswerTable(record.getId());
|
||||
log.debug("从 hq_sat_answer 表查询到答案记录数: {}", answers.size());
|
||||
|
||||
// 构造包含答案详情的记录对象
|
||||
HqSatRecordVo recordWithAnswers = new HqSatRecordVo();
|
||||
recordWithAnswers.setId(record.getId());
|
||||
recordWithAnswers.setSurveyId(record.getSurveyId());
|
||||
recordWithAnswers.setUserId(record.getUserId());
|
||||
recordWithAnswers.setUserType(record.getUserType());
|
||||
recordWithAnswers.setUserDept(record.getUserDept());
|
||||
recordWithAnswers.setSubmitTime(record.getSubmitTime());
|
||||
recordWithAnswers.setTotalScore(record.getTotalScore());
|
||||
recordWithAnswers.setAvgScore(record.getAvgScore());
|
||||
recordWithAnswers.setRemark(record.getRemark());
|
||||
|
||||
// 将answers数组作为扩展属性设置到记录对象
|
||||
// 由于HqSatRecordVo没有answers字段,我们使用一个包装类
|
||||
ExtendedSatRecordVo extendedRecord = new ExtendedSatRecordVo();
|
||||
extendedRecord.setRecord(recordWithAnswers);
|
||||
extendedRecord.setAnswers(answers);
|
||||
|
||||
// 临时使用remark字段传递answers数据
|
||||
try {
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
String answersJson = mapper.writeValueAsString(answers);
|
||||
recordWithAnswers.setRemark("ANSWERS_DATA:" + answersJson);
|
||||
log.debug("答案数据序列化成功,数据长度: {}", answersJson.length());
|
||||
} catch (Exception e) {
|
||||
log.error("序列化答案数据失败", e);
|
||||
}
|
||||
|
||||
return recordWithAnswers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 hq_sat_answer 表查询答案详情
|
||||
*
|
||||
* @param recordId 答题记录ID
|
||||
* @return 答案详情列表
|
||||
*/
|
||||
private List<Map<String, Object>> getAnswersFromSatAnswerTable(Long recordId) {
|
||||
try {
|
||||
log.debug("从 hq_sat_answer 表查询答案,记录ID: {}", recordId);
|
||||
|
||||
// 查询该记录的所有答案
|
||||
String answerSql = """
|
||||
SELECT question_id, question_type, answer_value,
|
||||
option_id, option_value, option_text, answer_score
|
||||
FROM hq_sat_answer
|
||||
WHERE record_id = ? AND deleted = 0
|
||||
ORDER BY question_id, option_id
|
||||
""";
|
||||
|
||||
List<Map<String, Object>> answerResults = jdbcTemplate.queryForList(answerSql, recordId);
|
||||
log.debug("查询到原始答案数据: {} 条", answerResults.size());
|
||||
|
||||
// 按题目ID分组聚合答案
|
||||
Map<Long, Map<String, Object>> questionAnswerMap = new HashMap<>();
|
||||
|
||||
for (Map<String, Object> answerRow : answerResults) {
|
||||
Long questionId = Long.valueOf(answerRow.get("question_id").toString());
|
||||
String questionType = answerRow.get("question_type").toString();
|
||||
String answerValue = (String) answerRow.get("answer_value");
|
||||
|
||||
// 获取或创建题目答案对象
|
||||
Map<String, Object> questionAnswer = questionAnswerMap.computeIfAbsent(questionId, k -> {
|
||||
Map<String, Object> answer = new HashMap<>();
|
||||
answer.put("questionId", questionId);
|
||||
answer.put("questionType", questionType);
|
||||
answer.put("answerValue", answerValue);
|
||||
return answer;
|
||||
});
|
||||
|
||||
log.debug("处理题目{}的答案: questionType={}, answerValue={}",
|
||||
questionId, questionType, answerValue);
|
||||
}
|
||||
|
||||
// 转换为列表并返回
|
||||
List<Map<String, Object>> result = new ArrayList<>(questionAnswerMap.values());
|
||||
log.debug("聚合后的答案数据: {} 个题目", result.size());
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("从 hq_sat_answer 表查询答案失败,记录ID: {}, 错误: {}",
|
||||
recordId, e.getMessage(), e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从remark字段解析答案数据
|
||||
*
|
||||
* @param detail 答题详情
|
||||
* @return 解析后的答案数据
|
||||
*/
|
||||
private Map<String, Object> parseAnswerFromRemark(HqSatDetailVo detail) {
|
||||
if (detail.getRemark() == null || detail.getRemark().trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> answer = new HashMap<>();
|
||||
answer.put("questionId", detail.getQuestionId());
|
||||
|
||||
String remark = detail.getRemark();
|
||||
log.debug("解析答题详情,题目ID: {}, remark: {}", detail.getQuestionId(), remark);
|
||||
|
||||
// 解析remark中的数据格式: "题目类型:1;答案值:A;选项详情:A(选项文本),"
|
||||
try {
|
||||
String[] parts = remark.split(";");
|
||||
String questionType = "";
|
||||
String answerValue = "";
|
||||
|
||||
for (String part : parts) {
|
||||
if (part.startsWith("题目类型:")) {
|
||||
questionType = part.substring("题目类型:".length());
|
||||
answer.put("questionType", questionType);
|
||||
} else if (part.startsWith("答案值:")) {
|
||||
answerValue = part.substring("答案值:".length());
|
||||
answer.put("answerValue", answerValue);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置其他默认值
|
||||
answer.put("id", detail.getId());
|
||||
answer.put("recordId", detail.getRecordId());
|
||||
answer.put("answerScore", detail.getAnswerScore() != null ? detail.getAnswerScore() : 0);
|
||||
answer.put("remark", detail.getRemark());
|
||||
|
||||
log.debug("解析答案完成: questionId={}, questionType={}, answerValue={}",
|
||||
detail.getQuestionId(), questionType, answerValue);
|
||||
|
||||
return answer;
|
||||
} catch (Exception e) {
|
||||
log.error("解析答题详情失败,题目ID: {}, remark: {}", detail.getQuestionId(), remark, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存答案详情到 hq_sat_answer 表
|
||||
*
|
||||
* @param recordId 答题记录ID
|
||||
* @param answerItem 答案项
|
||||
*/
|
||||
private void saveAnswerDetails(Long recordId, HqSatAnswerSubmitBo.AnswerItem answerItem) {
|
||||
try {
|
||||
String questionType = answerItem.getQuestionType();
|
||||
String answerValue = answerItem.getAnswerValue();
|
||||
|
||||
log.info("=== 开始保存答案详情到 hq_sat_answer 表 ===");
|
||||
log.info("记录ID: {}, 题目ID: {}, 题目类型: {}, 答案值: {}",
|
||||
recordId, answerItem.getQuestionId(), questionType, answerValue);
|
||||
log.info("选项数据: {}", answerItem.getOptions());
|
||||
|
||||
if (questionType.equals("1") || questionType.equals("2")) {
|
||||
// 单选题或多选题:保存选项信息
|
||||
log.info("处理单选题或多选题,题目类型: {}", questionType);
|
||||
|
||||
if (answerItem.getOptions() != null && !answerItem.getOptions().isEmpty()) {
|
||||
log.info("选项数据不为空,选项数量: {}", answerItem.getOptions().size());
|
||||
|
||||
for (HqSatAnswerSubmitBo.OptionDetail option : answerItem.getOptions()) {
|
||||
log.info("处理选项: optionId={}, optionValue={}, optionText={}, isSelected={}",
|
||||
option.getOptionId(), option.getOptionValue(), option.getOptionText(),
|
||||
option.getIsSelected());
|
||||
|
||||
if (Boolean.TRUE.equals(option.getIsSelected())) {
|
||||
log.info("选项已选中,准备插入数据库");
|
||||
|
||||
// 根据选项ID查询真实的分值
|
||||
Long realScore = 0L;
|
||||
try {
|
||||
String scoreSql = "SELECT option_value FROM hq_sat_option WHERE id = ? AND deleted = 0";
|
||||
List<Map<String, Object>> scoreResult = jdbcTemplate.queryForList(scoreSql,
|
||||
option.getOptionId());
|
||||
if (!scoreResult.isEmpty()) {
|
||||
Object scoreValue = scoreResult.get(0).get("option_value");
|
||||
realScore = scoreValue != null ? Long.valueOf(scoreValue.toString()) : 0L;
|
||||
log.info("查询到选项{}的真实分值: {}", option.getOptionId(), realScore);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("查询选项分值失败,使用默认值0,选项ID: {}, 错误: {}", option.getOptionId(), e.getMessage());
|
||||
}
|
||||
|
||||
String sql = """
|
||||
INSERT INTO hq_sat_answer (
|
||||
tenant_id, create_by, create_time,
|
||||
record_id, question_id, question_type,
|
||||
option_id, option_value, option_text,
|
||||
answer_value, answer_score, deleted
|
||||
) VALUES (?, ?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?, 0)
|
||||
""";
|
||||
|
||||
int rowsAffected = jdbcTemplate.update(sql,
|
||||
LoginHelper.getTenantId(), // tenant_id
|
||||
LoginHelper.getUserId(), // create_by
|
||||
recordId, // record_id
|
||||
answerItem.getQuestionId(), // question_id
|
||||
questionType, // question_type
|
||||
option.getOptionId(), // option_id
|
||||
option.getOptionValue(), // option_value(A、B、C、D)
|
||||
option.getOptionText(), // option_text
|
||||
answerValue, // answer_value
|
||||
realScore // answer_score(真实分值)
|
||||
);
|
||||
|
||||
log.info("选择题答案插入成功,影响行数: {}, 选项标识: {}, 分值: {}",
|
||||
rowsAffected, option.getOptionValue(), realScore);
|
||||
} else {
|
||||
log.info("选项未选中,跳过: {}", option.getOptionValue());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("选择题选项数据为空或null,题目ID: {}, 题目类型: {}",
|
||||
answerItem.getQuestionId(), questionType);
|
||||
}
|
||||
} else if (questionType.equals("3")) {
|
||||
// 问答题:直接保存文本内容
|
||||
log.info("处理问答题,答案值: {}", answerValue);
|
||||
|
||||
String sql = """
|
||||
INSERT INTO hq_sat_answer (
|
||||
tenant_id, create_by, create_time,
|
||||
record_id, question_id, question_type,
|
||||
option_id, option_value, option_text,
|
||||
answer_value, answer_score, deleted
|
||||
) VALUES (?, ?, NOW(), ?, ?, ?, NULL, NULL, NULL, ?, 0, 0)
|
||||
""";
|
||||
|
||||
int rowsAffected = jdbcTemplate.update(sql,
|
||||
LoginHelper.getTenantId(), // tenant_id
|
||||
LoginHelper.getUserId(), // create_by
|
||||
recordId, // record_id
|
||||
answerItem.getQuestionId(), // question_id
|
||||
questionType, // question_type
|
||||
answerValue // answer_value
|
||||
);
|
||||
|
||||
log.info("问答题答案插入成功,影响行数: {}", rowsAffected);
|
||||
}
|
||||
|
||||
log.info("=== 答案详情保存完成 ===");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("保存答案到 hq_sat_answer 表失败,题目ID: {}, 题目类型: {}, 错误详情: {}",
|
||||
answerItem.getQuestionId(), answerItem.getQuestionType(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展的答题记录VO,包含答案列表
|
||||
*/
|
||||
private static class ExtendedSatRecordVo {
|
||||
private HqSatRecordVo record;
|
||||
private List<Map<String, Object>> answers;
|
||||
|
||||
public HqSatRecordVo getRecord() {
|
||||
return record;
|
||||
}
|
||||
|
||||
public void setRecord(HqSatRecordVo record) {
|
||||
this.record = record;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getAnswers() {
|
||||
return answers;
|
||||
}
|
||||
|
||||
public void setAnswers(List<Map<String, Object>> answers) {
|
||||
this.answers = answers;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,771 @@
|
||||
# API 接口文档
|
||||
|
||||
## 文档说明
|
||||
|
||||
本文档记录项目所有的 API 接口信息,包括请求方式、参数说明、响应格式等。所有接口遵循 RESTful 设计规范。
|
||||
|
||||
### 基础信息
|
||||
|
||||
- **基础路径**: `/api`
|
||||
- **响应格式**: JSON
|
||||
- **字符编码**: UTF-8
|
||||
- **权限认证**: Sa-Token + JWT
|
||||
- **版本**: v1.0
|
||||
|
||||
### 统一响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
#### 状态码说明
|
||||
|
||||
| 状态码 | 说明 | 备注 |
|
||||
| ------ | ---------- | ---------------------- |
|
||||
| 200 | 成功 | 请求成功 |
|
||||
| 400 | 参数错误 | 请求参数格式或内容错误 |
|
||||
| 401 | 未认证 | 需要登录 |
|
||||
| 403 | 无权限 | 无访问权限 |
|
||||
| 404 | 不存在 | 请求的资源不存在 |
|
||||
| 500 | 服务器错误 | 内部服务器错误 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 问卷管理 API
|
||||
|
||||
### 1.1 问卷基础管理
|
||||
|
||||
#### 查询问卷列表
|
||||
|
||||
**接口地址**: `GET /hq/satSurvey/list`
|
||||
|
||||
**接口描述**: 分页查询问卷列表,支持权限过滤
|
||||
|
||||
**权限要求**: `hq:satSurvey:list`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| ----------- | ------ | ---- | --------------------- |
|
||||
| pageNum | int | 否 | 页码,默认 1 |
|
||||
| pageSize | int | 否 | 页大小,默认 10 |
|
||||
| surveyCode | string | 否 | 问卷编号 |
|
||||
| surveyTitle | string | 否 | 问卷标题 |
|
||||
| targetGroup | string | 否 | 目标群体 |
|
||||
| status | string | 否 | 问卷状态 |
|
||||
| isPublished | int | 否 | 是否发布(1 是 0 否) |
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": {
|
||||
"total": 100,
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"surveyCode": "SURVEY001",
|
||||
"surveyTitle": "满意度调查",
|
||||
"surveyDesc": "学生满意度调查",
|
||||
"targetGroup": "1",
|
||||
"startTime": "2025-01-01 00:00:00",
|
||||
"endTime": "2025-01-31 23:59:59",
|
||||
"isAnonymous": 1,
|
||||
"isPublished": 1,
|
||||
"status": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取问卷详情
|
||||
|
||||
**接口地址**: `GET /hq/satSurvey/{id}`
|
||||
|
||||
**接口描述**: 根据 ID 获取问卷详细信息
|
||||
|
||||
**权限要求**: `hq:satSurvey:query`
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| ------ | ---- | ---- | ------- |
|
||||
| id | long | 是 | 问卷 ID |
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"surveyCode": "SURVEY001",
|
||||
"surveyTitle": "满意度调查",
|
||||
"surveyDesc": "学生满意度调查",
|
||||
"surveyInstruction": "请认真填写",
|
||||
"targetGroup": "1",
|
||||
"startTime": "2025-01-01 00:00:00",
|
||||
"endTime": "2025-01-31 23:59:59",
|
||||
"maxSubmitTimes": 1,
|
||||
"isAnonymous": 1,
|
||||
"isPublished": 1,
|
||||
"status": "1",
|
||||
"remark": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 新增问卷
|
||||
|
||||
**接口地址**: `POST /hq/satSurvey`
|
||||
|
||||
**接口描述**: 创建新的问卷
|
||||
|
||||
**权限要求**: `hq:satSurvey:add`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
```json
|
||||
{
|
||||
"surveyCode": "SURVEY001",
|
||||
"surveyTitle": "满意度调查",
|
||||
"surveyDesc": "学生满意度调查",
|
||||
"surveyInstruction": "请认真填写",
|
||||
"targetGroup": "1",
|
||||
"startTime": "2025-01-01 00:00:00",
|
||||
"endTime": "2025-01-31 23:59:59",
|
||||
"maxSubmitTimes": 1,
|
||||
"isAnonymous": 1,
|
||||
"remark": ""
|
||||
}
|
||||
```
|
||||
|
||||
#### 修改问卷
|
||||
|
||||
**接口地址**: `PUT /hq/satSurvey`
|
||||
|
||||
**接口描述**: 修改问卷信息
|
||||
|
||||
**权限要求**: `hq:satSurvey:edit`
|
||||
|
||||
#### 删除问卷
|
||||
|
||||
**接口地址**: `DELETE /hq/satSurvey/{ids}`
|
||||
|
||||
**接口描述**: 批量删除问卷
|
||||
|
||||
**权限要求**: `hq:satSurvey:remove`
|
||||
|
||||
#### 发布问卷
|
||||
|
||||
**接口地址**: `PUT /hq/satSurvey/{id}/publish`
|
||||
|
||||
**接口描述**: 发布问卷,将状态改为进行中
|
||||
|
||||
**权限要求**: `hq:satSurvey:publish`
|
||||
|
||||
#### 撤销发布问卷
|
||||
|
||||
**接口地址**: `PUT /hq/satSurvey/{id}/unpublish`
|
||||
|
||||
**接口描述**: 撤销发布问卷,将状态改为草稿
|
||||
|
||||
**权限要求**: `hq:satSurvey:unpublish`
|
||||
|
||||
### 1.2 问卷题目管理
|
||||
|
||||
#### 获取问卷题目列表
|
||||
|
||||
**接口地址**: `GET /hq/satSurvey/{surveyId}/questions`
|
||||
|
||||
**接口描述**: 获取问卷关联的题目列表,包含题目详细信息和选项
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| -------- | ---- | ---- | ------- |
|
||||
| surveyId | long | 是 | 问卷 ID |
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"surveyId": 1,
|
||||
"questionId": 1,
|
||||
"sortOrder": 1,
|
||||
"weight": 1,
|
||||
"isRequired": 1,
|
||||
"questionTitle": "您对服务质量的评价?",
|
||||
"questionType": "1",
|
||||
"questionDesc": "请选择一个选项",
|
||||
"options": [
|
||||
{
|
||||
"id": 1,
|
||||
"questionId": 1,
|
||||
"optionValue": "A",
|
||||
"optionText": "非常满意",
|
||||
"optionScore": 5,
|
||||
"sortOrder": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 更新问卷题目关联
|
||||
|
||||
**接口地址**: `PUT /hq/satSurvey/{surveyId}/questions`
|
||||
|
||||
**接口描述**: 批量更新问卷题目关联关系
|
||||
|
||||
**权限要求**: `hq:satSurvey:question`
|
||||
|
||||
---
|
||||
|
||||
## 2. 答题 API(新增)
|
||||
|
||||
### 2.1 答题提交
|
||||
|
||||
#### 提交问卷答案
|
||||
|
||||
**接口地址**: `POST /hq/satSurvey/answer/submit`
|
||||
|
||||
**接口描述**: 提交问卷答案,正式提交后不可修改
|
||||
|
||||
**权限要求**: 需要登录
|
||||
|
||||
**请求参数**:
|
||||
|
||||
```json
|
||||
{
|
||||
"surveyId": 1,
|
||||
"startTime": "2025-01-26T14:00:00.000Z",
|
||||
"endTime": "2025-01-26T14:15:00.000Z",
|
||||
"isAnonymous": 1,
|
||||
"answers": [
|
||||
{
|
||||
"questionId": 1,
|
||||
"questionType": "1",
|
||||
"answerValue": "A",
|
||||
"options": [
|
||||
{
|
||||
"optionId": 1,
|
||||
"optionValue": "A",
|
||||
"optionText": "非常满意",
|
||||
"isSelected": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"questionId": 2,
|
||||
"questionType": "2",
|
||||
"answerValue": "A,C,D",
|
||||
"options": [
|
||||
{
|
||||
"optionId": 2,
|
||||
"optionValue": "A",
|
||||
"optionText": "选项A",
|
||||
"isSelected": true
|
||||
},
|
||||
{
|
||||
"optionId": 4,
|
||||
"optionValue": "C",
|
||||
"optionText": "选项C",
|
||||
"isSelected": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"questionId": 3,
|
||||
"questionType": "3",
|
||||
"answerValue": "这是我的意见和建议内容",
|
||||
"options": []
|
||||
}
|
||||
],
|
||||
"remark": ""
|
||||
}
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| ----------- | ------ | ---- | ----------------------------- |
|
||||
| surveyId | long | 是 | 问卷 ID |
|
||||
| startTime | string | 是 | 答题开始时间(ISO 8601 格式) |
|
||||
| endTime | string | 是 | 答题结束时间(ISO 8601 格式) |
|
||||
| isAnonymous | int | 是 | 是否匿名(1 是 0 否) |
|
||||
| answers | array | 是 | 答案列表 |
|
||||
| remark | string | 否 | 备注 |
|
||||
|
||||
**answers 中每项参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| ------------ | ------ | ---- | ------------------------------------------------ |
|
||||
| questionId | long | 是 | 题目 ID |
|
||||
| questionType | string | 是 | 题目类型(1 单选题 2 多选题 3 问答题) |
|
||||
| answerValue | string | 是 | 答案值(单选存选项值,多选逗号分隔,问答存文本) |
|
||||
| options | array | 否 | 选项详情(单选题和多选题需要) |
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "答题提交成功",
|
||||
"data": {
|
||||
"id": 1001,
|
||||
"surveyId": 1,
|
||||
"userId": 1,
|
||||
"userType": "student",
|
||||
"submitTime": "2025-01-26T14:15:00.000Z",
|
||||
"totalScore": 0,
|
||||
"avgScore": 0,
|
||||
"remark": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**多次提交功能说明**(v1.0.3 新增,v1.0.4 优化):
|
||||
|
||||
系统支持问卷多次提交功能,提交次数限制由问卷的 `max_submit_times` 字段控制:
|
||||
|
||||
1. **提交次数规则**:
|
||||
|
||||
- `max_submit_times = 0`: 不限制提交次数,用户可以无限次提交
|
||||
- `max_submit_times = 1`: 最多提交 1 次(默认行为,向后兼容)
|
||||
- `max_submit_times = N`: 最多提交 N 次
|
||||
|
||||
2. **数据存储策略**(v1.0.4 重要更新):
|
||||
|
||||
- **软删除历史数据**: 用户提交新答案时,系统会自动将之前的答题记录标记为已删除(deleted=1)
|
||||
- **保持数据完整性**: 历史数据完整保留用于审计,但业务查询只返回最新有效数据
|
||||
- **关联删除**: 通过 record_id 关联,确保 hq_sat_record、hq_sat_detail、hq_sat_answer 三表数据状态同步
|
||||
|
||||
3. **数据状态管理**:
|
||||
|
||||
- `deleted = 0`: 有效数据,正常业务查询可见
|
||||
- `deleted = 1`: 历史数据,仅审计查询可见
|
||||
- 用户每次只能看到最新的有效答题记录
|
||||
|
||||
4. **处理流程**:
|
||||
|
||||
```
|
||||
用户提交新答案 → 标记历史数据为删除 → 保存新答题数据 → 返回最新记录
|
||||
```
|
||||
|
||||
5. **错误处理**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 500,
|
||||
"msg": "您已达到该问卷的最大提交次数限制(3次)",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
6. **使用场景**:
|
||||
|
||||
- **单次提交**: 正式考试、重要调研等
|
||||
- **多次提交**: 练习测试、阶段性评估等
|
||||
- **无限提交**: 学习练习、反馈收集等
|
||||
|
||||
7. **业务优势**:
|
||||
- ✅ **用户体验**: 查看答题记录始终是最新版本
|
||||
- ✅ **数据审计**: 历史记录完整保留,支持数据回溯
|
||||
- ✅ **查询性能**: 业务查询只涉及有效数据,提高效率
|
||||
- ✅ **数据一致性**: 避免多版本数据混乱问题
|
||||
|
||||
**数据保存说明**:
|
||||
|
||||
此接口在保存答题记录时,会同时保存详细的答案信息到两个表:
|
||||
|
||||
1. **hq_sat_detail 表**:保存答题概要信息
|
||||
|
||||
- 记录答题记录 ID、题目 ID、答案分数等基础信息
|
||||
- remark 字段存储答案内容的摘要信息
|
||||
|
||||
2. **hq_sat_answer 表**:保存具体的答案详情(v1.0.1 新增,v1.0.2 修复)
|
||||
- **单选题**:为选中的选项创建一条记录,包含选项 ID、选项标识(A/B/C/D)、选项文本、真实分值
|
||||
- **多选题**:为每个选中的选项创建一条记录,使用选项标识区分不同选项
|
||||
- **问答题**:创建一条记录,选项相关字段为 NULL,答案值存储用户输入的文本内容
|
||||
|
||||
**hq_sat_answer 表数据示例**(v1.0.2 修复后):
|
||||
|
||||
```sql
|
||||
-- 单选题答案记录(用户选择"非常满意"选项)
|
||||
INSERT INTO hq_sat_answer VALUES (
|
||||
1, '000000', 1001, NOW(), 1, NOW(), 0, -- 基础字段
|
||||
123, 1, '1', 1, 'A', '非常满意', 'A', 5, NULL -- 答案字段
|
||||
);
|
||||
|
||||
-- 多选题答案记录(用户选中A、C两个选项)
|
||||
INSERT INTO hq_sat_answer VALUES
|
||||
(2, '000000', 1001, NOW(), 1, NOW(), 0, 123, 2, '2', 1, 'A', '非常满意', 'A,C', 5, NULL),
|
||||
(3, '000000', 1001, NOW(), 1, NOW(), 0, 123, 2, '2', 3, 'C', '一般', 'A,C', 1, NULL);
|
||||
|
||||
-- 问答题答案记录
|
||||
INSERT INTO hq_sat_answer VALUES (
|
||||
4, '000000', 1001, NOW(), 1, NOW(), 0, -- 基础字段
|
||||
123, 3, '3', NULL, NULL, NULL, '这是我的意见和建议', 0, NULL -- 答案字段
|
||||
);
|
||||
```
|
||||
|
||||
**字段说明**(修复后):
|
||||
|
||||
- `option_value`: 存储选项标识(A、B、C、D),便于统计分析
|
||||
- `answer_score`: 存储真实分值(从 hq_sat_option 表查询获得)
|
||||
- `answer_value`: 单选题存选项标识,多选题存逗号分隔的多个标识,问答题存文本
|
||||
|
||||
#### 保存问卷答案草稿
|
||||
|
||||
**接口地址**: `POST /hq/satSurvey/answer/draft`
|
||||
|
||||
**接口描述**: 保存问卷答案草稿,可多次保存和修改
|
||||
|
||||
**权限要求**: 需要登录
|
||||
|
||||
**请求参数**: 同提交问卷答案
|
||||
|
||||
**响应示例**: 同提交问卷答案
|
||||
|
||||
### 2.2 答题状态查询
|
||||
|
||||
#### 检查用户是否已答过问卷
|
||||
|
||||
**接口地址**: `GET /hq/satSurvey/{surveyId}/answered`
|
||||
|
||||
**接口描述**: 检查当前用户是否已经答过指定问卷(v1.0.3 更新:支持多次提交检查)
|
||||
|
||||
**权限要求**: 需要登录
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| -------- | ---- | ---- | ------- |
|
||||
| surveyId | long | 是 | 问卷 ID |
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": true
|
||||
}
|
||||
```
|
||||
|
||||
**响应说明**(v1.0.3 更新):
|
||||
|
||||
- `data` 为 `true` 表示用户已答过该问卷(至少提交过 1 次)
|
||||
- `data` 为 `false` 表示用户未答过该问卷
|
||||
|
||||
**多次提交支持**:
|
||||
此接口仅返回用户是否有答题记录,不考虑提交次数限制。要检查是否还能继续提交,请参考以下逻辑:
|
||||
|
||||
- 如果 `max_submit_times = 0`:始终可以提交
|
||||
- 如果 `max_submit_times > 0`:需要检查用户提交次数是否小于此限制
|
||||
- 具体的提交限制检查由"提交问卷答案"接口在提交时进行
|
||||
|
||||
#### 获取用户问卷答题记录
|
||||
|
||||
**接口地址**: `GET /hq/satSurvey/{surveyId}/answer`
|
||||
|
||||
**接口描述**: 获取当前用户对指定问卷的答题记录(v1.0.5 重大更新:优化数据源,支持前端答案回显)
|
||||
|
||||
**权限要求**: 需要登录
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| -------- | ---- | ---- | ------- |
|
||||
| surveyId | long | 是 | 问卷 ID |
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": {
|
||||
"id": 1001,
|
||||
"surveyId": 1,
|
||||
"userId": 1,
|
||||
"userType": "student",
|
||||
"submitTime": "2025-01-26T14:15:00.000Z",
|
||||
"totalScore": 0,
|
||||
"avgScore": 0,
|
||||
"remark": "ANSWERS_DATA:[{\"questionId\":1,\"questionType\":\"1\",\"answerValue\":\"A\"},{\"questionId\":2,\"questionType\":\"2\",\"answerValue\":\"A,C\"}]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据说明**(v1.0.5 重大更新):
|
||||
|
||||
- 此接口只返回用户最新的有效答题记录(deleted=0)
|
||||
- 如果用户进行了多次提交,历史记录已被软删除,查询不到
|
||||
- 返回的是用户最后一次提交的完整答题信息
|
||||
- 历史记录完整保留在数据库中,仅用于审计目的
|
||||
|
||||
**v1.0.5 新增功能 - 答案回显数据**:
|
||||
|
||||
为支持前端答题界面的历史答案回显功能,此接口现在会在 `remark` 字段中包含格式化的答案数据:
|
||||
|
||||
1. **数据格式**: `ANSWERS_DATA:[答案JSON数组]`
|
||||
2. **数据来源**: 直接从 `hq_sat_answer` 表查询,比 v1.0.4 之前的 `remark` 解析更准确
|
||||
3. **答案结构**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"questionId": 1,
|
||||
"questionType": "1",
|
||||
"answerValue": "A"
|
||||
},
|
||||
{
|
||||
"questionId": 2,
|
||||
"questionType": "2",
|
||||
"answerValue": "A,C"
|
||||
},
|
||||
{
|
||||
"questionId": 3,
|
||||
"questionType": "3",
|
||||
"answerValue": "用户输入的文本内容"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**前端答案回显逻辑**:
|
||||
|
||||
- **单选题** (`questionType = "1"`): `answerValue` 直接设置到单选控件(如 "A")
|
||||
- **多选题** (`questionType = "2"`): `answerValue` 按逗号分割成数组(如 ["A", "C"])
|
||||
- **问答题** (`questionType = "3"`): `answerValue` 直接设置到文本框
|
||||
|
||||
**使用场景**:
|
||||
|
||||
- ✅ **首次答题**: 返回 404,前端正常显示空表单
|
||||
- ✅ **重新答题**: 返回历史答案,前端自动回显到表单
|
||||
- ✅ **多次修改**: 基于上次答案继续编辑,提升用户体验
|
||||
|
||||
---
|
||||
|
||||
## 3. 题目管理 API
|
||||
|
||||
### 3.1 题目基础管理
|
||||
|
||||
#### 查询题目列表
|
||||
|
||||
**接口地址**: `GET /hq/satQuestion/list`
|
||||
|
||||
**权限要求**: `hq:satQuestion:list`
|
||||
|
||||
#### 获取题目选项
|
||||
|
||||
**接口地址**: `GET /hq/satQuestion/{questionId}/options`
|
||||
|
||||
**接口描述**: 获取指定题目的所有选项
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| ---------- | ---- | ---- | ------- |
|
||||
| questionId | long | 是 | 题目 ID |
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"questionId": 1,
|
||||
"optionValue": "A",
|
||||
"optionText": "非常满意",
|
||||
"optionScore": 5,
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"questionId": 1,
|
||||
"optionValue": "B",
|
||||
"optionText": "满意",
|
||||
"optionScore": 4,
|
||||
"sortOrder": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 答题记录管理 API
|
||||
|
||||
### 4.1 答题记录查询
|
||||
|
||||
#### 查询答题记录列表
|
||||
|
||||
**接口地址**: `GET /hq/satRecord/list`
|
||||
|
||||
**权限要求**: `hq:satRecord:list`
|
||||
|
||||
#### 获取答题记录详情
|
||||
|
||||
**接口地址**: `GET /hq/satRecord/{id}`
|
||||
|
||||
**权限要求**: `hq:satRecord:query`
|
||||
|
||||
---
|
||||
|
||||
## 5. 错误码说明
|
||||
|
||||
### 业务错误码
|
||||
|
||||
| 错误码 | 说明 | 解决方案 |
|
||||
| ------ | ------------------ | ------------------------ |
|
||||
| 40001 | 问卷不存在 | 检查问卷 ID 是否正确 |
|
||||
| 40002 | 问卷未在进行中状态 | 等待问卷开始或联系管理员 |
|
||||
| 40003 | 您已经答过这份问卷 | 不能重复答题 |
|
||||
| 40004 | 答题记录保存失败 | 重新提交或联系技术支持 |
|
||||
| 40005 | 题目类型不支持 | 检查题目类型是否正确 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据字典
|
||||
|
||||
### 6.1 问卷状态
|
||||
|
||||
| 值 | 说明 | 备注 |
|
||||
| --- | ------ | -------------- |
|
||||
| 0 | 草稿 | 未发布状态 |
|
||||
| 1 | 进行中 | 已发布,可答题 |
|
||||
| 2 | 已结束 | 超过结束时间 |
|
||||
|
||||
### 6.2 目标群体
|
||||
|
||||
| 值 | 说明 | 备注 |
|
||||
| --- | ------ | ---------- |
|
||||
| 1 | 学生 | 在校学生 |
|
||||
| 2 | 教职工 | 教师和职工 |
|
||||
| 3 | 全体 | 所有人员 |
|
||||
|
||||
### 6.4 用户类型
|
||||
|
||||
| 值 | 说明 | 备注 |
|
||||
| --- | ---------- | --------------------------------------------------------- |
|
||||
| 1 | 学生 | student、学生、stu 等角色映射为此值 |
|
||||
| 2 | 教师 | teacher、老师、教师、教职工、tea 等角色映射为此值 |
|
||||
| 3 | 超级管理员 | admin、管理员、超级管理员、administrator 等角色映射为此值 |
|
||||
|
||||
### 6.3 题目类型
|
||||
|
||||
| 值 | 说明 | 备注 |
|
||||
| --- | ------ | ---------------- |
|
||||
| 1 | 单选题 | 只能选择一个选项 |
|
||||
| 2 | 多选题 | 可以选择多个选项 |
|
||||
| 3 | 问答题 | 文本输入 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 版本更新记录
|
||||
|
||||
### v1.0.5 (2025-01-26)
|
||||
|
||||
- 🚀 **重大更新**:实现答题界面历史答案回显功能,提升多次答题用户体验
|
||||
- 🔧 **数据源优化**:`GET /hq/satSurvey/{surveyId}/answer` 接口改用 hq_sat_answer 表直接查询
|
||||
- 📊 新增 `getAnswersFromSatAnswerTable()` 方法,提供更准确的答案数据聚合
|
||||
- ✨ 前端新增 `loadPreviousAnswers()` 方法,支持三种题型的智能答案回显
|
||||
- 🎯 智能用户体验:只在成功加载历史答案时显示提示,异常时优雅降级
|
||||
- 🛡️ 错误处理优化:404 错误不显示提示,历史答案加载失败不阻断答题流程
|
||||
|
||||
### v1.0.4 (2025-01-26)
|
||||
|
||||
- 🔄 **重要优化**:多次提交时实现历史数据软删除,确保用户只有一份有效答题记录
|
||||
- 🗃️ 保留历史数据用于审计,但业务查询只返回最新有效数据
|
||||
- ⚡ 优化查询性能,提交次数统计和答题记录查询只涉及有效数据
|
||||
- 🛡️ 增强数据一致性,通过 record_id 关联确保三表数据状态同步
|
||||
|
||||
### v1.0.3 (2025-01-26)
|
||||
|
||||
- 🚀 **新功能**:实现问卷多次提交功能,支持根据 max_submit_times 设置控制提交次数
|
||||
- 📊 优化提交次数检查逻辑,支持 0=无限制、N=最大 N 次的灵活配置
|
||||
- ✅ 保留完整的答题历史记录,每次提交创建新的答题记录
|
||||
- 🎯 增强用户体验,提供详细的提交次数限制提示信息
|
||||
|
||||
### v1.0.2 (2025-01-26)
|
||||
|
||||
- 🔧 **重要修复**:修复单选题和多选题答案未记录到 hq_sat_answer 表的问题
|
||||
- 🎯 修复前端选项绑定使用分值导致的数据类型不匹配问题
|
||||
- ✅ 优化答案保存逻辑,正确存储选项标识(A、B、C、D)和分值
|
||||
- 📊 增强答案数据查询,支持选项分值自动计算
|
||||
- 🐛 修复多选题答案保存逻辑错误
|
||||
|
||||
### v1.0.1 (2025-01-26)
|
||||
|
||||
- 🔧 修复答题提交时 user_type 字段长度问题
|
||||
- ✅ 新增用户角色到数字编码的转换逻辑
|
||||
- 📚 新增用户类型数据字典说明
|
||||
- 🆕 新增答案详情记录功能,同时保存到 hq_sat_answer 表
|
||||
- 📝 完善提交问卷答案接口的数据保存说明
|
||||
|
||||
### v1.0.0 (2025-01-26)
|
||||
|
||||
- ✅ 新增答题提交 API
|
||||
- ✅ 新增答题状态查询 API
|
||||
- ✅ 完善问卷管理 API
|
||||
- ✅ 新增题目选项查询 API
|
||||
- ✅ 建立完整的 API 文档体系
|
||||
|
||||
---
|
||||
|
||||
## 8. 开发说明
|
||||
|
||||
### 8.1 接口测试
|
||||
|
||||
推荐使用以下工具进行接口测试:
|
||||
|
||||
- **Postman**: 图形化接口测试工具
|
||||
- **Apifox**: 国产 API 管理工具
|
||||
- **curl**: 命令行工具
|
||||
|
||||
### 8.2 认证方式
|
||||
|
||||
所有需要权限的接口都需要在请求头中携带认证信息:
|
||||
|
||||
```http
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
### 8.3 时间格式
|
||||
|
||||
所有时间字段统一使用 ISO 8601 格式:
|
||||
|
||||
```
|
||||
2025-01-26T14:15:00.000Z
|
||||
```
|
||||
|
||||
### 8.4 分页参数
|
||||
|
||||
所有分页接口统一使用以下参数:
|
||||
|
||||
- `pageNum`: 页码,从 1 开始
|
||||
- `pageSize`: 页大小,默认 10
|
||||
|
||||
---
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有 API 相关问题,请联系开发团队或查看项目仓库的 Issues。
|
||||
@@ -143,7 +143,7 @@ CREATE TABLE `hq_sat_record` (
|
||||
`deleted` tinyint(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
`survey_id` bigint(20) NOT NULL COMMENT '问卷ID',
|
||||
`user_id` bigint(20) DEFAULT NULL COMMENT '答题用户ID(匿名时为NULL)',
|
||||
`user_type` char(1) NOT NULL COMMENT '用户类型(1学生 2教职工)',
|
||||
`user_type` char(1) NOT NULL COMMENT '用户类型(1学生 2教职工 3超级管理员)',
|
||||
`user_dept` varchar(100) DEFAULT NULL COMMENT '用户部门(匿名时为NULL)',
|
||||
`submit_time` datetime NOT NULL COMMENT '提交时间',
|
||||
`total_score` decimal(10,2) DEFAULT '0.00' COMMENT '总分',
|
||||
@@ -180,6 +180,73 @@ CREATE TABLE `hq_sat_detail` (
|
||||
KEY `idx_answer_score` (`answer_score`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='答题详情表';
|
||||
|
||||
-- ==========================================
|
||||
-- 答题答案表(新增)
|
||||
-- ==========================================
|
||||
DROP TABLE IF EXISTS `hq_sat_answer`;
|
||||
CREATE TABLE `hq_sat_answer` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
`record_id` bigint(20) NOT NULL COMMENT '答题记录ID',
|
||||
`question_id` bigint(20) NOT NULL COMMENT '题目ID',
|
||||
`question_type` char(1) NOT NULL COMMENT '题目类型(1单选题 2多选题 3问答题)',
|
||||
`option_id` bigint(20) DEFAULT NULL COMMENT '选项ID(单选题、多选题用,问答题为NULL)',
|
||||
`option_value` varchar(50) DEFAULT NULL COMMENT '选项值(A、B、C、D等)',
|
||||
`option_text` varchar(500) DEFAULT NULL COMMENT '选项文本内容',
|
||||
`answer_value` text COMMENT '答案值(单选题存选项值,多选题存逗号分隔选项值,问答题存文本内容)',
|
||||
`answer_score` int(11) DEFAULT '0' COMMENT '该答案得分',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_record_id` (`record_id`),
|
||||
KEY `idx_question_id` (`question_id`),
|
||||
KEY `idx_question_type` (`question_type`),
|
||||
KEY `idx_option_id` (`option_id`),
|
||||
KEY `idx_record_question` (`record_id`, `question_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='答题答案表';
|
||||
|
||||
-- ==========================================
|
||||
-- 数据表索引和约束说明
|
||||
-- ==========================================
|
||||
/*
|
||||
hq_sat_answer 表设计说明:
|
||||
|
||||
1. 表用途:
|
||||
- 记录用户答题的具体答案内容
|
||||
- 支持单选题、多选题、问答题三种题型
|
||||
- 与 hq_sat_detail 表配合使用,detail表记录总体信息,answer表记录具体答案
|
||||
|
||||
2. 字段说明:
|
||||
- question_type:题目类型,便于按类型统计分析
|
||||
- option_id:选项ID,单选题和多选题需要关联到具体选项
|
||||
- option_value:选项值(如A、B、C、D),便于统计
|
||||
- option_text:选项文本,冗余存储便于查询
|
||||
- answer_value:核心字段,存储实际答案:
|
||||
* 单选题:存储选中的选项值,如 "A"
|
||||
* 多选题:存储多个选项值,如 "A,C,D"
|
||||
* 问答题:存储用户输入的文本内容
|
||||
- answer_score:该答案的得分
|
||||
|
||||
3. 数据存储示例:
|
||||
- 单选题:question_type='1', option_id=123, option_value='A', option_text='非常满意', answer_value='A', answer_score=5
|
||||
- 多选题:每个选中选项一条记录,或合并为一条记录存储 "A,C,D"
|
||||
- 问答题:question_type='3', option_id=NULL, answer_value='用户输入的文本内容', answer_score=0
|
||||
|
||||
4. 与其他表的关系:
|
||||
- record_id 关联 hq_sat_record.id
|
||||
- question_id 关联 hq_sat_question.id
|
||||
- option_id 关联 hq_sat_option.id(问答题为NULL)
|
||||
|
||||
5. 查询优化:
|
||||
- 建立复合索引 (record_id, question_id) 提高关联查询效率
|
||||
- 按题目类型、选项等建立索引便于统计分析
|
||||
*/
|
||||
|
||||
-- ==========================================================================
|
||||
-- 初始化数据
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { SatSurveyVO, SatSurveyForm, SatSurveyQuery } from '@/api/hq/answerSurvey/types';
|
||||
import {
|
||||
SatSurveyVO,
|
||||
SatSurveyForm,
|
||||
SatSurveyQuery,
|
||||
SubmitSurveyAnswerForm,
|
||||
SatRecordVO,
|
||||
SurveyStatisticsVO
|
||||
} from '@/api/hq/answerSurvey/types';
|
||||
|
||||
/**
|
||||
* 查询问卷列表
|
||||
@@ -134,7 +141,7 @@ export const getUserSurveyAnswer = (surveyId: string | number): AxiosPromise<any
|
||||
* 提交问卷答案
|
||||
* @param data 答案数据
|
||||
*/
|
||||
export const submitSurveyAnswer = (data: any) => {
|
||||
export const submitSurveyAnswer = (data: SubmitSurveyAnswerForm): AxiosPromise<SatRecordVO> => {
|
||||
return request({
|
||||
url: '/hq/satSurvey/answer/submit',
|
||||
method: 'post',
|
||||
@@ -146,10 +153,56 @@ export const submitSurveyAnswer = (data: any) => {
|
||||
* 保存问卷答案草稿
|
||||
* @param data 答案数据
|
||||
*/
|
||||
export const saveSurveyDraft = (data: any) => {
|
||||
export const saveSurveyDraft = (data: SubmitSurveyAnswerForm): AxiosPromise<SatRecordVO> => {
|
||||
return request({
|
||||
url: '/hq/satSurvey/answer/draft',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取问卷统计信息
|
||||
* @param surveyId 问卷ID
|
||||
*/
|
||||
export const getSurveyStatistics = (surveyId: string | number): AxiosPromise<SurveyStatisticsVO> => {
|
||||
return request({
|
||||
url: '/hq/satSurvey/' + surveyId + '/statistics',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户答题记录详情
|
||||
* @param recordId 答题记录ID
|
||||
*/
|
||||
export const getAnswerRecord = (recordId: string | number): AxiosPromise<SatRecordVO> => {
|
||||
return request({
|
||||
url: '/hq/satSurvey/answer/record/' + recordId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取问卷的所有答题记录列表
|
||||
* @param surveyId 问卷ID
|
||||
* @param query 查询参数
|
||||
*/
|
||||
export const getSurveyAnswerRecords = (surveyId: string | number, query?: any): AxiosPromise<SatRecordVO[]> => {
|
||||
return request({
|
||||
url: '/hq/satSurvey/' + surveyId + '/records',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除答题记录
|
||||
* @param recordId 答题记录ID
|
||||
*/
|
||||
export const deleteAnswerRecord = (recordId: string | number) => {
|
||||
return request({
|
||||
url: '/hq/satSurvey/answer/record/' + recordId,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
@@ -212,5 +212,279 @@ export interface SatSurveyQuery extends PageQuery {
|
||||
params?: any;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 答题相关类型定义
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* 答题答案VO
|
||||
*/
|
||||
export interface SatAnswerVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 答题记录ID
|
||||
*/
|
||||
recordId: string | number;
|
||||
|
||||
/**
|
||||
* 题目ID
|
||||
*/
|
||||
questionId: string | number;
|
||||
|
||||
/**
|
||||
* 题目类型(1单选题 2多选题 3问答题)
|
||||
*/
|
||||
questionType: string;
|
||||
|
||||
/**
|
||||
* 选项ID(单选题、多选题用,问答题为NULL)
|
||||
*/
|
||||
optionId?: string | number;
|
||||
|
||||
/**
|
||||
* 选项值(A、B、C、D等)
|
||||
*/
|
||||
optionValue?: string;
|
||||
|
||||
/**
|
||||
* 选项文本内容
|
||||
*/
|
||||
optionText?: string;
|
||||
|
||||
/**
|
||||
* 答案值(单选题存选项值,多选题存逗号分隔选项值,问答题存文本内容)
|
||||
*/
|
||||
answerValue: string;
|
||||
|
||||
/**
|
||||
* 该答案得分
|
||||
*/
|
||||
answerScore: number;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 答题记录VO
|
||||
*/
|
||||
export interface SatRecordVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 问卷ID
|
||||
*/
|
||||
surveyId: string | number;
|
||||
|
||||
/**
|
||||
* 答题人ID
|
||||
*/
|
||||
userId: string | number;
|
||||
|
||||
/**
|
||||
* 答题人姓名
|
||||
*/
|
||||
userName?: string;
|
||||
|
||||
/**
|
||||
* 答题开始时间
|
||||
*/
|
||||
startTime: string;
|
||||
|
||||
/**
|
||||
* 答题结束时间
|
||||
*/
|
||||
endTime?: string;
|
||||
|
||||
/**
|
||||
* 答题用时(秒)
|
||||
*/
|
||||
duration?: number;
|
||||
|
||||
/**
|
||||
* 总得分
|
||||
*/
|
||||
totalScore: number;
|
||||
|
||||
/**
|
||||
* 答题状态(0草稿 1已提交)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 是否匿名
|
||||
*/
|
||||
isAnonymous: number;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 答题详情列表
|
||||
*/
|
||||
answers?: SatAnswerVO[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交答题的单个答案
|
||||
*/
|
||||
export interface SubmitAnswerItem {
|
||||
/**
|
||||
* 题目ID
|
||||
*/
|
||||
questionId: string | number;
|
||||
|
||||
/**
|
||||
* 题目类型(1单选题 2多选题 3问答题)
|
||||
*/
|
||||
questionType: string;
|
||||
|
||||
/**
|
||||
* 答案值
|
||||
* - 单选题:选项值,如 "A"
|
||||
* - 多选题:多个选项值,如 "A,C,D"
|
||||
* - 问答题:用户输入的文本内容
|
||||
*/
|
||||
answerValue: string;
|
||||
|
||||
/**
|
||||
* 选项详情(单选题和多选题需要)
|
||||
*/
|
||||
options?: Array<{
|
||||
optionId: string | number;
|
||||
optionValue: string;
|
||||
optionText: string;
|
||||
isSelected: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交答题请求数据
|
||||
*/
|
||||
export interface SubmitSurveyAnswerForm {
|
||||
/**
|
||||
* 问卷ID
|
||||
*/
|
||||
surveyId: string | number;
|
||||
|
||||
/**
|
||||
* 答题开始时间
|
||||
*/
|
||||
startTime: string;
|
||||
|
||||
/**
|
||||
* 答题结束时间
|
||||
*/
|
||||
endTime: string;
|
||||
|
||||
/**
|
||||
* 是否匿名(1是 0否)
|
||||
*/
|
||||
isAnonymous: number;
|
||||
|
||||
/**
|
||||
* 答案列表
|
||||
*/
|
||||
answers: SubmitAnswerItem[];
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 答题统计信息
|
||||
*/
|
||||
export interface SurveyStatisticsVO {
|
||||
/**
|
||||
* 问卷ID
|
||||
*/
|
||||
surveyId: string | number;
|
||||
|
||||
/**
|
||||
* 问卷标题
|
||||
*/
|
||||
surveyTitle: string;
|
||||
|
||||
/**
|
||||
* 总参与人数
|
||||
*/
|
||||
totalParticipants: number;
|
||||
|
||||
/**
|
||||
* 已完成人数
|
||||
*/
|
||||
completedCount: number;
|
||||
|
||||
/**
|
||||
* 草稿数量
|
||||
*/
|
||||
draftCount: number;
|
||||
|
||||
/**
|
||||
* 平均用时(秒)
|
||||
*/
|
||||
avgDuration: number;
|
||||
|
||||
/**
|
||||
* 平均得分
|
||||
*/
|
||||
avgScore: number;
|
||||
|
||||
/**
|
||||
* 题目统计详情
|
||||
*/
|
||||
questionStats?: QuestionStatItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 题目统计项
|
||||
*/
|
||||
export interface QuestionStatItem {
|
||||
/**
|
||||
* 题目ID
|
||||
*/
|
||||
questionId: string | number;
|
||||
|
||||
/**
|
||||
* 题目标题
|
||||
*/
|
||||
questionTitle: string;
|
||||
|
||||
/**
|
||||
* 题目类型
|
||||
*/
|
||||
questionType: string;
|
||||
|
||||
/**
|
||||
* 回答人数
|
||||
*/
|
||||
answerCount: number;
|
||||
|
||||
/**
|
||||
* 选项统计(单选题、多选题)
|
||||
*/
|
||||
optionStats?: Array<{
|
||||
optionId: string | number;
|
||||
optionText: string;
|
||||
selectCount: number;
|
||||
selectRate: number;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* 问答题统计
|
||||
*/
|
||||
textAnswers?: string[];
|
||||
}
|
||||
@@ -127,7 +127,7 @@ const dynamicTenantEvent = async (tenantId: string) => {
|
||||
if (companyName.value != null && companyName.value !== '') {
|
||||
await dynamicTenant(tenantId);
|
||||
dynamic.value = true;
|
||||
await proxy?.$router.push('/');
|
||||
await router.push('/');
|
||||
await proxy?.$tab.closeAllPage();
|
||||
await proxy?.$tab.refreshPage();
|
||||
}
|
||||
@@ -136,7 +136,7 @@ const dynamicTenantEvent = async (tenantId: string) => {
|
||||
const dynamicClearEvent = async () => {
|
||||
await dynamicClear();
|
||||
dynamic.value = false;
|
||||
await proxy?.$router.push('/');
|
||||
await router.push('/');
|
||||
await proxy?.$tab.closeAllPage();
|
||||
await proxy?.$tab.refreshPage();
|
||||
};
|
||||
@@ -288,10 +288,18 @@ watch(
|
||||
|
||||
.user-avatar {
|
||||
cursor: pointer;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 8px;
|
||||
width: 30px !important;
|
||||
height: 30px !important;
|
||||
min-width: 30px;
|
||||
min-height: 30px;
|
||||
max-width: 30px;
|
||||
max-height: 30px;
|
||||
border-radius: 6px;
|
||||
margin-top: 10px;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
i {
|
||||
|
||||
@@ -153,6 +153,10 @@ service.interceptors.response.use(
|
||||
} else if (code === HttpStatus.WARN) {
|
||||
ElMessage({ message: msg, type: 'warning' });
|
||||
return Promise.reject(new Error(msg));
|
||||
} else if (code === 404) {
|
||||
// 对404错误进行特殊处理,不显示错误提示
|
||||
console.log('404错误被静默处理,不显示用户提示');
|
||||
return Promise.reject(new Error('404'));
|
||||
} else if (code !== HttpStatus.SUCCESS) {
|
||||
ElNotification.error({ title: msg });
|
||||
return Promise.reject('error');
|
||||
@@ -167,7 +171,13 @@ service.interceptors.response.use(
|
||||
} else if (message.includes('timeout')) {
|
||||
message = '系统接口请求超时';
|
||||
} else if (message.includes('Request failed with status code')) {
|
||||
message = '系统接口' + message.substr(message.length - 3) + '异常';
|
||||
const statusCode = message.substr(message.length - 3);
|
||||
// 对404错误进行特殊处理,不显示错误提示
|
||||
if (statusCode === '404') {
|
||||
console.log('404错误被静默处理,不显示用户提示');
|
||||
return Promise.reject(error);
|
||||
}
|
||||
message = '系统接口' + statusCode + '异常';
|
||||
}
|
||||
ElMessage({ message: message, type: 'error', duration: 5 * 1000 });
|
||||
return Promise.reject(error);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user