diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..8f2b7113 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.compile.nullAnalysis.mode": "disabled" +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/psychology/qrcode.js b/ruoyi-ui/src/api/psychology/qrcode.js index 2167c888..283e4f95 100644 --- a/ruoyi-ui/src/api/psychology/qrcode.js +++ b/ruoyi-ui/src/api/psychology/qrcode.js @@ -51,3 +51,46 @@ export function regenerateQrcode(qrcodeId) { }) } +// 扫描二维码(公开接口) +export function scanQrcode(qrcodeCode) { + return request({ + url: '/psychology/qrcode/scan/' + qrcodeCode, + method: 'get' + }) +} + +// 生成量表测评二维码 +export function generateScaleQrcode(scaleId) { + return request({ + url: '/psychology/qrcode/generate/scale', + method: 'post', + params: { scaleId: scaleId } + }) +} + +// 生成报告查看二维码 +export function generateReportQrcode(reportId) { + return request({ + url: '/psychology/qrcode/generate/report', + method: 'post', + params: { reportId: reportId } + }) +} + +// 生成测评查看报告二维码 +export function generateAssessmentQrcode(assessmentId) { + return request({ + url: '/psychology/qrcode/generate/assessment', + method: 'post', + params: { assessmentId: assessmentId } + }) +} + +// 获取二维码图片(Base64) +export function getQrcodeImage(qrcodeId) { + return request({ + url: '/psychology/qrcode/image/' + qrcodeId, + method: 'get' + }) +} + diff --git a/ruoyi-ui/src/api/psychology/questionnaireAnswer.js b/ruoyi-ui/src/api/psychology/questionnaireAnswer.js new file mode 100644 index 00000000..e3e464aa --- /dev/null +++ b/ruoyi-ui/src/api/psychology/questionnaireAnswer.js @@ -0,0 +1,69 @@ +import request from '@/utils/request' + +// 查询问卷答题列表 +export function listQuestionnaireAnswer(query) { + return request({ + url: '/psychology/questionnaire/answer/list', + method: 'get', + params: query + }) +} + +// 查询问卷答题详细 +export function getQuestionnaireAnswer(answerId) { + return request({ + url: '/psychology/questionnaire/answer/' + answerId, + method: 'get' + }) +} + +// 开始问卷答题 +export function startQuestionnaireAnswer(data) { + return request({ + url: '/psychology/questionnaire/answer/start', + method: 'post', + data: data + }) +} + +// 获取问卷题目列表 +export function getQuestionnaireItems(questionnaireId) { + return request({ + url: '/psychology/questionnaire/answer/items/' + questionnaireId, + method: 'get' + }) +} + +// 获取答题答案详情列表 +export function getAnswerDetails(answerId) { + return request({ + url: '/psychology/questionnaire/answer/details/' + answerId, + method: 'get' + }) +} + +// 保存答案 +export function saveQuestionnaireAnswer(data) { + return request({ + url: '/psychology/questionnaire/answer/save', + method: 'post', + data: data + }) +} + +// 提交问卷 +export function submitQuestionnaireAnswer(answerId) { + return request({ + url: '/psychology/questionnaire/answer/submit/' + answerId, + method: 'post' + }) +} + +// 获取问卷成绩排名列表 +export function getQuestionnaireRankList(questionnaireId) { + return request({ + url: '/psychology/questionnaire/answer/rank/' + questionnaireId, + method: 'get' + }) +} + diff --git a/ruoyi-ui/src/api/psychology/questionnaireItem.js b/ruoyi-ui/src/api/psychology/questionnaireItem.js new file mode 100644 index 00000000..60e0986d --- /dev/null +++ b/ruoyi-ui/src/api/psychology/questionnaireItem.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询问卷题目列表 +export function listQuestionnaireItem(questionnaireId) { + return request({ + url: '/psychology/questionnaire/item/list/' + questionnaireId, + method: 'get' + }) +} + +// 查询题目详细 +export function getQuestionnaireItem(itemId) { + return request({ + url: '/psychology/questionnaire/item/' + itemId, + method: 'get' + }) +} + +// 新增题目 +export function addQuestionnaireItem(data) { + return request({ + url: '/psychology/questionnaire/item', + method: 'post', + data: data + }) +} + +// 修改题目 +export function updateQuestionnaireItem(data) { + return request({ + url: '/psychology/questionnaire/item', + method: 'put', + data: data + }) +} + +// 删除题目 +export function delQuestionnaireItem(itemIds) { + return request({ + url: '/psychology/questionnaire/item/' + itemIds, + method: 'delete' + }) +} + diff --git a/ruoyi-ui/src/api/psychology/questionnaireOption.js b/ruoyi-ui/src/api/psychology/questionnaireOption.js new file mode 100644 index 00000000..bae75256 --- /dev/null +++ b/ruoyi-ui/src/api/psychology/questionnaireOption.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询问卷选项列表 +export function listQuestionnaireOption(itemId) { + return request({ + url: '/psychology/questionnaire/option/list/' + itemId, + method: 'get' + }) +} + +// 查询问卷选项详细 +export function getQuestionnaireOption(optionId) { + return request({ + url: '/psychology/questionnaire/option/' + optionId, + method: 'get' + }) +} + +// 新增问卷选项 +export function addQuestionnaireOption(data) { + return request({ + url: '/psychology/questionnaire/option', + method: 'post', + data: data + }) +} + +// 修改问卷选项 +export function updateQuestionnaireOption(data) { + return request({ + url: '/psychology/questionnaire/option', + method: 'put', + data: data + }) +} + +// 删除问卷选项 +export function delQuestionnaireOption(optionId) { + return request({ + url: '/psychology/questionnaire/option/' + optionId, + method: 'delete' + }) +} + diff --git a/ruoyi-ui/src/api/psychology/report.js b/ruoyi-ui/src/api/psychology/report.js index 4a8577d4..6ae13eea 100644 --- a/ruoyi-ui/src/api/psychology/report.js +++ b/ruoyi-ui/src/api/psychology/report.js @@ -10,9 +10,10 @@ export function listReport(query) { } // 查询报告详细 -export function getReport(reportId) { +export function getReport(reportId, sourceType) { + const url = '/psychology/report/' + reportId + (sourceType ? '?sourceType=' + sourceType : ''); return request({ - url: '/psychology/report/' + reportId, + url: url, method: 'get' }) } @@ -59,3 +60,18 @@ export function generateReport(assessmentId) { }) } +// 导出报告(Excel格式) +export function exportReport(reportIds, queryParams) { + // 构建参数对象 + const params = { ...queryParams } + if (reportIds && reportIds.length > 0) { + params.reportIds = reportIds.join(',') + } + return request({ + url: '/psychology/report/export', + method: 'post', + params: params, + responseType: 'blob' + }) +} + diff --git a/ruoyi-ui/src/api/psychology/scale.js b/ruoyi-ui/src/api/psychology/scale.js index 1da0ff47..d4e7b852 100644 --- a/ruoyi-ui/src/api/psychology/scale.js +++ b/ruoyi-ui/src/api/psychology/scale.js @@ -94,3 +94,15 @@ export function extractText(file) { }) } +// 导出量表(JSON格式) +export function exportScale(scaleIds) { + // 使用download方法处理文件下载,参数通过URL传递 + const params = scaleIds && scaleIds.length > 0 ? { scaleIds: scaleIds.join(',') } : {} + return request({ + url: '/psychology/scale/export', + method: 'post', + params: params, + responseType: 'blob' + }) +} + diff --git a/ruoyi-ui/src/router/index.js b/ruoyi-ui/src/router/index.js index 09df4f03..a22556f3 100644 --- a/ruoyi-ui/src/router/index.js +++ b/ruoyi-ui/src/router/index.js @@ -51,6 +51,11 @@ export const constantRoutes = [ component: () => import('@/views/register'), hidden: true }, + { + path: '/psychology/qrcode/scan/:qrcodeCode', + component: () => import('@/views/psychology/qrcode/scan'), + hidden: true + }, { path: '/404', component: () => import('@/views/error/404'), @@ -342,14 +347,47 @@ export const dynamicRoutes = [ roles: ['admin'] } }, - // 问卷管理 + // 自定义问卷 { path: 'questionnaire', name: 'Questionnaire', component: () => import('@/views/psychology/questionnaire/index'), meta: { - title: '问卷管理', - icon: 'copy', + title: '自定义问卷', + icon: 'edit', + roles: ['admin'] + } + }, + // 问卷开始答题 + { + path: 'questionnaire/start', + name: 'QuestionnaireStart', + component: () => import('@/views/psychology/questionnaire/start'), + hidden: true, + meta: { + title: '开始答题', + roles: ['admin', 'common'] + } + }, + // 问卷答题 + { + path: 'questionnaire/taking', + name: 'QuestionnaireTaking', + component: () => import('@/views/psychology/questionnaire/taking'), + hidden: true, + meta: { + title: '问卷答题', + roles: ['admin', 'common'] + } + }, + // 问卷题目管理 + { + path: 'questionnaire/item', + name: 'QuestionnaireItem', + component: () => import('@/views/psychology/questionnaire/item'), + hidden: true, + meta: { + title: '问卷题目管理', roles: ['admin'] } } diff --git a/ruoyi-ui/src/utils/request.js b/ruoyi-ui/src/utils/request.js index 7150ecb1..520fa71d 100644 --- a/ruoyi-ui/src/utils/request.js +++ b/ruoyi-ui/src/utils/request.js @@ -79,6 +79,8 @@ service.interceptors.response.use(res => { const msg = errorCode[code] || res.data.msg || errorCode['default'] // 二进制数据则直接返回 if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { + // 对于blob类型,直接返回blob数据 + // 错误处理在error拦截器中处理 return res.data } if (code === 401) { @@ -107,9 +109,23 @@ service.interceptors.response.use(res => { return res.data } }, - error => { + async error => { console.log('err' + error) let { message } = error + // 对于blob类型的错误响应,尝试解析错误信息 + if (error.response && error.response.config && + (error.response.config.responseType === 'blob' || error.response.config.responseType === 'arraybuffer') && + error.response.data instanceof Blob) { + try { + const text = await error.response.data.text() + const errorObj = JSON.parse(text) + message = errorObj.error || errorObj.msg || '导出失败' + // 对于文件导出错误,不自动显示Message,让调用方处理 + return Promise.reject(new Error(message)) + } catch (e) { + // 解析失败,使用默认错误信息 + } + } if (message == "Network Error") { message = "后端接口连接异常" } else if (message.includes("timeout")) { @@ -117,8 +133,13 @@ service.interceptors.response.use(res => { } else if (message.includes("Request failed with status code")) { message = "系统接口" + message.substr(message.length - 3) + "异常" } - Message({ message: message, type: 'error', duration: 5 * 1000 }) - return Promise.reject(error) + // 对于文件导出错误,不自动显示Message,让调用方处理 + const isExportError = error.response && error.response.config && + (error.response.config.responseType === 'blob' || error.response.config.responseType === 'arraybuffer') + if (!isExportError) { + Message({ message: message, type: 'error', duration: 5 * 1000 }) + } + return Promise.reject(new Error(message)) } ) diff --git a/ruoyi-ui/src/views/psychology/assessment/start.vue b/ruoyi-ui/src/views/psychology/assessment/start.vue index 18aa5077..06e2cd44 100644 --- a/ruoyi-ui/src/views/psychology/assessment/start.vue +++ b/ruoyi-ui/src/views/psychology/assessment/start.vue @@ -2,28 +2,32 @@
- 选择量表开始测评 + 选择量表或问卷开始测评 返回
- - + + - {{ scale.scaleName }} + + 问卷 + 量表 + {{ scale.scaleName }} + {{ scale.itemCount }}题 - 选择被测评人 + 选择被测评人(仅量表需要) - - + + @@ -38,10 +42,13 @@ +
+ 提示:选择问卷时不需要选择用户档案 +
- 开始测评 + 开始测评/答题 取消 管理用户档案 @@ -94,7 +101,7 @@ export default { }, rules: { scaleId: [ - { required: true, message: "请选择量表", trigger: "change" } + { required: true, message: "请选择量表或问卷", trigger: "change" } ], profileId: [ { required: true, message: "请从存档用户中选择", trigger: "change" } @@ -103,6 +110,11 @@ export default { }; }, created() { + // 检查URL参数中是否有scaleId + const scaleId = this.$route.query.scaleId; + if (scaleId) { + this.form.scaleId = parseInt(scaleId); + } this.loadScales(); this.loadProfiles(); this.loadPaused(); @@ -117,23 +129,47 @@ export default { // 判断是否是管理员:userId === 1 或者 roles 中包含 'admin' const isAdmin = userId === 1 || (roles && roles.includes('admin')); - // 如果是管理员,显示所有量表;否则只显示有权限的量表 + // 如果是管理员,显示所有量表和问卷;否则只显示有权限的量表 if (isAdmin) { - // 管理员显示所有量表 - listScale({ status: '0' }).then(response => { + // 管理员显示所有量表和问卷 + listScale({ status: '0', includeQuestionnaire: true }).then(response => { this.scaleList = response.rows.filter(scale => scale.itemCount > 0); + // 如果URL中有scaleId,检查是否是问卷 + if (this.form.scaleId) { + const selectedScale = this.scaleList.find(s => s.scaleId === this.form.scaleId); + if (selectedScale && selectedScale.sourceType === 'questionnaire') { + // 如果是问卷,直接跳转到问卷开始页面 + const questionnaireId = selectedScale.originalId || Math.abs(selectedScale.scaleId); + this.$router.replace({ + path: '/psychology/questionnaire/start', + query: { questionnaireId: questionnaireId } + }); + } + } }).catch(error => { console.error("加载量表列表失败:", error); this.$message.error('加载量表列表失败,请稍后重试'); }); } else { - // 普通用户只显示有权限的量表 + // 普通用户只显示有权限的量表(问卷不需要权限,也可以显示) if (!userId || isNaN(userId) || userId <= 0) { console.error("用户ID无效:", userId); - // 如果userId无效,默认显示所有量表(可能是临时会话问题) - console.warn("用户ID无效,尝试加载所有量表"); - listScale({ status: '0' }).then(response => { + // 如果userId无效,默认显示所有量表和问卷(可能是临时会话问题) + console.warn("用户ID无效,尝试加载所有量表和问卷"); + listScale({ status: '0', includeQuestionnaire: true }).then(response => { this.scaleList = response.rows.filter(scale => scale.itemCount > 0); + // 如果URL中有scaleId,检查是否是问卷 + if (this.form.scaleId) { + const selectedScale = this.scaleList.find(s => s.scaleId === this.form.scaleId); + if (selectedScale && selectedScale.sourceType === 'questionnaire') { + // 如果是问卷,直接跳转到问卷开始页面 + const questionnaireId = selectedScale.originalId || Math.abs(selectedScale.scaleId); + this.$router.replace({ + path: '/psychology/questionnaire/start', + query: { questionnaireId: questionnaireId } + }); + } + } }).catch(error => { console.error("加载量表列表失败:", error); this.$message.error('加载量表列表失败,请稍后重试'); @@ -143,16 +179,30 @@ export default { import('@/api/psychology/permission').then(module => { module.getUserScaleIds(userId).then(permissionResponse => { const allowedScaleIds = permissionResponse.data || []; - if (allowedScaleIds.length === 0) { - this.scaleList = []; - this.$message.warning('您还没有被分配任何量表权限,请联系管理员'); - return; - } - // 获取所有量表,然后过滤 - listScale({ status: '0' }).then(response => { - this.scaleList = response.rows.filter(scale => - scale.itemCount > 0 && allowedScaleIds.includes(scale.scaleId) - ); + // 获取所有量表和问卷,然后过滤 + listScale({ status: '0', includeQuestionnaire: true }).then(response => { + this.scaleList = response.rows.filter(scale => { + if (scale.itemCount === 0) return false; + // 问卷不需要权限,直接显示 + if (scale.sourceType === 'questionnaire') return true; + // 量表需要检查权限 + return allowedScaleIds.includes(scale.scaleId); + }); + if (this.scaleList.length === 0) { + this.$message.warning('您还没有被分配任何量表权限,请联系管理员'); + } + // 如果URL中有scaleId,检查是否是问卷 + if (this.form.scaleId) { + const selectedScale = this.scaleList.find(s => s.scaleId === this.form.scaleId); + if (selectedScale && selectedScale.sourceType === 'questionnaire') { + // 如果是问卷,直接跳转到问卷开始页面 + const questionnaireId = selectedScale.originalId || Math.abs(selectedScale.scaleId); + this.$router.replace({ + path: '/psychology/questionnaire/start', + query: { questionnaireId: questionnaireId } + }); + } + } }).catch(error => { console.error("加载量表列表失败:", error); this.$message.error('加载量表列表失败,请稍后重试'); @@ -191,43 +241,85 @@ export default { this.pausedList = response.data || []; }); }, + /** 获取用户档案验证规则(动态) */ + getProfileRules() { + // 检查选中的是否是问卷 + const selectedScale = this.scaleList.find(s => s.scaleId === this.form.scaleId); + if (selectedScale && selectedScale.sourceType === 'questionnaire') { + // 问卷不需要用户档案 + return []; + } + // 量表需要用户档案 + return [ + { required: true, message: "请从存档用户中选择", trigger: "change" } + ]; + }, /** 提交表单 */ submitForm() { - this.$refs["form"].validate(valid => { - if (valid) { - this.loading = true; - // 找到选中的用户档案 - const selectedProfile = this.profileList.find(p => p.profileId === this.form.profileId); - if (selectedProfile) { - // 构建测评表单数据 - const assessmentData = { - scaleId: this.form.scaleId, - // 移除profileId,后端AssessmentStartVO不需要这个字段 - assesseeName: selectedProfile.userName, - // 从userName等信息中提取性别和年龄,或使用默认值 - assesseeGender: '0', // 默认性别 - assesseeAge: 0, // 默认年龄 - assesseePhone: selectedProfile.phone, - anonymous: false // 添加anonymous字段,设置为非匿名 - }; - - startAssessment(assessmentData).then(response => { - if (response.code === 200) { - this.$modal.msgSuccess("测评已开始"); - const assessmentId = response.data; - // 确保使用正确的路由路径 - this.$router.push({ path: '/psychology/assessment/taking', query: { assessmentId: assessmentId } }); - } - this.loading = false; - }).catch(error => { - console.error('Failed to start assessment:', error); - this.$modal.msgError("开始测评失败,请重试"); - this.loading = false; - }); - } else { - this.$modal.msgError("请选择有效的用户档案"); + // 先检查是否选择了量表或问卷 + if (!this.form.scaleId) { + this.$modal.msgError("请选择量表或问卷"); + return; + } + + // 检查选中的是量表还是问卷 + const selectedScale = this.scaleList.find(s => s.scaleId === this.form.scaleId); + if (!selectedScale) { + this.$modal.msgError("请选择有效的量表或问卷"); + return; + } + + // 如果是问卷,直接跳转到问卷答题页面(不需要用户档案) + if (selectedScale.sourceType === 'questionnaire') { + const questionnaireId = selectedScale.originalId || Math.abs(selectedScale.scaleId); + // 问卷不需要用户档案,直接跳转到问卷开始页面 + this.$router.push({ + path: '/psychology/questionnaire/start', + query: { questionnaireId: questionnaireId } + }); + return; + } + + // 如果是量表,需要验证用户档案 + this.$refs["form"].validateField('profileId', (error) => { + if (error) { + this.$modal.msgError("请选择用户档案"); + return; + } + + // 验证通过,创建测评记录 + this.loading = true; + // 找到选中的用户档案 + const selectedProfile = this.profileList.find(p => p.profileId === this.form.profileId); + if (selectedProfile) { + // 构建测评表单数据 + const assessmentData = { + scaleId: this.form.scaleId, + // 移除profileId,后端AssessmentStartVO不需要这个字段 + assesseeName: selectedProfile.userName, + // 从userName等信息中提取性别和年龄,或使用默认值 + assesseeGender: '0', // 默认性别 + assesseeAge: 0, // 默认年龄 + assesseePhone: selectedProfile.phone, + anonymous: false // 添加anonymous字段,设置为非匿名 + }; + + startAssessment(assessmentData).then(response => { + if (response.code === 200) { + this.$modal.msgSuccess("测评已开始"); + const assessmentId = response.data; + // 确保使用正确的路由路径 + this.$router.push({ path: '/psychology/assessment/taking', query: { assessmentId: assessmentId } }); + } this.loading = false; - } + }).catch(error => { + console.error('Failed to start assessment:', error); + this.$modal.msgError("开始测评失败,请重试"); + this.loading = false; + }); + } else { + this.$modal.msgError("请选择有效的用户档案"); + this.loading = false; } }); }, diff --git a/ruoyi-ui/src/views/psychology/qrcode/index.vue b/ruoyi-ui/src/views/psychology/qrcode/index.vue index 842b4858..b5782611 100644 --- a/ruoyi-ui/src/views/psychology/qrcode/index.vue +++ b/ruoyi-ui/src/views/psychology/qrcode/index.vue @@ -91,9 +91,19 @@ - + @@ -112,8 +122,14 @@ {{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }} - + + + diff --git a/ruoyi-ui/src/views/psychology/questionnaire/index.vue b/ruoyi-ui/src/views/psychology/questionnaire/index.vue index ec087708..4bced042 100644 --- a/ruoyi-ui/src/views/psychology/questionnaire/index.vue +++ b/ruoyi-ui/src/views/psychology/questionnaire/index.vue @@ -111,8 +111,15 @@ {{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }} - + + + + + + diff --git a/ruoyi-ui/src/views/psychology/questionnaire/start.vue b/ruoyi-ui/src/views/psychology/questionnaire/start.vue new file mode 100644 index 00000000..afe94c23 --- /dev/null +++ b/ruoyi-ui/src/views/psychology/questionnaire/start.vue @@ -0,0 +1,145 @@ + + + + + + diff --git a/ruoyi-ui/src/views/psychology/questionnaire/taking.vue b/ruoyi-ui/src/views/psychology/questionnaire/taking.vue new file mode 100644 index 00000000..363648bb --- /dev/null +++ b/ruoyi-ui/src/views/psychology/questionnaire/taking.vue @@ -0,0 +1,627 @@ + + + + + + diff --git a/ruoyi-ui/src/views/psychology/report/detail.vue b/ruoyi-ui/src/views/psychology/report/detail.vue index 2a34b5a2..a8799e15 100644 --- a/ruoyi-ui/src/views/psychology/report/detail.vue +++ b/ruoyi-ui/src/views/psychology/report/detail.vue @@ -8,7 +8,11 @@ {{ reportForm.reportId }} - {{ reportForm.assessmentId }} + + 问卷 + 量表 + + {{ reportForm.assessmentId || reportForm.answerId }} {{ reportForm.reportTitle || '-' }} 标准报告 @@ -27,7 +31,33 @@
报告内容 -
+
+ 报告内容正在生成中... +
+
+ + + 成绩排名 +
+ 查看排名 + + + + + + + + + + +
PDF下载
@@ -39,13 +69,18 @@