2025年11月12日

This commit is contained in:
xiao12feng 2025-11-12 18:18:30 +08:00
parent 6805ed2981
commit 60f2ab49c8
33 changed files with 2203 additions and 363 deletions

View File

@ -67,3 +67,30 @@ export function getQuestionnaireRankList(questionnaireId) {
})
}
// 查询待评分的主观题列表
export function getPendingScoringList(query) {
return request({
url: '/psychology/questionnaire/answer/scoring/pending',
method: 'get',
params: query
})
}
// 提交主观题评分
export function submitScoring(data) {
return request({
url: '/psychology/questionnaire/answer/scoring/submit',
method: 'post',
data: data
})
}
// 批量提交主观题评分
export function batchSubmitScoring(data) {
return request({
url: '/psychology/questionnaire/answer/scoring/batch',
method: 'post',
data: data
})
}

View File

@ -44,14 +44,41 @@ export function updateReport(data) {
})
}
// 删除报告
export function delReport(reportIds) {
// 修改报告支持传递sourceType
export function updateReportWithType(reportId, sourceType, data) {
const updateData = {
reportId: reportId,
sourceType: sourceType,
...data
}
return request({
url: '/psychology/report/' + reportIds,
url: '/psychology/report',
method: 'put',
data: updateData
})
}
// 删除报告支持传递sourceType
export function delReport(reportIds, sourceType) {
let url = '/psychology/report/' + reportIds;
if (sourceType) {
url += '?sourceType=' + sourceType;
}
return request({
url: url,
method: 'delete'
})
}
// 批量删除报告支持传递sourceType映射
export function delReports(reportData) {
return request({
url: '/psychology/report/batch',
method: 'delete',
data: reportData
})
}
// 生成报告
export function generateReport(assessmentId) {
return request({

View File

@ -390,6 +390,17 @@ export const dynamicRoutes = [
title: '问卷题目管理',
roles: ['admin']
}
},
// 主观题评分
{
path: 'questionnaire/scoring',
name: 'QuestionnaireScoring',
component: () => import('@/views/psychology/questionnaire/scoring'),
meta: {
title: '主观题评分',
icon: 'edit',
roles: ['admin']
}
}
]
}

View File

@ -298,10 +298,12 @@ export default {
this.getList();
},
methods: {
/** 加载量表列表 */
/** 加载量表列表(只显示量表,不包含问卷) */
loadScaleList() {
listScale({ status: '0', pageNum: 1, pageSize: 1000 }).then(response => {
this.scaleList = response.rows || [];
listScale({ status: '0', pageNum: 1, pageSize: 1000, includeQuestionnaire: false }).then(response => {
//
this.scaleList = (response.rows || [])
.filter(scale => !scale.sourceType || scale.sourceType === 'scale');
});
},
/** 加载因子列表(用于搜索) */

View File

@ -209,10 +209,12 @@ export default {
this.loading = false;
});
},
/** 加载量表列表 */
/** 加载量表列表(只显示量表,不包含问卷) */
loadScales() {
listScale({ status: '0' }).then(response => {
this.scaleList = response.rows || [];
listScale({ status: '0', includeQuestionnaire: false }).then(response => {
//
this.scaleList = (response.rows || [])
.filter(scale => !scale.sourceType || scale.sourceType === 'scale');
});
},
/** 加载用户列表 */

View File

@ -77,14 +77,17 @@ export default {
this.userName = response.data.userName;
});
},
/** 加载量表列表 */
/** 加载量表列表(只显示量表,不包含问卷) */
loadScales() {
listScale({ status: '0' }).then(response => {
this.scaleList = (response.rows || []).map(scale => ({
key: scale.scaleId,
label: scale.scaleName,
itemCount: scale.itemCount || 0
}));
listScale({ status: '0', includeQuestionnaire: false }).then(response => {
//
this.scaleList = (response.rows || [])
.filter(scale => !scale.sourceType || scale.sourceType === 'scale')
.map(scale => ({
key: scale.scaleId,
label: scale.scaleName,
itemCount: scale.itemCount || 0
}));
});
},
/** 加载用户已有权限的量表 */

View File

@ -425,10 +425,12 @@ export default {
this.loading = false;
});
},
/** 加载量表列表 */
/** 加载量表列表(只显示量表,不包含问卷) */
loadScales() {
listScale({ status: '0', pageNum: 1, pageSize: 1000 }).then(response => {
this.scaleList = response.rows || [];
listScale({ status: '0', pageNum: 1, pageSize: 1000, includeQuestionnaire: false }).then(response => {
//
this.scaleList = (response.rows || [])
.filter(scale => !scale.sourceType || scale.sourceType === 'scale');
}).catch(() => {
this.scaleList = [];
});

View File

@ -111,7 +111,7 @@
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="280">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="360">
<template slot-scope="scope">
<el-button
size="mini"
@ -120,6 +120,13 @@
@click="handleManageItems(scope.row)"
v-hasPermi="['psychology:questionnaire:query']"
>题目管理</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-trophy"
@click="handleViewRank(scope.row)"
v-hasPermi="['psychology:questionnaire:query']"
>查看排名</el-button>
<el-button
size="mini"
type="text"
@ -232,11 +239,51 @@
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!-- 查看排名对话框 -->
<el-dialog title="问卷排名" :visible.sync="rankOpen" width="800px" append-to-body>
<div style="margin-bottom: 15px;">
<span style="font-size: 16px; font-weight: bold;">问卷名称{{ currentQuestionnaireName }}</span>
</div>
<el-table v-loading="rankLoading" :data="rankList" border style="width: 100%">
<el-table-column type="index" label="排名" width="100" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.$index + 1 === 1" type="danger" size="medium">第1名</el-tag>
<el-tag v-else-if="scope.$index + 1 === 2" type="warning" size="medium">第2名</el-tag>
<el-tag v-else-if="scope.$index + 1 === 3" type="success" size="medium">第3名</el-tag>
<span v-else style="font-size: 14px;">{{ scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column prop="respondentName" label="答题人" width="150" />
<el-table-column prop="totalScore" label="总分" width="120" align="center">
<template slot-scope="scope">
<span style="font-weight: bold; color: #409EFF;">{{ scope.row.totalScore || 0 }}</span>
</template>
</el-table-column>
<el-table-column prop="rank" label="排名" width="100" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.row.rank === 1" type="danger">第1名</el-tag>
<el-tag v-else-if="scope.row.rank === 2" type="warning">第2名</el-tag>
<el-tag v-else-if="scope.row.rank === 3" type="success">第3名</el-tag>
<span v-else>{{ scope.row.rank }}</span>
</template>
</el-table-column>
<el-table-column prop="submitTime" label="提交时间" width="180" align="center">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button @click="rankOpen = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listQuestionnaire, getQuestionnaire, delQuestionnaire, addQuestionnaire, updateQuestionnaire } from "@/api/psychology/questionnaire"
import { getQuestionnaireRankList } from "@/api/psychology/questionnaireAnswer"
export default {
name: "PsyQuestionnaire",
@ -272,6 +319,11 @@ export default {
},
//
form: {},
//
rankOpen: false,
rankLoading: false,
rankList: [],
currentQuestionnaireName: "",
//
rules: {
questionnaireCode: [
@ -393,6 +445,25 @@ export default {
this.getList()
this.$modal.msgSuccess("删除成功")
}).catch(() => {})
},
/** 查看排名 */
handleViewRank(row) {
this.currentQuestionnaireName = row.questionnaireName || "";
this.rankOpen = true;
this.rankList = [];
this.rankLoading = true;
getQuestionnaireRankList(row.questionnaireId).then(response => {
this.rankList = response.data || [];
this.rankLoading = false;
if (this.rankList.length === 0) {
this.$modal.msgWarning("暂无排名数据");
}
}).catch(error => {
this.rankLoading = false;
console.error('加载排名失败:', error);
this.$modal.msgError("加载排名失败");
});
}
}
}

View File

@ -0,0 +1,371 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="问卷名称" prop="questionnaireName">
<el-input
v-model="queryParams.questionnaireName"
placeholder="请输入问卷名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="答题人" prop="respondentName">
<el-input
v-model="queryParams.respondentName"
placeholder="请输入答题人姓名"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-check"
size="mini"
:disabled="multiple"
@click="handleBatchScore"
v-hasPermi="['psychology:questionnaire:score']"
>批量评分</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="scoringList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" prop="detailId" width="80" />
<el-table-column label="问卷名称" align="center" prop="questionnaireName" :show-overflow-tooltip="true" />
<el-table-column label="答题人" align="center" prop="respondentName" width="120" />
<el-table-column label="题目序号" align="center" prop="itemNumber" width="100" />
<el-table-column label="题目类型" align="center" prop="itemType" width="100">
<template slot-scope="scope">
<el-tag v-if="scope.row.itemType === 'text'" type="info">简答题</el-tag>
<el-tag v-else-if="scope.row.itemType === 'textarea'" type="warning">问答题</el-tag>
<el-tag v-else-if="scope.row.itemType === 'essay'" type="danger">作文题</el-tag>
<span v-else>{{ scope.row.itemType }}</span>
</template>
</el-table-column>
<el-table-column label="题目内容" align="left" prop="itemContent" :show-overflow-tooltip="true" min-width="200" />
<el-table-column label="题目分值" align="center" prop="itemScore" width="100">
<template slot-scope="scope">
<span>{{ scope.row.itemScore || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="答案内容" align="left" prop="answerText" :show-overflow-tooltip="true" min-width="200" />
<el-table-column label="当前得分" align="center" prop="answerScore" width="100">
<template slot-scope="scope">
<span>{{ scope.row.answerScore || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="提交时间" align="center" prop="submitTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleScore(scope.row)"
v-hasPermi="['psychology:questionnaire:score']"
>评分</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 评分对话框 -->
<el-dialog title="主观题评分" :visible.sync="scoreOpen" width="800px" append-to-body>
<el-form ref="scoreForm" :model="scoreForm" :rules="scoreRules" label-width="120px">
<el-form-item label="问卷名称">
<el-input v-model="scoreForm.questionnaireName" :disabled="true" />
</el-form-item>
<el-form-item label="答题人">
<el-input v-model="scoreForm.respondentName" :disabled="true" />
</el-form-item>
<el-form-item label="题目序号">
<el-input v-model="scoreForm.itemNumber" :disabled="true" />
</el-form-item>
<el-form-item label="题目类型">
<el-input v-model="scoreForm.itemTypeName" :disabled="true" />
</el-form-item>
<el-form-item label="题目内容">
<el-input v-model="scoreForm.itemContent" type="textarea" :rows="3" :disabled="true" />
</el-form-item>
<el-form-item label="题目分值">
<el-input v-model="scoreForm.itemScore" :disabled="true" />
</el-form-item>
<el-form-item label="答案内容">
<el-input v-model="scoreForm.answerText" type="textarea" :rows="6" :disabled="true" />
</el-form-item>
<el-form-item label="得分" prop="score">
<el-input-number
v-model="scoreForm.score"
:min="0"
:max="scoreForm.itemScore || 100"
:precision="2"
:step="0.5"
placeholder="请输入得分"
style="width: 100%;"
/>
<div style="margin-top: 5px; color: #909399; font-size: 12px;">
满分{{ scoreForm.itemScore || 0 }}
</div>
</el-form-item>
<el-form-item label="评语">
<el-input
v-model="scoreForm.comment"
type="textarea"
:rows="4"
placeholder="请输入评语(可选)"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitScoreForm"> </el-button>
<el-button @click="cancelScore"> </el-button>
</div>
</el-dialog>
<!-- 批量评分对话框 -->
<el-dialog title="批量评分" :visible.sync="batchScoreOpen" width="900px" append-to-body>
<el-table :data="selectedRows" border max-height="400">
<el-table-column label="序号" align="center" width="60" type="index" />
<el-table-column label="问卷名称" prop="questionnaireName" :show-overflow-tooltip="true" />
<el-table-column label="答题人" prop="respondentName" width="100" />
<el-table-column label="题目序号" prop="itemNumber" width="80" />
<el-table-column label="题目分值" prop="itemScore" width="80" />
<el-table-column label="得分" width="150">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.score"
:min="0"
:max="scope.row.itemScore || 100"
:precision="2"
:step="0.5"
size="small"
style="width: 100%;"
/>
</template>
</el-table-column>
<el-table-column label="评语" min-width="200">
<template slot-scope="scope">
<el-input
v-model="scope.row.comment"
type="textarea"
:rows="2"
placeholder="评语(可选)"
size="small"
/>
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitBatchScore"> </el-button>
<el-button @click="cancelBatchScore"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getPendingScoringList, submitScoring, batchSubmitScoring } from "@/api/psychology/questionnaireAnswer";
export default {
name: "QuestionnaireScoring",
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
scoringList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
questionnaireName: undefined,
respondentName: undefined
},
//
scoreOpen: false,
scoreForm: {},
scoreRules: {
score: [
{ required: true, message: "得分不能为空", trigger: "blur" },
{ type: 'number', min: 0, message: "得分不能小于0", trigger: "blur" }
]
},
//
batchScoreOpen: false,
selectedRows: []
};
},
created() {
this.getList();
},
methods: {
/** 查询待评分列表 */
getList() {
this.loading = true;
getPendingScoringList(this.queryParams).then(response => {
this.scoringList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.detailId);
this.single = selection.length != 1;
this.multiple = !selection.length;
this.selectedRows = selection;
},
/** 评分按钮操作 */
handleScore(row) {
const itemTypeMap = {
'text': '简答题',
'textarea': '问答题',
'essay': '作文题'
};
this.scoreForm = {
detailId: row.detailId,
questionnaireName: row.questionnaireName,
respondentName: row.respondentName,
itemNumber: row.itemNumber,
itemTypeName: itemTypeMap[row.itemType] || row.itemType,
itemContent: row.itemContent,
itemScore: row.itemScore || 0,
answerText: row.answerText || '',
score: row.answerScore || 0,
comment: ''
};
this.scoreOpen = true;
},
/** 提交评分表单 */
submitScoreForm() {
this.$refs["scoreForm"].validate(valid => {
if (valid) {
const data = {
detailId: this.scoreForm.detailId,
score: this.scoreForm.score,
comment: this.scoreForm.comment
};
submitScoring(data).then(response => {
this.$modal.msgSuccess("评分成功");
this.scoreOpen = false;
this.getList();
}).catch(error => {
console.error('评分失败:', error);
this.$modal.msgError("评分失败");
});
}
});
},
/** 取消评分 */
cancelScore() {
this.scoreOpen = false;
this.resetScoreForm();
},
/** 重置评分表单 */
resetScoreForm() {
this.scoreForm = {
detailId: null,
questionnaireName: '',
respondentName: '',
itemNumber: null,
itemTypeName: '',
itemContent: '',
itemScore: 0,
answerText: '',
score: 0,
comment: ''
};
if (this.$refs["scoreForm"]) {
this.$refs["scoreForm"].resetFields();
}
},
/** 批量评分按钮操作 */
handleBatchScore() {
if (this.selectedRows.length === 0) {
this.$modal.msgWarning("请选择要评分的题目");
return;
}
// scorecomment
this.selectedRows = this.selectedRows.map(row => ({
...row,
score: row.answerScore || 0,
comment: ''
}));
this.batchScoreOpen = true;
},
/** 提交批量评分 */
submitBatchScore() {
const scoringList = this.selectedRows.map(row => ({
detailId: row.detailId,
score: row.score || 0,
comment: row.comment || ''
}));
batchSubmitScoring(scoringList).then(response => {
this.$modal.msgSuccess(response.msg || "批量评分成功");
this.batchScoreOpen = false;
this.getList();
}).catch(error => {
console.error('批量评分失败:', error);
this.$modal.msgError("批量评分失败");
});
},
/** 取消批量评分 */
cancelBatchScore() {
this.batchScoreOpen = false;
}
}
};
</script>
<style scoped>
.app-container {
padding: 20px;
}
</style>

View File

@ -3,7 +3,16 @@
<el-card v-loading="loading">
<div slot="header" class="clearfix">
<span>测评报告详情</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="handleBack">返回</el-button>
<div style="float: right;">
<el-button
style="padding: 3px 0; margin-right: 10px;"
type="text"
icon="el-icon-edit"
@click="handleEdit"
v-hasPermi="['psychology:report:edit']"
>编辑</el-button>
<el-button style="padding: 3px 0" type="text" @click="handleBack">返回</el-button>
</div>
</div>
<el-descriptions title="基本信息" :column="2" border>
@ -64,11 +73,50 @@
<el-button type="primary" icon="el-icon-download" @click="handleDownloadPDF">下载PDF报告</el-button>
</div>
</el-card>
<!-- 报告编辑对话框 -->
<el-dialog title="编辑报告" :visible.sync="editOpen" width="900px" append-to-body>
<el-form ref="editForm" :model="editForm" :rules="editRules" label-width="100px">
<el-form-item label="报告标题" prop="reportTitle">
<el-input v-model="editForm.reportTitle" placeholder="请输入报告标题" />
</el-form-item>
<el-form-item label="报告类型" prop="reportType">
<el-select v-model="editForm.reportType" placeholder="请选择报告类型">
<el-option label="标准报告" value="standard" />
<el-option label="详细报告" value="detailed" />
<el-option label="简要报告" value="brief" />
</el-select>
</el-form-item>
<el-form-item label="报告摘要" prop="summary">
<el-input
v-model="editForm.summary"
type="textarea"
:rows="4"
placeholder="请输入报告摘要"
/>
</el-form-item>
<el-form-item label="报告内容" prop="reportContent">
<el-input
v-model="editForm.reportContent"
type="textarea"
:rows="15"
placeholder="请输入报告内容支持HTML格式"
/>
<div style="margin-top: 10px; color: #909399; font-size: 12px;">
<i class="el-icon-info"></i> 提示报告内容支持HTML格式可以包含标题段落列表等格式
</div>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitEditForm"> </el-button>
<el-button @click="cancelEdit"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getReport, getReportByAssessmentId } from "@/api/psychology/report";
import { getReport, getReportByAssessmentId, updateReportWithType } from "@/api/psychology/report";
import { getQuestionnaireRankList } from "@/api/psychology/questionnaireAnswer";
import request from '@/utils/request';
@ -80,7 +128,18 @@ export default {
reportForm: {},
sourceType: null,
rankList: [],
rankLoading: false
rankLoading: false,
//
editOpen: false,
editForm: {},
editRules: {
reportTitle: [
{ required: true, message: "报告标题不能为空", trigger: "blur" }
],
reportType: [
{ required: true, message: "报告类型不能为空", trigger: "change" }
]
}
};
},
created() {
@ -178,6 +237,60 @@ export default {
console.error('获取答题记录失败:', error);
this.$modal.msgError("获取答题记录失败");
});
},
/** 编辑按钮操作 */
handleEdit() {
this.editForm = {
reportId: this.reportForm.reportId,
sourceType: this.sourceType,
reportTitle: this.reportForm.reportTitle || '',
reportType: this.reportForm.reportType || 'standard',
summary: this.reportForm.summary || '',
reportContent: this.reportForm.reportContent || ''
};
this.editOpen = true;
},
/** 提交编辑表单 */
submitEditForm() {
this.$refs["editForm"].validate(valid => {
if (valid) {
const updateData = {
reportTitle: this.editForm.reportTitle,
reportType: this.editForm.reportType,
summary: this.editForm.summary,
reportContent: this.editForm.reportContent
};
updateReportWithType(this.editForm.reportId, this.editForm.sourceType, updateData).then(response => {
this.$modal.msgSuccess("修改成功");
this.editOpen = false;
//
this.loadReport();
}).catch(error => {
console.error('修改报告失败:', error);
this.$modal.msgError("修改失败");
});
}
});
},
/** 取消编辑 */
cancelEdit() {
this.editOpen = false;
this.resetEditForm();
},
/** 重置编辑表单 */
resetEditForm() {
this.editForm = {
reportId: null,
sourceType: null,
reportTitle: '',
reportType: 'standard',
summary: '',
reportContent: ''
};
if (this.$refs["editForm"]) {
this.$refs["editForm"].resetFields();
}
}
}
};

View File

@ -121,11 +121,50 @@
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 报告编辑对话框 -->
<el-dialog title="编辑报告" :visible.sync="editOpen" width="900px" append-to-body>
<el-form ref="editForm" :model="editForm" :rules="editRules" label-width="100px">
<el-form-item label="报告标题" prop="reportTitle">
<el-input v-model="editForm.reportTitle" placeholder="请输入报告标题" />
</el-form-item>
<el-form-item label="报告类型" prop="reportType">
<el-select v-model="editForm.reportType" placeholder="请选择报告类型">
<el-option label="标准报告" value="standard" />
<el-option label="详细报告" value="detailed" />
<el-option label="简要报告" value="brief" />
</el-select>
</el-form-item>
<el-form-item label="报告摘要" prop="summary">
<el-input
v-model="editForm.summary"
type="textarea"
:rows="4"
placeholder="请输入报告摘要"
/>
</el-form-item>
<el-form-item label="报告内容" prop="reportContent">
<el-input
v-model="editForm.reportContent"
type="textarea"
:rows="15"
placeholder="请输入报告内容支持HTML格式"
/>
<div style="margin-top: 10px; color: #909399; font-size: 12px;">
<i class="el-icon-info"></i> 提示报告内容支持HTML格式可以包含标题段落列表等格式
</div>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitEditForm"> </el-button>
<el-button @click="cancelEdit"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listReport, getReport, delReport, exportReport } from "@/api/psychology/report";
import { listReport, getReport, delReport, exportReport, updateReportWithType } from "@/api/psychology/report";
export default {
name: "Report",
@ -135,6 +174,8 @@ export default {
loading: true,
//
ids: [],
// sourceType
selectedRows: [],
//
single: true,
//
@ -152,6 +193,17 @@ export default {
sourceType: undefined,
reportType: undefined,
isGenerated: undefined
},
//
editOpen: false,
editForm: {},
editRules: {
reportTitle: [
{ required: true, message: "报告标题不能为空", trigger: "blur" }
],
reportType: [
{ required: true, message: "报告类型不能为空", trigger: "change" }
]
}
};
},
@ -181,6 +233,7 @@ export default {
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.reportId);
this.selectedRows = selection; // sourceType
this.single = selection.length != 1;
this.multiple = !selection.length;
},
@ -190,8 +243,66 @@ export default {
},
/** 修改按钮操作 */
handleUpdate(row) {
// TODO:
this.$modal.msgInfo("报告编辑功能待实现");
//
getReport(row.reportId, row.sourceType).then(response => {
if (response.data) {
this.editForm = {
reportId: response.data.reportId,
sourceType: row.sourceType,
reportTitle: response.data.reportTitle || '',
reportType: response.data.reportType || 'standard',
summary: response.data.summary || '',
reportContent: response.data.reportContent || ''
};
this.editOpen = true;
} else {
this.$modal.msgError("获取报告数据失败");
}
}).catch(error => {
console.error('获取报告数据失败:', error);
this.$modal.msgError("获取报告数据失败");
});
},
/** 提交编辑表单 */
submitEditForm() {
this.$refs["editForm"].validate(valid => {
if (valid) {
const updateData = {
reportTitle: this.editForm.reportTitle,
reportType: this.editForm.reportType,
summary: this.editForm.summary,
reportContent: this.editForm.reportContent
};
updateReportWithType(this.editForm.reportId, this.editForm.sourceType, updateData).then(response => {
this.$modal.msgSuccess("修改成功");
this.editOpen = false;
this.getList();
}).catch(error => {
console.error('修改报告失败:', error);
this.$modal.msgError("修改失败");
});
}
});
},
/** 取消编辑 */
cancelEdit() {
this.editOpen = false;
this.resetEditForm();
},
/** 重置编辑表单 */
resetEditForm() {
this.editForm = {
reportId: null,
sourceType: null,
reportTitle: '',
reportType: 'standard',
summary: '',
reportContent: ''
};
if (this.$refs["editForm"]) {
this.$refs["editForm"].resetFields();
}
},
/** 导出按钮操作 */
handleExport() {
@ -241,13 +352,67 @@ export default {
},
/** 删除按钮操作 */
handleDelete(row) {
const reportIds = row.reportId ? [row.reportId] : this.ids;
let reportIds;
let sourceType;
if (row && row.reportId) {
//
reportIds = [row.reportId];
sourceType = row.sourceType;
} else {
//
reportIds = this.ids;
//
if (this.selectedRows && this.selectedRows.length > 0) {
const types = [...new Set(this.selectedRows.map(r => r.sourceType))];
if (types.length === 1) {
sourceType = types[0];
} else {
//
this.$modal.confirm('选中的报告包含不同类型,将分别删除。是否确认删除?').then(() => {
this.deleteReportsByType();
}).catch(() => {});
return;
}
}
}
this.$modal.confirm('是否确认删除选中的报告?').then(() => {
return delReport(reportIds);
return delReport(reportIds, sourceType);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
}).catch((error) => {
console.error('删除失败:', error);
const errorMsg = error.msg || error.message || "删除失败";
this.$modal.msgError(errorMsg);
});
},
/** 按类型分别删除报告 */
deleteReportsByType() {
// sourceType
const grouped = {};
this.selectedRows.forEach(row => {
const type = row.sourceType || 'assessment';
if (!grouped[type]) {
grouped[type] = [];
}
grouped[type].push(row.reportId);
});
//
const deletePromises = Object.keys(grouped).map(type => {
return delReport(grouped[type], type);
});
Promise.all(deletePromises).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch((error) => {
console.error('删除失败:', error);
const errorMsg = error.msg || error.message || "删除失败";
this.$modal.msgError(errorMsg);
});
}
}
};

View File

@ -314,11 +314,15 @@ export default {
}
},
methods: {
/** 加载量表列表 */
/** 加载量表列表(只显示量表,不包含问卷) */
loadScaleList() {
this.scaleLoading = true;
listScale(this.scaleQuery).then(response => {
this.scaleList = response.rows || [];
//
const query = { ...this.scaleQuery, includeQuestionnaire: false };
listScale(query).then(response => {
//
this.scaleList = (response.rows || [])
.filter(scale => !scale.sourceType || scale.sourceType === 'scale');
this.scaleLoading = false;
}).catch(() => {
this.scaleLoading = false;

View File

@ -910,7 +910,7 @@ export default {
handleQuestionnaireDetail(row) {
const questionnaireId = row.originalId || Math.abs(row.scaleId)
getQuestionnaire(questionnaireId).then(response => {
this.$modal.msgInfo("问卷详情:" + JSON.stringify(response.data, null, 2))
this.$modal.msg("问卷详情:" + JSON.stringify(response.data, null, 2))
})
},
/** 问卷修改按钮操作 */

View File

@ -268,11 +268,15 @@ export default {
}
},
methods: {
/** 加载量表列表 */
/** 加载量表列表(只显示量表,不包含问卷) */
loadScales() {
this.scaleLoading = true;
listScale(this.scaleQuery).then(response => {
this.scaleList = response.rows || [];
//
const query = { ...this.scaleQuery, includeQuestionnaire: false };
listScale(query).then(response => {
//
this.scaleList = (response.rows || [])
.filter(scale => !scale.sourceType || scale.sourceType === 'scale');
this.scaleLoading = false;
}).catch(() => {
this.scaleLoading = false;

View File

@ -307,10 +307,12 @@ export default {
this.loading = false;
});
},
/** 加载量表列表 */
/** 加载量表列表(只显示量表,不包含问卷) */
loadScales() {
listScale({ status: '0', pageNum: 1, pageSize: 1000 }).then(response => {
this.scaleList = response.rows || [];
listScale({ status: '0', pageNum: 1, pageSize: 1000, includeQuestionnaire: false }).then(response => {
//
this.scaleList = (response.rows || [])
.filter(scale => !scale.sourceType || scale.sourceType === 'scale');
});
},
/** 加载因子列表 */

View File

@ -45,6 +45,9 @@ public class PsyAssessmentReportController extends BaseController
@Autowired
private PsyQuestionnaireReportMapper questionnaireReportMapper;
@Autowired
private com.ddnai.system.service.psychology.IPsyQuestionnaireAnswerService questionnaireAnswerService;
/**
* 获取报告列表包含测评报告和问卷报告
@ -233,26 +236,176 @@ public class PsyAssessmentReportController extends BaseController
}
/**
* 修改报告
* 修改报告支持测评报告和问卷报告
*/
@PreAuthorize("@ss.hasPermi('psychology:report:edit')")
@Log(title = "测评报告", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody PsyAssessmentReport report)
public AjaxResult edit(@RequestBody java.util.Map<String, Object> params)
{
report.setUpdateBy(getUsername());
return toAjax(reportService.updateReport(report));
String sourceType = (String) params.get("sourceType");
Long reportId = Long.valueOf(params.get("reportId").toString());
if ("questionnaire".equals(sourceType)) {
// 更新问卷报告
PsyQuestionnaireReport report = questionnaireReportMapper.selectReportById(reportId);
if (report == null) {
return error("报告不存在");
}
if (params.containsKey("reportTitle")) {
report.setReportTitle((String) params.get("reportTitle"));
}
if (params.containsKey("reportContent")) {
report.setReportContent((String) params.get("reportContent"));
}
if (params.containsKey("summary")) {
report.setSummary((String) params.get("summary"));
}
if (params.containsKey("reportType")) {
report.setReportType((String) params.get("reportType"));
}
report.setUpdateBy(getUsername());
int result = questionnaireReportMapper.updateReport(report);
return toAjax(result);
} else {
// 更新测评报告
PsyAssessmentReport report = reportService.selectReportById(reportId);
if (report == null) {
return error("报告不存在");
}
if (params.containsKey("reportTitle")) {
report.setReportTitle((String) params.get("reportTitle"));
}
if (params.containsKey("reportContent")) {
report.setReportContent((String) params.get("reportContent"));
}
if (params.containsKey("summary")) {
report.setSummary((String) params.get("summary"));
}
if (params.containsKey("reportType")) {
report.setReportType((String) params.get("reportType"));
}
report.setUpdateBy(getUsername());
return toAjax(reportService.updateReport(report));
}
}
/**
* 删除报告
* 删除报告支持测评报告和问卷报告
*/
@PreAuthorize("@ss.hasPermi('psychology:report:remove')")
@Log(title = "测评报告", businessType = BusinessType.DELETE)
@DeleteMapping("/{reportIds}")
public AjaxResult remove(@PathVariable Long[] reportIds)
public AjaxResult remove(@PathVariable Long[] reportIds, @RequestParam(required = false) String sourceType)
{
return toAjax(reportService.deleteReportByIds(reportIds));
try {
int totalDeleted = 0;
if ("questionnaire".equals(sourceType)) {
// 删除问卷报告同时删除对应的答题记录
for (Long reportId : reportIds) {
// 先查询报告获取answerId
PsyQuestionnaireReport report = questionnaireReportMapper.selectReportById(reportId);
if (report != null) {
Long answerId = report.getAnswerId();
// 删除报告
int result = questionnaireReportMapper.deleteReportById(reportId);
if (result > 0) {
totalDeleted++;
System.out.println("删除问卷报告成功reportId: " + reportId + ", answerId: " + answerId);
// 删除对应的答题记录会级联删除答案详情
try {
// 先获取答题记录以便重新计算排名
com.ddnai.system.domain.psychology.PsyQuestionnaireAnswer answer =
questionnaireAnswerService.selectAnswerById(answerId);
Long questionnaireId = null;
if (answer != null) {
questionnaireId = answer.getQuestionnaireId();
}
// 删除答题记录会级联删除答案详情
int deleteResult = questionnaireAnswerService.deleteAnswerByIds(new Long[]{answerId});
System.out.println("删除答题记录结果: " + deleteResult + ", answerId: " + answerId);
// 重新计算排名
if (questionnaireId != null) {
// 通过查询所有答题记录来重新计算排名
com.ddnai.system.domain.psychology.PsyQuestionnaireAnswer query =
new com.ddnai.system.domain.psychology.PsyQuestionnaireAnswer();
query.setQuestionnaireId(questionnaireId);
query.setStatus("1");
java.util.List<com.ddnai.system.domain.psychology.PsyQuestionnaireAnswer> allAnswers =
questionnaireAnswerService.selectAnswerList(query);
// 按总分降序排序
allAnswers.sort((a, b) -> {
if (a.getTotalScore() == null && b.getTotalScore() == null) return 0;
if (a.getTotalScore() == null) return 1;
if (b.getTotalScore() == null) return -1;
int compare = b.getTotalScore().compareTo(a.getTotalScore());
if (compare != 0) return compare;
if (a.getSubmitTime() != null && b.getSubmitTime() != null) {
return a.getSubmitTime().compareTo(b.getSubmitTime());
}
return 0;
});
// 更新排名
int rank = 1;
for (com.ddnai.system.domain.psychology.PsyQuestionnaireAnswer a : allAnswers) {
a.setRank(rank++);
questionnaireAnswerService.updateAnswer(a);
}
System.out.println("重新计算排名成功questionnaireId: " + questionnaireId);
}
} catch (Exception e) {
System.err.println("删除答题记录失败answerId: " + answerId + ", 错误: " + e.getMessage());
e.printStackTrace();
}
} else {
System.err.println("删除问卷报告失败reportId: " + reportId + "(可能不存在)");
}
} else {
System.err.println("问卷报告不存在reportId: " + reportId);
}
}
} else {
// 默认删除测评报告如果不存在则尝试删除问卷报告
for (Long reportId : reportIds) {
// 先尝试删除测评报告使用单个ID的数组
Long[] singleIdArray = new Long[]{reportId};
int result = reportService.deleteReportByIds(singleIdArray);
if (result > 0) {
totalDeleted++;
System.out.println("删除测评报告成功reportId: " + reportId);
} else {
// 如果测评报告不存在尝试删除问卷报告
System.out.println("测评报告不存在尝试删除问卷报告reportId: " + reportId);
int qResult = questionnaireReportMapper.deleteReportById(reportId);
if (qResult > 0) {
totalDeleted++;
System.out.println("删除问卷报告成功reportId: " + reportId);
} else {
System.err.println("删除报告失败reportId: " + reportId + "(测评报告和问卷报告都不存在)");
}
}
}
}
if (totalDeleted > 0) {
return success("成功删除 " + totalDeleted + " 个报告");
} else {
return error("删除失败,报告不存在或已被删除");
}
} catch (Exception e) {
logger.error("删除报告失败", e);
return error("删除报告失败:" + e.getMessage());
}
}
/**

View File

@ -1,5 +1,6 @@
package com.ddnai.web.controller.psychology;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
@ -45,6 +46,9 @@ public class PsyQuestionnaireAnswerController extends BaseController
@Autowired
private IPsyQuestionnaireAnswerService answerService;
@Autowired
private com.ddnai.system.mapper.psychology.PsyQuestionnaireReportMapper questionnaireReportMapper;
/**
* 获取问卷答题列表
@ -179,18 +183,45 @@ public class PsyQuestionnaireAnswerController extends BaseController
}
/**
* 获取问卷成绩排名列表
* 获取问卷成绩排名列表只返回报告管理中存在的报告对应的答题记录
*/
@GetMapping("/rank/{questionnaireId}")
public AjaxResult getRankList(@PathVariable Long questionnaireId)
{
// 先查询报告管理中的所有问卷报告使用与报告管理相同的查询逻辑
com.ddnai.system.domain.psychology.PsyQuestionnaireReport reportQuery =
new com.ddnai.system.domain.psychology.PsyQuestionnaireReport();
List<com.ddnai.system.domain.psychology.PsyQuestionnaireReport> allReports =
questionnaireReportMapper.selectReportList(reportQuery);
// 收集所有报告的answerId这些是报告管理中可见的报告
java.util.Set<Long> validAnswerIds = new java.util.HashSet<>();
for (com.ddnai.system.domain.psychology.PsyQuestionnaireReport report : allReports) {
if (report.getAnswerId() != null) {
// 只包含指定问卷的报告
PsyQuestionnaireAnswer answer = answerService.selectAnswerById(report.getAnswerId());
if (answer != null && answer.getQuestionnaireId().equals(questionnaireId)) {
validAnswerIds.add(report.getAnswerId());
}
}
}
// 查询该问卷的所有答题记录
PsyQuestionnaireAnswer query = new PsyQuestionnaireAnswer();
query.setQuestionnaireId(questionnaireId);
query.setStatus("1"); // 只查询已完成的
List<PsyQuestionnaireAnswer> list = answerService.selectAnswerList(query);
// 只保留报告管理中存在的答题记录
List<PsyQuestionnaireAnswer> filteredList = new ArrayList<>();
for (PsyQuestionnaireAnswer answer : list) {
if (validAnswerIds.contains(answer.getAnswerId())) {
filteredList.add(answer);
}
}
// 按总分降序排序总分相同按提交时间升序
list.sort((a, b) -> {
filteredList.sort((a, b) -> {
if (a.getTotalScore() == null && b.getTotalScore() == null) return 0;
if (a.getTotalScore() == null) return 1;
if (b.getTotalScore() == null) return -1;
@ -203,7 +234,7 @@ public class PsyQuestionnaireAnswerController extends BaseController
return 0;
});
return success(list);
return success(filteredList);
}
/**
@ -225,5 +256,75 @@ public class PsyQuestionnaireAnswerController extends BaseController
return error("报告生成失败:" + e.getMessage());
}
}
/**
* 查询待评分的主观题列表
*/
@PreAuthorize("@ss.hasPermi('psychology:questionnaire:score')")
@GetMapping("/scoring/pending")
public TableDataInfo getPendingScoringList(PsyQuestionnaireAnswerDetail detail)
{
startPage();
List<Map<String, Object>> list = answerService.selectPendingScoringList(detail);
return getDataTable(list);
}
/**
* 提交主观题评分
*/
@PreAuthorize("@ss.hasPermi('psychology:questionnaire:score')")
@Log(title = "主观题评分", businessType = BusinessType.UPDATE)
@PostMapping("/scoring/submit")
public AjaxResult submitScoring(@RequestBody Map<String, Object> params)
{
Long detailId = Long.valueOf(params.get("detailId").toString());
java.math.BigDecimal score = new java.math.BigDecimal(params.get("score").toString());
String comment = params.get("comment") != null ? params.get("comment").toString() : null;
int result = answerService.submitScoring(detailId, score, comment);
if (result > 0)
{
return success("评分成功");
}
return error("评分失败");
}
/**
* 批量提交主观题评分
*/
@PreAuthorize("@ss.hasPermi('psychology:questionnaire:score')")
@Log(title = "主观题批量评分", businessType = BusinessType.UPDATE)
@PostMapping("/scoring/batch")
public AjaxResult batchSubmitScoring(@RequestBody List<Map<String, Object>> scoringList)
{
int successCount = 0;
int failCount = 0;
for (Map<String, Object> params : scoringList)
{
try
{
Long detailId = Long.valueOf(params.get("detailId").toString());
java.math.BigDecimal score = new java.math.BigDecimal(params.get("score").toString());
String comment = params.get("comment") != null ? params.get("comment").toString() : null;
int result = answerService.submitScoring(detailId, score, comment);
if (result > 0)
{
successCount++;
}
else
{
failCount++;
}
}
catch (Exception e)
{
failCount++;
}
}
return success("批量评分完成:成功 " + successCount + " 条,失败 " + failCount + "");
}
}

View File

@ -91,7 +91,17 @@ public class PsyQuestionnaireController extends BaseController
@DeleteMapping("/{questionnaireIds}")
public AjaxResult remove(@PathVariable Long[] questionnaireIds)
{
return toAjax(questionnaireService.deleteQuestionnaireByIds(questionnaireIds));
try {
int result = questionnaireService.deleteQuestionnaireByIds(questionnaireIds);
if (result > 0) {
return success("删除问卷成功");
} else {
return error("删除问卷失败,可能问卷不存在或已被删除");
}
} catch (Exception e) {
logger.error("删除问卷失败", e);
return error("删除问卷失败:" + e.getMessage());
}
}
}

View File

@ -82,8 +82,21 @@ public class PsyScalePermissionController extends BaseController
@PostMapping
public AjaxResult add(@Validated @RequestBody PsyScalePermission permission)
{
permission.setCreateBy(getUsername());
return toAjax(permissionService.insertPermission(permission));
try {
permission.setCreateBy(getUsername());
int result = permissionService.insertPermission(permission);
if (result > 0) {
return success("添加权限成功");
} else {
return error("添加权限失败");
}
} catch (RuntimeException e) {
logger.error("添加权限失败", e);
return error(e.getMessage());
} catch (Exception e) {
logger.error("添加权限失败", e);
return error("添加权限失败:" + e.getMessage());
}
}
/**
@ -94,8 +107,21 @@ public class PsyScalePermissionController extends BaseController
@PutMapping
public AjaxResult edit(@Validated @RequestBody PsyScalePermission permission)
{
permission.setUpdateBy(getUsername());
return toAjax(permissionService.updatePermission(permission));
try {
permission.setUpdateBy(getUsername());
int result = permissionService.updatePermission(permission);
if (result > 0) {
return success("修改权限成功");
} else {
return error("修改权限失败");
}
} catch (RuntimeException e) {
logger.error("修改权限失败", e);
return error(e.getMessage());
} catch (Exception e) {
logger.error("修改权限失败", e);
return error("修改权限失败:" + e.getMessage());
}
}
/**
@ -117,11 +143,21 @@ public class PsyScalePermissionController extends BaseController
@PostMapping("/assign")
public AjaxResult assignUserScales(@RequestBody java.util.Map<String, Object> params)
{
Long userId = Long.valueOf(params.get("userId").toString());
@SuppressWarnings("unchecked")
List<Long> scaleIdList = (List<Long>) params.get("scaleIds");
Long[] scaleIds = scaleIdList != null ? scaleIdList.toArray(new Long[0]) : new Long[0];
return toAjax(permissionService.batchAssignUserScalePermission(userId, scaleIds));
try {
Long userId = Long.valueOf(params.get("userId").toString());
@SuppressWarnings("unchecked")
List<Long> scaleIdList = (List<Long>) params.get("scaleIds");
Long[] scaleIds = scaleIdList != null ? scaleIdList.toArray(new Long[0]) : new Long[0];
int result = permissionService.batchAssignUserScalePermission(userId, scaleIds);
if (result > 0) {
return success("分配权限成功,共分配 " + result + " 个权限");
} else {
return error("分配权限失败,可能所有量表都不存在");
}
} catch (Exception e) {
logger.error("分配权限失败", e);
return error("分配权限失败:" + e.getMessage());
}
}
}

View File

@ -6,9 +6,9 @@ spring:
druid:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/ry_news?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
url: jdbc:mysql://1.15.149.240:3306/ry_news?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root
password: 9daafa7710f5198f
# 从库数据源
slave:
# 从数据源开关/默认关闭

View File

@ -67,5 +67,21 @@ public interface PsyQuestionnaireAnswerDetailMapper
* @return 结果
*/
public int deleteDetailByIds(Long[] detailIds);
/**
* 根据答题ID批量删除答案详情
*
* @param answerIds 答题ID数组
* @return 结果
*/
public int deleteDetailByAnswerIds(Long[] answerIds);
/**
* 查询待评分的主观题列表关联题目和答题记录信息
*
* @param detail 查询条件
* @return 待评分题目列表
*/
public List<java.util.Map<String, Object>> selectPendingScoringList(PsyQuestionnaireAnswerDetail detail);
}

View File

@ -200,6 +200,8 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
{
// 主观题标记为待评分
detail.setIsScored("0");
// 主观题的answerScore保持为null不设置避免覆盖
// detail.setAnswerScore(null); // 不要设置保持原值
}
// 更新答案详情
@ -270,7 +272,7 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
return;
}
// 获取所有答案详情
// 获取所有答案详情重新从数据库获取确保获取最新数据
List<PsyQuestionnaireAnswerDetail> details = detailMapper.selectDetailListByAnswerId(answerId);
if (details == null || details.isEmpty()) {
System.err.println("答案详情为空answerId: " + answerId);
@ -279,6 +281,19 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
System.out.println("获取到 " + details.size() + " 条答案详情");
// 为了确保获取最新数据重新查询每个detail避免MyBatis一级缓存问题
for (int i = 0; i < details.size(); i++) {
PsyQuestionnaireAnswerDetail detail = details.get(i);
PsyQuestionnaireAnswerDetail latestDetail = detailMapper.selectDetailById(detail.getDetailId());
if (latestDetail != null) {
// 使用最新的数据
details.set(i, latestDetail);
System.out.println("刷新detail - detailId: " + latestDetail.getDetailId() +
", isScored: " + latestDetail.getIsScored() +
", answerScore: " + latestDetail.getAnswerScore());
}
}
// 生成报告内容
StringBuilder reportContent = new StringBuilder();
reportContent.append("<div class='report-container'>");
@ -313,6 +328,24 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
if (item == null) {
continue;
}
// 确保主观题标识正确从题目类型判断
boolean isSubjective = isSubjectiveType(item.getItemType());
String isSubjectiveStr = isSubjective ? "1" : "0";
// 如果数据库中的isSubjective字段为空或不正确使用题目类型判断的结果
if (detail.getIsSubjective() == null || detail.getIsSubjective().isEmpty()) {
detail.setIsSubjective(isSubjectiveStr);
}
// 调试日志输出每个题目的评分状态
System.out.println("报告生成 - 题目ID: " + detail.getItemId() +
", detailId: " + detail.getDetailId() +
", isScored: " + detail.getIsScored() +
", isSubjective: " + detail.getIsSubjective() +
", answerScore: " + detail.getAnswerScore() +
", answerScore类型: " + (detail.getAnswerScore() != null ? detail.getAnswerScore().getClass().getName() : "null") +
", itemType: " + item.getItemType());
reportContent.append("<tr>");
reportContent.append("<td style='padding: 10px; border: 1px solid #ddd;'>").append(item.getItemNumber() != null ? item.getItemNumber() : "").append("</td>");
// HTML转义题目内容防止XSS攻击
@ -320,7 +353,25 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
itemContent = itemContent.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;").replace("'", "&#39;");
reportContent.append("<td style='padding: 10px; border: 1px solid #ddd;'>").append(itemContent).append("</td>");
reportContent.append("<td style='padding: 10px; border: 1px solid #ddd; text-align: center;'>").append(detail.getAnswerScore() != null ? detail.getAnswerScore() : BigDecimal.ZERO).append("</td>");
String statusText = "1".equals(detail.getIsScored()) ? "已评分" : ("1".equals(detail.getIsSubjective()) ? "待评分" : "未评分");
// 判断状态优先检查isScored如果已评分则显示"已评分"否则根据是否为主观题显示"待评分""未评分"
String statusText;
String isScoredValue = detail.getIsScored();
String isSubjectiveValue = detail.getIsSubjective();
// 调试日志
System.out.println("状态判断 - isScored: [" + isScoredValue + "], isSubjective: [" + isSubjectiveValue + "], isSubjective(计算): " + isSubjective);
if ("1".equals(isScoredValue)) {
statusText = "已评分";
System.out.println("状态结果: 已评分");
} else if ("1".equals(isSubjectiveValue) || isSubjective) {
statusText = "待评分";
System.out.println("状态结果: 待评分");
} else {
statusText = "未评分";
System.out.println("状态结果: 未评分");
}
reportContent.append("<td style='padding: 10px; border: 1px solid #ddd;'>").append(statusText).append("</td>");
reportContent.append("</tr>");
}
@ -813,5 +864,188 @@ public class PsyQuestionnaireAnswerServiceImpl implements IPsyQuestionnaireAnswe
answerMapper.updateAnswer(answer);
}
}
/**
* 查询待评分的主观题列表
*/
@Override
public List<java.util.Map<String, Object>> selectPendingScoringList(PsyQuestionnaireAnswerDetail detail)
{
return detailMapper.selectPendingScoringList(detail);
}
/**
* 提交主观题评分
*/
@Override
@Transactional
public int submitScoring(Long detailId, BigDecimal score, String comment)
{
// 获取答案详情
PsyQuestionnaireAnswerDetail detail = detailMapper.selectDetailById(detailId);
if (detail == null)
{
return 0;
}
// 获取题目信息判断是否为主观题
PsyQuestionnaireItem item = itemMapper.selectItemById(detail.getItemId());
if (item == null)
{
return 0;
}
// 判断是否为主观题优先使用数据库字段如果为空则根据题目类型判断
boolean isSubjective = "1".equals(detail.getIsSubjective());
if (detail.getIsSubjective() == null || detail.getIsSubjective().isEmpty())
{
isSubjective = isSubjectiveType(item.getItemType());
// 更新数据库字段
detail.setIsSubjective(isSubjective ? "1" : "0");
}
// 验证是否为主观题
if (!isSubjective)
{
System.err.println("提交评分失败不是主观题detailId: " + detailId + ", itemType: " + item.getItemType());
return 0;
}
// 允许重新评分如果已评分允许修改
// 注释掉这个检查允许重新评分
// if ("1".equals(detail.getIsScored()))
// {
// System.err.println("提交评分失败已评分detailId: " + detailId);
// return 0;
// }
// 验证分数是否超过题目分值
if (item.getScore() != null)
{
if (score.compareTo(item.getScore()) > 0)
{
score = item.getScore(); // 超过题目分值设置为题目分值
}
}
// 更新答案详情
// 确保score不为null并且使用正确的精度
BigDecimal finalScore = score != null ? score : BigDecimal.ZERO;
detail.setAnswerScore(finalScore);
detail.setIsScored("1");
detail.setScoredBy(SecurityUtils.getUsername());
detail.setScoredTime(DateUtils.getNowDate());
// 确保isSubjective字段已设置
if (detail.getIsSubjective() == null || detail.getIsSubjective().isEmpty()) {
detail.setIsSubjective(isSubjective ? "1" : "0");
}
System.out.println("========== 提交评分开始 ==========");
System.out.println("detailId: " + detailId);
System.out.println("answerId: " + detail.getAnswerId());
System.out.println("score(参数): " + score);
System.out.println("finalScore(设置值): " + finalScore);
System.out.println("answerScore(对象设置前): " + detail.getAnswerScore());
System.out.println("isScored(设置前): " + detail.getIsScored());
System.out.println("isSubjective: " + detail.getIsSubjective());
// 再次确认设置
detail.setAnswerScore(finalScore);
detail.setIsScored("1");
System.out.println("answerScore(对象设置后): " + detail.getAnswerScore());
System.out.println("isScored(设置后): " + detail.getIsScored());
System.out.println("准备调用 updateDetail...");
int result = detailMapper.updateDetail(detail);
System.out.println("updateDetail 返回结果: " + result);
System.out.println("========== 提交评分结束 ==========");
// 立即验证更新是否成功重新查询数据库
if (result > 0) {
PsyQuestionnaireAnswerDetail verifyDetail = detailMapper.selectDetailById(detailId);
if (verifyDetail != null) {
System.out.println("验证更新结果 - detailId: " + detailId +
", answerScore(数据库): " + verifyDetail.getAnswerScore() +
", isScored(数据库): " + verifyDetail.getIsScored());
// 如果数据库中的分数不正确记录警告
if (verifyDetail.getAnswerScore() == null || verifyDetail.getAnswerScore().compareTo(score) != 0) {
System.err.println("警告:数据库中的分数与设置的分数不一致!设置的分数: " + score +
", 数据库中的分数: " + verifyDetail.getAnswerScore());
}
} else {
System.err.println("警告更新后无法查询到detaildetailId: " + detailId);
}
}
// 如果评分成功更新答题记录的总分
if (result > 0)
{
// 重新计算总分重新从数据库获取最新数据
List<PsyQuestionnaireAnswerDetail> allDetails = detailMapper.selectDetailListByAnswerId(detail.getAnswerId());
BigDecimal totalScore = BigDecimal.ZERO;
for (PsyQuestionnaireAnswerDetail d : allDetails)
{
if (d.getAnswerScore() != null)
{
totalScore = totalScore.add(d.getAnswerScore());
}
System.out.println("计算总分 - detailId: " + d.getDetailId() +
", answerScore: " + d.getAnswerScore() +
", isScored: " + d.getIsScored());
}
System.out.println("计算出的总分: " + totalScore);
// 更新答题记录
PsyQuestionnaireAnswer answer = answerMapper.selectAnswerById(detail.getAnswerId());
if (answer != null)
{
answer.setTotalScore(totalScore);
answer.setUpdateBy(SecurityUtils.getUsername());
answerMapper.updateAnswer(answer);
// 重新计算排名
updateRank(answer.getQuestionnaireId());
// 重新生成报告以反映最新的评分
// 注意由于在同一个事务中MyBatis一级缓存可能返回旧数据
// 这里先刷新一下确保获取最新数据
try {
// 强制刷新重新获取一次最新的detail数据确保isScored和answerScore已更新
// 清除MyBatis一级缓存强制从数据库查询
PsyQuestionnaireAnswerDetail latestDetail = detailMapper.selectDetailById(detailId);
if (latestDetail != null) {
System.out.println("重新获取detail - detailId: " + detailId +
", isScored: " + latestDetail.getIsScored() +
", answerScore: " + latestDetail.getAnswerScore() +
", answerScore类型: " + (latestDetail.getAnswerScore() != null ? latestDetail.getAnswerScore().getClass().getName() : "null"));
// 如果获取的数据不正确记录警告
if (latestDetail.getAnswerScore() == null || latestDetail.getAnswerScore().compareTo(score) != 0) {
System.err.println("严重警告重新获取的detail分数不正确期望分数: " + score +
", 实际分数: " + latestDetail.getAnswerScore());
}
} else {
System.err.println("警告无法重新获取detaildetailId: " + detailId);
}
// 生成报告会重新从数据库获取所有数据
generateQuestionnaireReport(detail.getAnswerId());
System.out.println("评分后重新生成报告成功answerId: " + detail.getAnswerId());
} catch (Exception e) {
// 报告生成失败不影响评分但记录错误
System.err.println("评分后重新生成报告失败answerId: " + detail.getAnswerId());
System.err.println("错误信息: " + e.getMessage());
e.printStackTrace();
}
}
}
return result;
}
}

View File

@ -3,8 +3,13 @@ package com.ddnai.system.service.impl.psychology;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ddnai.system.domain.psychology.PsyQuestionnaire;
import com.ddnai.system.domain.psychology.PsyQuestionnaireAnswer;
import com.ddnai.system.mapper.psychology.PsyQuestionnaireMapper;
import com.ddnai.system.mapper.psychology.PsyQuestionnaireAnswerMapper;
import com.ddnai.system.mapper.psychology.PsyQuestionnaireAnswerDetailMapper;
import com.ddnai.system.mapper.psychology.PsyQuestionnaireReportMapper;
import com.ddnai.system.service.psychology.IPsyQuestionnaireService;
/**
@ -17,6 +22,15 @@ public class PsyQuestionnaireServiceImpl implements IPsyQuestionnaireService
{
@Autowired
private PsyQuestionnaireMapper questionnaireMapper;
@Autowired
private PsyQuestionnaireAnswerMapper answerMapper;
@Autowired
private PsyQuestionnaireAnswerDetailMapper detailMapper;
@Autowired
private PsyQuestionnaireReportMapper reportMapper;
/**
* 查询问卷信息
@ -73,9 +87,92 @@ public class PsyQuestionnaireServiceImpl implements IPsyQuestionnaireService
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteQuestionnaireByIds(Long[] questionnaireIds)
{
return questionnaireMapper.deleteQuestionnaireByIds(questionnaireIds);
try {
System.out.println("开始删除问卷问卷ID数量: " + questionnaireIds.length);
// 在删除问卷前先删除相关的答题记录答案详情和报告
for (Long questionnaireId : questionnaireIds)
{
System.out.println("处理问卷ID: " + questionnaireId);
// 1. 查询该问卷的所有答题记录
PsyQuestionnaireAnswer query = new PsyQuestionnaireAnswer();
query.setQuestionnaireId(questionnaireId);
List<PsyQuestionnaireAnswer> answers = answerMapper.selectAnswerList(query);
System.out.println("查询到答题记录数量: " + (answers != null ? answers.size() : 0));
if (answers != null && !answers.isEmpty())
{
Long[] answerIds = answers.stream()
.map(PsyQuestionnaireAnswer::getAnswerId)
.toArray(Long[]::new);
System.out.println("准备删除答题记录,数量: " + answerIds.length);
// 2. 删除每个答题记录对应的报告
int reportCount = 0;
for (PsyQuestionnaireAnswer answer : answers)
{
try {
com.ddnai.system.domain.psychology.PsyQuestionnaireReport report =
reportMapper.selectReportByAnswerId(answer.getAnswerId());
if (report != null)
{
int deleteResult = reportMapper.deleteReportById(report.getReportId());
if (deleteResult > 0) {
reportCount++;
System.out.println("删除报告成功reportId: " + report.getReportId());
} else {
System.err.println("删除报告失败reportId: " + report.getReportId());
}
}
} catch (Exception e) {
System.err.println("删除报告时发生异常answerId: " + answer.getAnswerId());
System.err.println("异常信息: " + e.getMessage());
e.printStackTrace();
throw new RuntimeException("删除问卷报告失败: " + e.getMessage(), e);
}
}
System.out.println("共删除报告数量: " + reportCount);
// 3. 删除答案详情必须先删除因为有外键约束
try {
int detailResult = detailMapper.deleteDetailByAnswerIds(answerIds);
System.out.println("删除答案详情结果: " + detailResult);
} catch (Exception e) {
System.err.println("删除答案详情时发生异常");
System.err.println("异常信息: " + e.getMessage());
e.printStackTrace();
throw new RuntimeException("删除答案详情失败: " + e.getMessage(), e);
}
// 4. 删除答题记录
try {
int answerResult = answerMapper.deleteAnswerByIds(answerIds);
System.out.println("删除答题记录结果: " + answerResult);
} catch (Exception e) {
System.err.println("删除答题记录时发生异常");
System.err.println("异常信息: " + e.getMessage());
e.printStackTrace();
throw new RuntimeException("删除答题记录失败: " + e.getMessage(), e);
}
}
}
// 5. 最后删除问卷本身
int result = questionnaireMapper.deleteQuestionnaireByIds(questionnaireIds);
System.out.println("删除问卷结果: " + result);
return result;
} catch (Exception e) {
System.err.println("删除问卷时发生异常");
System.err.println("异常信息: " + e.getMessage());
e.printStackTrace();
throw new RuntimeException("删除问卷失败: " + e.getMessage(), e);
}
}
/**

View File

@ -8,6 +8,7 @@ import com.ddnai.common.utils.SecurityUtils;
import com.ddnai.system.domain.psychology.PsyScalePermission;
import com.ddnai.system.mapper.psychology.PsyScalePermissionMapper;
import com.ddnai.system.service.psychology.IPsyScalePermissionService;
import com.ddnai.system.service.psychology.IPsyScaleService;
/**
* 量表权限 服务层实现
@ -19,6 +20,9 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
{
@Autowired
private PsyScalePermissionMapper permissionMapper;
@Autowired
private IPsyScaleService scaleService;
/**
* 查询量表权限信息
@ -84,6 +88,16 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
@Override
public int insertPermission(PsyScalePermission permission)
{
// 验证 scale_id 是否存在
if (permission.getScaleId() != null)
{
com.ddnai.system.domain.psychology.PsyScale scale = scaleService.selectScaleById(permission.getScaleId());
if (scale == null)
{
throw new RuntimeException("量表不存在scaleId: " + permission.getScaleId());
}
}
if (permission.getStatus() == null)
{
permission.setStatus("0");
@ -100,6 +114,16 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
@Override
public int updatePermission(PsyScalePermission permission)
{
// 验证 scale_id 是否存在
if (permission.getScaleId() != null)
{
com.ddnai.system.domain.psychology.PsyScale scale = scaleService.selectScaleById(permission.getScaleId());
if (scale == null)
{
throw new RuntimeException("量表不存在scaleId: " + permission.getScaleId());
}
}
return permissionMapper.updatePermission(permission);
}
@ -160,6 +184,14 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService
String username = SecurityUtils.getUsername();
for (Long scaleId : scaleIds)
{
// 验证 scale_id 是否存在
com.ddnai.system.domain.psychology.PsyScale scale = scaleService.selectScaleById(scaleId);
if (scale == null)
{
System.err.println("警告量表不存在跳过该权限分配scaleId: " + scaleId);
continue; // 跳过不存在的量表
}
PsyScalePermission permission = new PsyScalePermission();
permission.setUserId(userId);
permission.setScaleId(scaleId);

View File

@ -81,5 +81,23 @@ public interface IPsyQuestionnaireAnswerService
* @param answerId 答题ID
*/
public void generateReport(Long answerId);
/**
* 查询待评分的主观题列表
*
* @param detail 查询条件
* @return 待评分题目列表包含题目信息答案信息等
*/
public List<java.util.Map<String, Object>> selectPendingScoringList(PsyQuestionnaireAnswerDetail detail);
/**
* 提交主观题评分
*
* @param detailId 答案详情ID
* @param score 得分
* @param comment 评语可选
* @return 结果
*/
public int submitScoring(Long detailId, java.math.BigDecimal score, String comment);
}

View File

@ -72,7 +72,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="optionId != null">option_id = #{optionId}, </if>
<if test="optionIds != null">option_ids = #{optionIds}, </if>
<if test="answerText != null">answer_text = #{answerText}, </if>
<if test="answerScore != null">answer_score = #{answerScore}, </if>
<!-- 对于answerScore使用choose确保即使为0或小数也能更新但null时不更新 -->
<choose>
<when test="answerScore != null">
answer_score = #{answerScore,jdbcType=DECIMAL},
</when>
</choose>
<if test="isSubjective != null and isSubjective != ''">is_subjective = #{isSubjective}, </if>
<if test="isScored != null and isScored != ''">is_scored = #{isScored}, </if>
<if test="scoredBy != null and scoredBy != ''">scored_by = #{scoredBy}, </if>
@ -92,6 +97,46 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{detailId}
</foreach>
</delete>
<delete id="deleteDetailByAnswerIds" parameterType="String">
delete from psy_questionnaire_answer_detail where answer_id in
<foreach item="answerId" collection="array" open="(" separator="," close=")">
#{answerId}
</foreach>
</delete>
<!-- 查询待评分的主观题列表(关联题目、答题记录、问卷信息) -->
<select id="selectPendingScoringList" parameterType="com.ddnai.system.domain.psychology.PsyQuestionnaireAnswerDetail" resultType="java.util.Map">
select
d.detail_id as detailId,
d.answer_id as answerId,
d.item_id as itemId,
d.answer_text as answerText,
d.answer_score as answerScore,
d.is_scored as isScored,
d.scored_by as scoredBy,
d.scored_time as scoredTime,
i.item_number as itemNumber,
i.item_content as itemContent,
i.item_type as itemType,
i.score as itemScore,
a.questionnaire_id as questionnaireId,
a.respondent_name as respondentName,
a.submit_time as submitTime,
q.questionnaire_name as questionnaireName
from psy_questionnaire_answer_detail d
inner join psy_questionnaire_item i on d.item_id = i.item_id
inner join psy_questionnaire_answer a on d.answer_id = a.answer_id
inner join psy_questionnaire q on a.questionnaire_id = q.questionnaire_id
where d.is_subjective = '1' and d.is_scored = '0'
<if test="answerId != null">
and d.answer_id = #{answerId}
</if>
<if test="itemId != null">
and d.item_id = #{itemId}
</if>
order by a.submit_time desc, i.item_number asc
</select>
</mapper>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,149 @@
-- 添加主观题评分菜单
-- 菜单类型C菜单
-- 父菜单ID2009心理测评管理
-- 排序在问卷管理2041order_num=8之后设置为13在量表权限管理之后
-- 设置字符集(确保中文正确显示)
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
-- 添加主观题评分菜单(菜单项)
-- 注意menu_id需要根据实际情况调整这里使用AUTO_INCREMENT自动生成
INSERT INTO `sys_menu` (
`menu_name`,
`parent_id`,
`order_num`,
`path`,
`component`,
`query`,
`route_name`,
`is_frame`,
`is_cache`,
`menu_type`,
`visible`,
`status`,
`perms`,
`icon`,
`create_by`,
`create_time`,
`remark`
) VALUES (
'主观题评分', -- 菜单名称
2009, -- 父菜单ID心理测评管理
13, -- 显示顺序在量表权限管理order_num=12之后
'questionnaire/scoring', -- 路由地址
'psychology/questionnaire/scoring', -- 组件路径
NULL, -- 路由参数
'QuestionnaireScoring', -- 路由名称
1, -- 是否为外链1否
0, -- 是否缓存0缓存
'C', -- 菜单类型C菜单
'0', -- 菜单状态0显示
'0', -- 菜单状态0正常
'psychology:questionnaire:score', -- 权限标识
'edit', -- 菜单图标
'admin', -- 创建者
NOW(), -- 创建时间
'主观题评分菜单' -- 备注
);
-- 添加主观题评分的按钮权限
-- 评分查询使用子查询获取刚插入的菜单ID
INSERT INTO `sys_menu` (
`menu_name`,
`parent_id`,
`order_num`,
`path`,
`component`,
`query`,
`route_name`,
`is_frame`,
`is_cache`,
`menu_type`,
`visible`,
`status`,
`perms`,
`icon`,
`create_by`,
`create_time`,
`remark`
)
SELECT
'评分查询',
menu_id,
1,
'',
'',
NULL,
'',
1,
0,
'F',
'0',
'0',
'psychology:questionnaire:score:query',
'#',
'admin',
NOW(),
''
FROM sys_menu
WHERE menu_name = '主观题评分' AND parent_id = 2009
LIMIT 1;
-- 评分操作
INSERT INTO `sys_menu` (
`menu_name`,
`parent_id`,
`order_num`,
`path`,
`component`,
`query`,
`route_name`,
`is_frame`,
`is_cache`,
`menu_type`,
`visible`,
`status`,
`perms`,
`icon`,
`create_by`,
`create_time`,
`remark`
)
SELECT
'评分操作',
menu_id,
2,
'',
'',
NULL,
'',
1,
0,
'F',
'0',
'0',
'psychology:questionnaire:score',
'#',
'admin',
NOW(),
''
FROM sys_menu
WHERE menu_name = '主观题评分' AND parent_id = 2009
LIMIT 1;
-- 为管理员角色role_id=1添加菜单权限
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
SELECT 1, menu_id FROM sys_menu WHERE menu_name = '主观题评分' AND parent_id = 2009
ON DUPLICATE KEY UPDATE role_id = role_id;
-- 为管理员角色添加按钮权限
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
SELECT 1, m2.menu_id
FROM sys_menu m1
INNER JOIN sys_menu m2 ON m2.parent_id = m1.menu_id
WHERE m1.menu_name = '主观题评分' AND m1.parent_id = 2009
ON DUPLICATE KEY UPDATE role_id = role_id;

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,139 @@
# 主观题评分功能使用说明
## 功能说明
主观题评分功能允许管理员对问卷中的主观题(简答题、问答题、作文题)进行手动评分。
## 添加菜单方法
### 方法一执行SQL脚本推荐
1. 打开数据库管理工具如Navicat、MySQL Workbench等
2. 连接到数据库
3. 执行 `sql/添加主观题评分菜单.sql` 文件中的SQL语句
4. 刷新浏览器页面,菜单应该会出现在"心理测评管理"下
### 方法二:通过菜单管理页面手动添加
1. 登录系统,进入 **系统管理** -> **菜单管理**
2. 找到 **心理测评管理**菜单ID2009
3. 点击 **新增** 按钮
4. 填写以下信息:
- **菜单名称**:主观题评分
- **父菜单**:心理测评管理
- **显示顺序**13
- **路由地址**questionnaire/scoring
- **组件路径**psychology/questionnaire/scoring
- **路由名称**QuestionnaireScoring
- **菜单类型**:菜单
- **菜单图标**edit
- **权限标识**psychology:questionnaire:score
- **是否显示**:显示
- **状态**:正常
5. 点击 **确定** 保存
6. 添加按钮权限(可选):
- 在菜单管理中找到刚创建的"主观题评分"菜单
- 点击 **新增** 添加按钮权限:
- **菜单名称**:评分查询
- **权限标识**psychology:questionnaire:score:query
- **菜单类型**:按钮
- 再添加一个:
- **菜单名称**:评分操作
- **权限标识**psychology:questionnaire:score
- **菜单类型**:按钮
7. 分配菜单权限:
- 进入 **系统管理** -> **角色管理**
- 找到 **管理员** 角色,点击 **修改**
- 在 **菜单权限** 中勾选 **主观题评分** 及其按钮权限
- 点击 **确定** 保存
## 使用方法
### 1. 访问主观题评分页面
- 方法一:在左侧菜单中找到 **心理测评管理** -> **主观题评分**
- 方法二直接访问URL`/psychology/questionnaire/scoring`
### 2. 查看待评分题目
页面会自动显示所有待评分的主观题,包括:
- 问卷名称
- 答题人
- 题目序号和内容
- 题目类型(简答题/问答题/作文题)
- 题目分值
- 答案内容
- 提交时间
### 3. 单个评分
1. 在列表中点击 **评分** 按钮
2. 在弹出的对话框中:
- 查看题目内容和答案内容
- 输入得分(不能超过题目分值)
- 可选:输入评语
3. 点击 **确定** 保存评分
### 4. 批量评分
1. 在列表中勾选多个待评分的题目
2. 点击 **批量评分** 按钮
3. 在批量评分对话框中为每个题目输入得分和评语
4. 点击 **确定** 批量保存
### 5. 搜索和筛选
- 可以通过 **问卷名称** 搜索
- 可以通过 **答题人** 搜索
## 功能特点
1. **自动识别主观题**系统自动识别简答题text、问答题textarea、作文题essay三种类型
2. **分数验证**:评分不能超过题目分值,系统会自动限制
3. **自动更新总分**:评分后自动重新计算答题记录的总分
4. **自动更新排名**:评分后自动重新计算该问卷的排名
5. **批量评分**:支持同时为多个题目评分,提高效率
6. **评语功能**:可以为每个答案添加评语(可选)
## 注意事项
1. 只有已提交的问卷中的主观题才会出现在待评分列表中
2. 已经评分的题目不会出现在待评分列表中
3. 评分后,用户查看报告时会看到更新后的成绩
4. 如果评分失败,请检查:
- 是否有评分权限(`psychology:questionnaire:score`
- 题目是否为主观题且未评分
- 得分是否超过题目分值
## 权限配置
需要的权限标识:
- `psychology:questionnaire:score` - 评分操作权限
- `psychology:questionnaire:score:query` - 查询权限(可选)
## 常见问题
### Q: 菜单中没有显示"主观题评分"
A: 需要执行SQL脚本或通过菜单管理页面手动添加菜单并确保已分配菜单权限给当前角色。
### Q: 点击菜单后页面空白?
A: 检查:
1. 路由配置是否正确(`ruoyi-ui/src/router/index.js`
2. 页面文件是否存在(`ruoyi-ui/src/views/psychology/questionnaire/scoring.vue`
3. 浏览器控制台是否有错误信息
### Q: 没有待评分的数据?
A: 检查:
1. 是否有已提交的问卷答题记录
2. 问卷中是否包含主观题text、textarea、essay类型
3. 主观题是否已经被评分过(已评分的不会显示)
### Q: 评分后总分没有更新?
A: 系统会自动更新,如果未更新,请:
1. 刷新页面查看
2. 检查后端日志是否有错误
3. 确认评分是否成功(查看操作日志)