量表导入功能实现
This commit is contained in:
parent
f4cef1779c
commit
6473c94e1c
4621
SCL90症状自评量表.json
Normal file
4621
SCL90症状自评量表.json
Normal file
File diff suppressed because it is too large
Load Diff
21
pom.xml
21
pom.xml
|
|
@ -28,6 +28,7 @@
|
|||
<oshi.version>6.8.3</oshi.version>
|
||||
<commons.io.version>2.19.0</commons.io.version>
|
||||
<poi.version>4.1.2</poi.version>
|
||||
<pdfbox.version>2.0.29</pdfbox.version>
|
||||
<velocity.version>2.3</velocity.version>
|
||||
<jwt.version>0.9.1</jwt.version>
|
||||
<zxing.version>3.5.1</zxing.version>
|
||||
|
|
@ -156,6 +157,26 @@
|
|||
<version>${poi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- POI Word文档解析 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-scratchpad</artifactId>
|
||||
<version>${poi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- PDF解析工具 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox</artifactId>
|
||||
<version>${pdfbox.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox-tools</artifactId>
|
||||
<version>${pdfbox.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- velocity代码生成使用模板 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ export function getPermission(permissionId) {
|
|||
|
||||
// 根据用户ID获取用户有权限访问的量表ID列表
|
||||
export function getUserScaleIds(userId) {
|
||||
if (!userId || userId === 'undefined' || userId === 'null' || isNaN(userId)) {
|
||||
return Promise.reject(new Error('用户ID无效'));
|
||||
}
|
||||
return request({
|
||||
url: '/psychology/permission/user/' + userId + '/scales',
|
||||
method: 'get'
|
||||
|
|
@ -61,12 +64,15 @@ export function delPermission(permissionIds) {
|
|||
|
||||
// 批量分配用户量表权限
|
||||
export function assignUserScales(userId, scaleIds) {
|
||||
if (!userId || userId === 'undefined' || userId === 'null' || isNaN(userId)) {
|
||||
return Promise.reject(new Error('用户ID无效'));
|
||||
}
|
||||
return request({
|
||||
url: '/psychology/permission/assign',
|
||||
method: 'post',
|
||||
data: {
|
||||
userId: userId,
|
||||
scaleIds: scaleIds
|
||||
scaleIds: scaleIds || []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,3 +43,54 @@ export function delScale(scaleId) {
|
|||
})
|
||||
}
|
||||
|
||||
// 导入量表(JSON格式)
|
||||
export function importScale(data) {
|
||||
return request({
|
||||
url: '/psychology/scale/importJson',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 导入量表(文件格式)
|
||||
export function importScaleFile(file) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return request({
|
||||
url: '/psychology/scale/import',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 预览文档解析结果
|
||||
export function previewDocument(file) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return request({
|
||||
url: '/psychology/scale/preview',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 提取文档文本
|
||||
export function extractText(file) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return request({
|
||||
url: '/psychology/scale/extract-text',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -190,7 +190,8 @@ export default {
|
|||
},
|
||||
/** 查看报告按钮操作 */
|
||||
handleView(row) {
|
||||
this.$router.push({ path: 'assessment/report', query: { assessmentId: row.assessmentId } });
|
||||
// 跳转到报告详情页面,使用测评ID查询报告
|
||||
this.$router.push({ path: '/psychology/report/detail', query: { assessmentId: row.assessmentId } });
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
|
|
|
|||
|
|
@ -110,17 +110,36 @@ export default {
|
|||
methods: {
|
||||
/** 加载量表列表 */
|
||||
loadScales() {
|
||||
// 获取当前登录用户ID
|
||||
const userId = this.$store.getters.userId;
|
||||
// 获取当前登录用户ID和角色
|
||||
const userId = this.$store.getters.id;
|
||||
const roles = this.$store.getters.roles || [];
|
||||
|
||||
// 判断是否是管理员:userId === 1 或者 roles 中包含 'admin'
|
||||
const isAdmin = userId === 1 || (roles && roles.includes('admin'));
|
||||
|
||||
// 如果是管理员,显示所有量表;否则只显示有权限的量表
|
||||
if (userId === 1) {
|
||||
if (isAdmin) {
|
||||
// 管理员显示所有量表
|
||||
listScale({ status: '0' }).then(response => {
|
||||
this.scaleList = response.rows.filter(scale => scale.itemCount > 0);
|
||||
}).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 => {
|
||||
this.scaleList = response.rows.filter(scale => scale.itemCount > 0);
|
||||
}).catch(error => {
|
||||
console.error("加载量表列表失败:", error);
|
||||
this.$message.error('加载量表列表失败,请稍后重试');
|
||||
});
|
||||
return;
|
||||
}
|
||||
import('@/api/psychology/permission').then(module => {
|
||||
module.getUserScaleIds(userId).then(permissionResponse => {
|
||||
const allowedScaleIds = permissionResponse.data || [];
|
||||
|
|
@ -134,7 +153,13 @@ export default {
|
|||
this.scaleList = response.rows.filter(scale =>
|
||||
scale.itemCount > 0 && allowedScaleIds.includes(scale.scaleId)
|
||||
);
|
||||
}).catch(error => {
|
||||
console.error("加载量表列表失败:", error);
|
||||
this.$message.error('加载量表列表失败,请稍后重试');
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error("加载用户量表权限失败:", error);
|
||||
this.$message.error('加载量表权限失败,请稍后重试');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,17 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<el-dropdown v-if="isAdmin" @command="handleQuickFill" style="margin-right: 10px;">
|
||||
<el-button type="info" icon="el-icon-s-promotion">
|
||||
快速填充<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="first">填充第一个选项并提交</el-dropdown-item>
|
||||
<el-dropdown-item command="middle">填充中间选项并提交</el-dropdown-item>
|
||||
<el-dropdown-item command="last">填充最后一个选项并提交</el-dropdown-item>
|
||||
<el-dropdown-item command="random">随机填充并提交</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<el-button type="warning" @click="handlePause">暂停测评</el-button>
|
||||
<el-button @click="handleExit">退出</el-button>
|
||||
</div>
|
||||
|
|
@ -122,6 +133,12 @@ export default {
|
|||
return false;
|
||||
}
|
||||
return this.answeredCount === this.itemList.length;
|
||||
},
|
||||
// 判断是否为管理员
|
||||
isAdmin() {
|
||||
const userId = this.$store.getters.id;
|
||||
const roles = this.$store.getters.roles || [];
|
||||
return userId === 1 || (roles && roles.includes('admin'));
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
|
@ -190,7 +207,14 @@ export default {
|
|||
loadAllOptions() {
|
||||
const promises = this.itemList.map(item => {
|
||||
return listOption(item.itemId).then(response => {
|
||||
this.$set(this.optionMap, item.itemId, response.data || []);
|
||||
const options = response.data || [];
|
||||
// 按sortOrder排序,确保选项顺序正确
|
||||
options.sort((a, b) => {
|
||||
const orderA = a.sortOrder || 0;
|
||||
const orderB = b.sortOrder || 0;
|
||||
return orderA - orderB;
|
||||
});
|
||||
this.$set(this.optionMap, item.itemId, options);
|
||||
});
|
||||
});
|
||||
return Promise.all(promises);
|
||||
|
|
@ -312,6 +336,152 @@ export default {
|
|||
this.$modal.msgError(error.msg || "提交失败,请重试");
|
||||
});
|
||||
});
|
||||
},
|
||||
/** 快速填充所有题目并提交(管理员功能) */
|
||||
handleQuickFill(command) {
|
||||
if (!this.isAdmin) {
|
||||
this.$modal.msgError("您没有权限执行此操作");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查所有选项是否已加载
|
||||
const hasAllOptions = this.itemList.every(item => {
|
||||
const options = this.optionMap[item.itemId];
|
||||
return options && options.length > 0;
|
||||
});
|
||||
|
||||
if (!hasAllOptions) {
|
||||
this.$modal.msgWarning("正在加载题目选项,请稍候...");
|
||||
// 等待选项加载完成
|
||||
this.loadAllOptions().then(() => {
|
||||
this.performQuickFill(command);
|
||||
}).catch(error => {
|
||||
console.error('加载选项失败:', error);
|
||||
this.$modal.msgError("加载题目选项失败,请重试");
|
||||
});
|
||||
} else {
|
||||
this.performQuickFill(command);
|
||||
}
|
||||
},
|
||||
/** 执行快速填充 */
|
||||
performQuickFill(command) {
|
||||
let optionStrategy = null;
|
||||
let strategyName = '';
|
||||
|
||||
switch (command) {
|
||||
case 'first':
|
||||
optionStrategy = (options) => options.length > 0 ? options[0] : null;
|
||||
strategyName = '第一个选项';
|
||||
break;
|
||||
case 'middle':
|
||||
optionStrategy = (options) => {
|
||||
if (options.length === 0) return null;
|
||||
const middleIndex = Math.floor((options.length - 1) / 2);
|
||||
return options[middleIndex];
|
||||
};
|
||||
strategyName = '中间选项';
|
||||
break;
|
||||
case 'last':
|
||||
optionStrategy = (options) => options.length > 0 ? options[options.length - 1] : null;
|
||||
strategyName = '最后一个选项';
|
||||
break;
|
||||
case 'random':
|
||||
optionStrategy = (options) => {
|
||||
if (options.length === 0) return null;
|
||||
const randomIndex = Math.floor(Math.random() * options.length);
|
||||
return options[randomIndex];
|
||||
};
|
||||
strategyName = '随机选项';
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
this.$modal.confirm(`确定要用"${strategyName}"填充所有题目并提交吗?此操作不可撤销。`).then(() => {
|
||||
this.loading = true;
|
||||
this.$modal.loading("正在快速填充所有题目...");
|
||||
|
||||
// 遍历所有题目,填充答案
|
||||
const fillPromises = [];
|
||||
|
||||
for (const item of this.itemList) {
|
||||
const options = this.optionMap[item.itemId] || [];
|
||||
if (options.length === 0) {
|
||||
console.warn(`题目 ${item.itemId} 没有选项,跳过`);
|
||||
continue; // 跳过没有选项的题目
|
||||
}
|
||||
|
||||
if (item.itemType === 'single' || item.itemType === 'matrix') {
|
||||
// 单选题或矩阵题,选择单个选项
|
||||
const selectedOption = optionStrategy(options);
|
||||
if (selectedOption) {
|
||||
const answer = {
|
||||
assessmentId: this.assessmentId,
|
||||
itemId: item.itemId,
|
||||
optionId: selectedOption.optionId,
|
||||
optionIds: null,
|
||||
answerScore: selectedOption.optionScore || 0
|
||||
};
|
||||
this.$set(this.answersMap, item.itemId, answer);
|
||||
fillPromises.push(this.saveAnswerToServerPromise(answer));
|
||||
}
|
||||
} else if (item.itemType === 'multiple') {
|
||||
// 多选题,选择第一个选项(可以根据需要调整)
|
||||
const selectedOption = optionStrategy(options);
|
||||
if (selectedOption) {
|
||||
const selectedOptions = [selectedOption.optionId];
|
||||
const totalScore = selectedOption.optionScore || 0;
|
||||
|
||||
const answer = {
|
||||
assessmentId: this.assessmentId,
|
||||
itemId: item.itemId,
|
||||
optionId: null,
|
||||
optionIds: selectedOptions.join(','),
|
||||
answerScore: totalScore
|
||||
};
|
||||
this.$set(this.answersMap, item.itemId, answer);
|
||||
fillPromises.push(this.saveAnswerToServerPromise(answer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fillPromises.length === 0) {
|
||||
this.$modal.closeLoading();
|
||||
this.loading = false;
|
||||
this.$modal.msgError("没有可填充的题目");
|
||||
return;
|
||||
}
|
||||
|
||||
// 等待所有答案保存完成
|
||||
Promise.all(fillPromises).then(() => {
|
||||
this.$modal.closeLoading();
|
||||
this.$modal.msgSuccess(`已填充 ${fillPromises.length} 道题目,正在提交测评...`);
|
||||
|
||||
// 提交测评
|
||||
submitAssessment(this.assessmentId).then(response => {
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(response.msg || "测评已提交,报告已生成");
|
||||
this.$router.push('/psychology/assessment');
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
this.$modal.msgError(error.msg || "提交失败,请重试");
|
||||
});
|
||||
}).catch(error => {
|
||||
this.$modal.closeLoading();
|
||||
this.loading = false;
|
||||
console.error('快速填充失败:', error);
|
||||
this.$modal.msgError("快速填充失败,请重试");
|
||||
});
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
});
|
||||
},
|
||||
/** 保存答案到服务器(返回Promise) */
|
||||
saveAnswerToServerPromise(answer) {
|
||||
return saveAnswer(answer).catch(error => {
|
||||
console.error('保存答案失败:', error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
|
|||
|
|
@ -54,11 +54,17 @@ export default {
|
|||
},
|
||||
created() {
|
||||
const userId = this.$route.params.userId;
|
||||
if (userId) {
|
||||
this.userId = parseInt(userId);
|
||||
if (userId && userId !== 'undefined' && userId !== 'null') {
|
||||
const parsedUserId = parseInt(userId);
|
||||
if (!isNaN(parsedUserId) && parsedUserId > 0) {
|
||||
this.userId = parsedUserId;
|
||||
this.loadUserInfo();
|
||||
this.loadScales();
|
||||
this.loadUserScales();
|
||||
} else {
|
||||
this.$modal.msgError("用户ID参数无效");
|
||||
this.handleBack();
|
||||
}
|
||||
} else {
|
||||
this.$modal.msgError("缺少用户ID参数");
|
||||
this.handleBack();
|
||||
|
|
@ -83,12 +89,23 @@ export default {
|
|||
},
|
||||
/** 加载用户已有权限的量表 */
|
||||
loadUserScales() {
|
||||
if (!this.userId || isNaN(this.userId)) {
|
||||
this.$modal.msgError("用户ID无效,无法加载权限");
|
||||
return;
|
||||
}
|
||||
getUserScaleIds(this.userId).then(response => {
|
||||
this.selectedScaleIds = response.data || [];
|
||||
}).catch(error => {
|
||||
console.error("加载用户量表权限失败:", error);
|
||||
this.$modal.msgError("加载用户量表权限失败");
|
||||
});
|
||||
},
|
||||
/** 提交表单 */
|
||||
submitForm() {
|
||||
if (!this.userId || isNaN(this.userId)) {
|
||||
this.$modal.msgError("用户ID无效,无法保存");
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
assignUserScales(this.userId, this.selectedScaleIds).then(response => {
|
||||
this.$modal.msgSuccess("分配成功");
|
||||
|
|
|
|||
|
|
@ -54,6 +54,16 @@
|
|||
v-hasPermi="['psychology:scale:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
icon="el-icon-upload2"
|
||||
size="mini"
|
||||
@click="handleImport"
|
||||
v-hasPermi="['psychology:scale:add']"
|
||||
>导入</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
|
|
@ -254,11 +264,68 @@
|
|||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 导入对话框 -->
|
||||
<el-dialog :title="importTitle" :visible.sync="importOpen" width="800px" append-to-body>
|
||||
<el-alert
|
||||
title="导入说明"
|
||||
type="info"
|
||||
:closable="false"
|
||||
style="margin-bottom: 20px;"
|
||||
>
|
||||
<div slot="description">
|
||||
<p>1. 支持的文件格式:JSON、PDF、DOCX、DOC</p>
|
||||
<p>2. PDF和DOCX文件会自动解析题目、选项等信息</p>
|
||||
<p>3. 建议先使用"预览解析结果"查看识别效果,确认无误后再导入</p>
|
||||
<p>4. 请确保量表编码唯一,否则导入将失败</p>
|
||||
<p>5. 题目数量会自动计算,无需手动填写</p>
|
||||
</div>
|
||||
</el-alert>
|
||||
<el-tabs v-model="importTab" type="border-card">
|
||||
<el-tab-pane label="JSON文本" name="text">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="20"
|
||||
v-model="importJsonText"
|
||||
placeholder="请粘贴JSON格式的量表数据"
|
||||
></el-input>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="文件上传" name="file">
|
||||
<el-upload
|
||||
ref="upload"
|
||||
:limit="1"
|
||||
accept=".json,.pdf,.docx,.doc"
|
||||
:disabled="upload.isUploading"
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
drag
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<div class="el-upload__tip" slot="tip">支持JSON、PDF、DOCX、DOC格式文件,系统会自动识别并解析</div>
|
||||
</el-upload>
|
||||
<div v-if="upload.selectedFile" style="margin-top: 10px;">
|
||||
<el-tag type="info">已选择文件: {{ upload.selectedFile.name }}</el-tag>
|
||||
<el-button
|
||||
v-if="isDocumentFile(upload.selectedFile.name)"
|
||||
type="text"
|
||||
size="mini"
|
||||
@click="handlePreview"
|
||||
style="margin-left: 10px;"
|
||||
>预览解析结果</el-button>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitImport">确 定</el-button>
|
||||
<el-button @click="cancelImport">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listScale, getScale, delScale, addScale, updateScale } from "@/api/psychology/scale"
|
||||
import { listScale, getScale, delScale, addScale, updateScale, importScale, importScaleFile, previewDocument } from "@/api/psychology/scale"
|
||||
|
||||
export default {
|
||||
name: "PsyScale",
|
||||
|
|
@ -283,6 +350,19 @@ export default {
|
|||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 导入对话框
|
||||
importOpen: false,
|
||||
importTitle: "导入量表",
|
||||
importTab: "text",
|
||||
importJsonText: "",
|
||||
upload: {
|
||||
// 是否显示上传进度
|
||||
isUploading: false,
|
||||
// 选中的文件
|
||||
selectedFile: null,
|
||||
// 文件内容
|
||||
fileContent: null
|
||||
},
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
|
|
@ -450,6 +530,156 @@ export default {
|
|||
handleManageFactors(row) {
|
||||
const scaleId = row.scaleId
|
||||
this.$router.push({ path: '/psychology/scale/factor', query: { scaleId: scaleId, scaleName: row.scaleName } })
|
||||
},
|
||||
/** 导入按钮操作 */
|
||||
handleImport() {
|
||||
this.importOpen = true
|
||||
this.importTab = "text"
|
||||
this.importJsonText = ""
|
||||
},
|
||||
/** 取消导入 */
|
||||
cancelImport() {
|
||||
this.importOpen = false
|
||||
this.importJsonText = ""
|
||||
this.upload.selectedFile = null
|
||||
this.upload.fileContent = null
|
||||
if (this.$refs.upload) {
|
||||
this.$refs.upload.clearFiles()
|
||||
}
|
||||
},
|
||||
/** 提交导入 */
|
||||
submitImport() {
|
||||
if (this.importTab === "text") {
|
||||
if (!this.importJsonText || this.importJsonText.trim() === "") {
|
||||
this.$modal.msgWarning("请输入JSON数据")
|
||||
return
|
||||
}
|
||||
try {
|
||||
const importData = JSON.parse(this.importJsonText)
|
||||
importScale(importData).then(response => {
|
||||
this.$modal.msgSuccess(response.msg || "导入成功")
|
||||
this.importOpen = false
|
||||
this.importJsonText = ""
|
||||
this.getList()
|
||||
}).catch(error => {
|
||||
this.$modal.msgError(error.msg || "导入失败")
|
||||
})
|
||||
} catch (e) {
|
||||
this.$modal.msgError("JSON格式错误:" + e.message)
|
||||
}
|
||||
} else {
|
||||
// 文件上传模式
|
||||
if (!this.upload.selectedFile) {
|
||||
this.$modal.msgWarning("请先选择要上传的文件")
|
||||
return
|
||||
}
|
||||
|
||||
// 判断文件类型
|
||||
const fileName = this.upload.selectedFile.name || ""
|
||||
if (this.isDocumentFile(fileName)) {
|
||||
// PDF/DOCX/DOC文件,直接导入
|
||||
this.doImportFile()
|
||||
} else {
|
||||
// JSON文件,读取内容后导入
|
||||
if (!this.upload.fileContent) {
|
||||
this.$modal.msgWarning("请先选择要上传的文件")
|
||||
return
|
||||
}
|
||||
try {
|
||||
const importData = JSON.parse(this.upload.fileContent)
|
||||
this.upload.isUploading = true
|
||||
importScale(importData).then(response => {
|
||||
this.$modal.msgSuccess(response.msg || "导入成功")
|
||||
this.importOpen = false
|
||||
this.importJsonText = ""
|
||||
this.upload.selectedFile = null
|
||||
this.upload.fileContent = null
|
||||
this.$refs.upload.clearFiles()
|
||||
this.upload.isUploading = false
|
||||
this.getList()
|
||||
}).catch(error => {
|
||||
this.$modal.msgError(error.msg || "导入失败")
|
||||
this.upload.isUploading = false
|
||||
})
|
||||
} catch (e) {
|
||||
this.$modal.msgError("JSON格式错误:" + e.message)
|
||||
this.upload.isUploading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/** 判断是否为文档文件(PDF/DOCX/DOC) */
|
||||
isDocumentFile(fileName) {
|
||||
if (!fileName) return false
|
||||
const lowerName = fileName.toLowerCase()
|
||||
return lowerName.endsWith('.pdf') || lowerName.endsWith('.docx') || lowerName.endsWith('.doc')
|
||||
},
|
||||
/** 直接导入文件(不预览) */
|
||||
doImportFile() {
|
||||
if (!this.upload.selectedFile) {
|
||||
this.$modal.msgWarning("请先选择文件")
|
||||
return
|
||||
}
|
||||
this.upload.isUploading = true
|
||||
importScaleFile(this.upload.selectedFile).then(response => {
|
||||
this.$modal.msgSuccess(response.msg || "导入成功")
|
||||
this.importOpen = false
|
||||
this.upload.selectedFile = null
|
||||
this.upload.fileContent = null
|
||||
this.$refs.upload.clearFiles()
|
||||
this.upload.isUploading = false
|
||||
this.getList()
|
||||
}).catch(error => {
|
||||
this.$modal.msgError(error.msg || "导入失败")
|
||||
this.upload.isUploading = false
|
||||
})
|
||||
},
|
||||
/** 预览文档解析结果 */
|
||||
handlePreview() {
|
||||
if (!this.upload.selectedFile) {
|
||||
this.$modal.msgWarning("请先选择文件")
|
||||
return
|
||||
}
|
||||
this.$modal.loading("正在解析文档,请稍候...")
|
||||
previewDocument(this.upload.selectedFile).then(response => {
|
||||
this.$modal.closeLoading()
|
||||
const data = response.data
|
||||
if (data && data.importData) {
|
||||
// 将解析结果转换为JSON显示在文本框中
|
||||
this.importJsonText = JSON.stringify(data.importData, null, 2)
|
||||
this.importTab = "text"
|
||||
this.$modal.msgSuccess("文档解析完成,请检查并调整解析结果后再导入")
|
||||
} else {
|
||||
this.$modal.msgError("解析失败,未能识别量表结构")
|
||||
}
|
||||
}).catch(error => {
|
||||
this.$modal.closeLoading()
|
||||
this.$modal.msgError(error.msg || "解析失败")
|
||||
})
|
||||
},
|
||||
/** 文件选择变化处理 */
|
||||
handleFileChange(file, fileList) {
|
||||
if (file.raw) {
|
||||
this.upload.selectedFile = file.raw
|
||||
const fileName = file.raw.name || ""
|
||||
|
||||
// 如果是JSON文件,读取内容
|
||||
if (fileName.toLowerCase().endsWith('.json')) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
this.upload.fileContent = e.target.result
|
||||
}
|
||||
reader.onerror = () => {
|
||||
this.$modal.msgError("文件读取失败")
|
||||
this.upload.selectedFile = null
|
||||
this.upload.fileContent = null
|
||||
}
|
||||
reader.readAsText(file.raw, 'UTF-8')
|
||||
} else {
|
||||
// PDF/DOCX/DOC文件,不需要预读内容
|
||||
this.upload.fileContent = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -492,7 +492,16 @@ export default {
|
|||
},
|
||||
/** 分配量表权限操作 */
|
||||
handleAuthScale: function(row) {
|
||||
this.$router.push("/psychology/permission/user/" + row.userId)
|
||||
if (!row || !row.userId) {
|
||||
this.$modal.msgError("用户信息不完整,无法分配权限");
|
||||
return;
|
||||
}
|
||||
const userId = row.userId;
|
||||
if (isNaN(userId) || userId <= 0) {
|
||||
this.$modal.msgError("用户ID无效");
|
||||
return;
|
||||
}
|
||||
this.$router.push("/psychology/permission/user/" + userId);
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm: function() {
|
||||
|
|
|
|||
|
|
@ -72,6 +72,23 @@
|
|||
<artifactId>javase</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- PDF解析 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox-tools</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Word文档解析 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-scratchpad</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
|||
|
|
@ -258,14 +258,27 @@ public class PsyAssessmentController extends BaseController
|
|||
assessmentService.updateAssessment(assessment);
|
||||
|
||||
// 自动生成报告
|
||||
try
|
||||
{
|
||||
reportService.generateReport(assessmentId);
|
||||
|
||||
return success("提交成功,报告已生成");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// 报告生成失败,但不影响测评提交
|
||||
e.printStackTrace();
|
||||
return success("提交成功,但报告生成失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
return error("提交失败:" + e.getMessage());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
return error("提交失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,13 +11,17 @@ import org.springframework.web.bind.annotation.PostMapping;
|
|||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.ddnai.common.annotation.Log;
|
||||
import com.ddnai.common.core.controller.BaseController;
|
||||
import com.ddnai.common.core.domain.AjaxResult;
|
||||
import com.ddnai.common.core.page.TableDataInfo;
|
||||
import com.ddnai.common.enums.BusinessType;
|
||||
import com.ddnai.system.domain.psychology.PsyScale;
|
||||
import com.ddnai.system.domain.psychology.vo.ScaleImportVO;
|
||||
import com.ddnai.system.service.psychology.IDocumentParseService;
|
||||
import com.ddnai.system.service.psychology.IPsyScaleService;
|
||||
|
||||
/**
|
||||
|
|
@ -32,6 +36,9 @@ public class PsyScaleController extends BaseController
|
|||
@Autowired
|
||||
private IPsyScaleService scaleService;
|
||||
|
||||
@Autowired
|
||||
private IDocumentParseService documentParseService;
|
||||
|
||||
/**
|
||||
* 获取量表列表
|
||||
*/
|
||||
|
|
@ -92,5 +99,159 @@ public class PsyScaleController extends BaseController
|
|||
{
|
||||
return toAjax(scaleService.deleteScaleByIds(scaleIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入量表(支持JSON和文档文件)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('psychology:scale:add')")
|
||||
@Log(title = "心理量表", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/import")
|
||||
public AjaxResult importScale(@RequestParam(value = "file", required = false) MultipartFile file,
|
||||
@RequestParam(value = "jsonData", required = false) String jsonData)
|
||||
{
|
||||
try
|
||||
{
|
||||
ScaleImportVO data = null;
|
||||
java.util.Map<String, Object> rawJsonData = null;
|
||||
|
||||
// 如果上传了文件,尝试解析文档
|
||||
if (file != null && !file.isEmpty())
|
||||
{
|
||||
String filename = file.getOriginalFilename();
|
||||
if (documentParseService.isSupported(filename))
|
||||
{
|
||||
// 解析文档(PDF、DOCX、DOC)
|
||||
data = documentParseService.parseDocument(file);
|
||||
// PDF/DOCX解析的结果可能没有factorCode,所以rawJsonData为null
|
||||
}
|
||||
else
|
||||
{
|
||||
// 不支持的文件格式,尝试作为JSON文件读取
|
||||
String jsonText = new String(file.getBytes(), java.nio.charset.StandardCharsets.UTF_8);
|
||||
// 先解析为Map以保留factorCode信息
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.Map<String, Object> parsedMap = (java.util.Map<String, Object>)
|
||||
com.alibaba.fastjson2.JSON.parseObject(jsonText, java.util.Map.class);
|
||||
rawJsonData = parsedMap;
|
||||
// 再转换为ScaleImportVO
|
||||
data = com.alibaba.fastjson2.JSON.parseObject(jsonText, ScaleImportVO.class);
|
||||
}
|
||||
}
|
||||
// 如果没有文件,尝试从jsonData参数解析
|
||||
else if (jsonData != null && !jsonData.trim().isEmpty())
|
||||
{
|
||||
// 先解析为Map以保留factorCode信息
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.Map<String, Object> parsedMap = (java.util.Map<String, Object>)
|
||||
com.alibaba.fastjson2.JSON.parseObject(jsonData, java.util.Map.class);
|
||||
rawJsonData = parsedMap;
|
||||
// 再转换为ScaleImportVO
|
||||
data = com.alibaba.fastjson2.JSON.parseObject(jsonData, ScaleImportVO.class);
|
||||
}
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
return error("导入数据不能为空,请上传文件或提供JSON数据");
|
||||
}
|
||||
|
||||
// 调用支持factorCode映射的导入方法
|
||||
Long scaleId = scaleService.importScale(data, rawJsonData, getUsername());
|
||||
return success("导入成功,量表ID:" + scaleId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return error("导入失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入量表(JSON格式,用于前端直接传递JSON对象)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('psychology:scale:add')")
|
||||
@Log(title = "心理量表", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importJson")
|
||||
public AjaxResult importScaleJson(@RequestBody java.util.Map<String, Object> jsonData)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (jsonData == null)
|
||||
{
|
||||
return error("导入数据不能为空");
|
||||
}
|
||||
|
||||
// 使用ObjectMapper转换为ScaleImportVO
|
||||
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
ScaleImportVO importData = objectMapper.convertValue(jsonData, ScaleImportVO.class);
|
||||
|
||||
// 调用导入服务,传入原始JSON以便处理factorCode映射
|
||||
Long scaleId = scaleService.importScale(importData, jsonData, getUsername());
|
||||
return success("导入成功,量表ID:" + scaleId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return error("导入失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览文档解析结果(不导入,仅返回解析后的数据)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('psychology:scale:add')")
|
||||
@PostMapping("/preview")
|
||||
public AjaxResult previewDocument(@RequestParam("file") MultipartFile file)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (file == null || file.isEmpty())
|
||||
{
|
||||
return error("请选择要预览的文件");
|
||||
}
|
||||
|
||||
String filename = file.getOriginalFilename();
|
||||
if (!documentParseService.isSupported(filename))
|
||||
{
|
||||
return error("不支持的文件格式,仅支持PDF、DOCX、DOC格式");
|
||||
}
|
||||
|
||||
// 解析文档
|
||||
ScaleImportVO importData = documentParseService.parseDocument(file);
|
||||
|
||||
// 同时返回原始文本,方便用户查看和调整
|
||||
String originalText = documentParseService.extractText(file);
|
||||
|
||||
java.util.Map<String, Object> result = new java.util.HashMap<>();
|
||||
result.put("importData", importData);
|
||||
result.put("originalText", originalText);
|
||||
|
||||
return success(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return error("解析失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取文档文本内容(用于调试和查看)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('psychology:scale:add')")
|
||||
@PostMapping("/extract-text")
|
||||
public AjaxResult extractText(@RequestParam("file") MultipartFile file)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (file == null || file.isEmpty())
|
||||
{
|
||||
return error("请选择文件");
|
||||
}
|
||||
|
||||
String text = documentParseService.extractText(file);
|
||||
return success(text);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return error("提取文本失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,29 @@
|
|||
<artifactId>ry-news-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- excel工具 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- POI Word文档解析 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-scratchpad</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- PDF解析工具 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox-tools</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
package com.ddnai.system.domain.psychology.vo;
|
||||
|
||||
import com.ddnai.system.domain.psychology.PsyResultInterpretation;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 解释配置导入VO(支持factorCode映射)
|
||||
*
|
||||
* @author ddnai
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class InterpretationImportVO extends PsyResultInterpretation
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 因子编码(用于导入时映射到factorId) */
|
||||
@JsonProperty("factorCode")
|
||||
private String factorCode;
|
||||
|
||||
public String getFactorCode()
|
||||
{
|
||||
return factorCode;
|
||||
}
|
||||
|
||||
public void setFactorCode(String factorCode)
|
||||
{
|
||||
this.factorCode = factorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为PsyResultInterpretation对象
|
||||
*/
|
||||
public PsyResultInterpretation toPsyResultInterpretation()
|
||||
{
|
||||
PsyResultInterpretation interpretation = new PsyResultInterpretation();
|
||||
interpretation.setScaleId(this.getScaleId());
|
||||
interpretation.setFactorId(this.getFactorId());
|
||||
interpretation.setScoreRangeMin(this.getScoreRangeMin());
|
||||
interpretation.setScoreRangeMax(this.getScoreRangeMax());
|
||||
interpretation.setLevel(this.getLevel());
|
||||
interpretation.setLevelName(this.getLevelName());
|
||||
interpretation.setInterpretationTitle(this.getInterpretationTitle());
|
||||
interpretation.setInterpretationContent(this.getInterpretationContent());
|
||||
interpretation.setSuggestions(this.getSuggestions());
|
||||
interpretation.setSortOrder(this.getSortOrder());
|
||||
interpretation.setCreateBy(this.getCreateBy());
|
||||
interpretation.setCreateTime(this.getCreateTime());
|
||||
interpretation.setUpdateBy(this.getUpdateBy());
|
||||
interpretation.setUpdateTime(this.getUpdateTime());
|
||||
return interpretation;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
package com.ddnai.system.domain.psychology.vo;
|
||||
|
||||
import java.util.List;
|
||||
import com.ddnai.system.domain.psychology.PsyScale;
|
||||
import com.ddnai.system.domain.psychology.PsyFactor;
|
||||
import com.ddnai.system.domain.psychology.PsyScaleItem;
|
||||
import com.ddnai.system.domain.psychology.PsyScaleOption;
|
||||
import com.ddnai.system.domain.psychology.PsyFactorRule;
|
||||
import com.ddnai.system.domain.psychology.PsyResultInterpretation;
|
||||
import com.ddnai.system.domain.psychology.PsyWarningRule;
|
||||
|
||||
/**
|
||||
* 量表导入VO
|
||||
* 用于接收导入的JSON数据
|
||||
*
|
||||
* @author ddnai
|
||||
*/
|
||||
public class ScaleImportVO
|
||||
{
|
||||
/** 量表基本信息 */
|
||||
private PsyScale scale;
|
||||
|
||||
/** 因子列表 */
|
||||
private List<FactorImportVO> factors;
|
||||
|
||||
/** 题目列表 */
|
||||
private List<ItemImportVO> items;
|
||||
|
||||
/** 解释配置列表(可选) */
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.AS_EMPTY)
|
||||
private List<PsyResultInterpretation> interpretations;
|
||||
|
||||
/** 预警规则列表(可选) */
|
||||
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.AS_EMPTY)
|
||||
private List<PsyWarningRule> warningRules;
|
||||
|
||||
public PsyScale getScale()
|
||||
{
|
||||
return scale;
|
||||
}
|
||||
|
||||
public void setScale(PsyScale scale)
|
||||
{
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public List<FactorImportVO> getFactors()
|
||||
{
|
||||
return factors;
|
||||
}
|
||||
|
||||
public void setFactors(List<FactorImportVO> factors)
|
||||
{
|
||||
this.factors = factors;
|
||||
}
|
||||
|
||||
public List<ItemImportVO> getItems()
|
||||
{
|
||||
return items;
|
||||
}
|
||||
|
||||
public void setItems(List<ItemImportVO> items)
|
||||
{
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
public List<PsyResultInterpretation> getInterpretations()
|
||||
{
|
||||
return interpretations;
|
||||
}
|
||||
|
||||
public void setInterpretations(List<PsyResultInterpretation> interpretations)
|
||||
{
|
||||
this.interpretations = interpretations;
|
||||
}
|
||||
|
||||
public List<PsyWarningRule> getWarningRules()
|
||||
{
|
||||
return warningRules;
|
||||
}
|
||||
|
||||
public void setWarningRules(List<PsyWarningRule> warningRules)
|
||||
{
|
||||
this.warningRules = warningRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* 因子导入VO(包含计分规则)
|
||||
*/
|
||||
public static class FactorImportVO
|
||||
{
|
||||
/** 因子信息 */
|
||||
private PsyFactor factor;
|
||||
|
||||
/** 计分规则列表 */
|
||||
private List<FactorRuleImportVO> rules;
|
||||
|
||||
public PsyFactor getFactor()
|
||||
{
|
||||
return factor;
|
||||
}
|
||||
|
||||
public void setFactor(PsyFactor factor)
|
||||
{
|
||||
this.factor = factor;
|
||||
}
|
||||
|
||||
public List<FactorRuleImportVO> getRules()
|
||||
{
|
||||
return rules;
|
||||
}
|
||||
|
||||
public void setRules(List<FactorRuleImportVO> rules)
|
||||
{
|
||||
this.rules = rules;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 因子计分规则导入VO(包含题目序号用于映射)
|
||||
*/
|
||||
public static class FactorRuleImportVO
|
||||
{
|
||||
/** 题目序号(用于映射到新的题目ID) */
|
||||
private Integer itemNumber;
|
||||
|
||||
/** 计分规则信息 */
|
||||
private PsyFactorRule rule;
|
||||
|
||||
public Integer getItemNumber()
|
||||
{
|
||||
return itemNumber;
|
||||
}
|
||||
|
||||
public void setItemNumber(Integer itemNumber)
|
||||
{
|
||||
this.itemNumber = itemNumber;
|
||||
}
|
||||
|
||||
public PsyFactorRule getRule()
|
||||
{
|
||||
return rule;
|
||||
}
|
||||
|
||||
public void setRule(PsyFactorRule rule)
|
||||
{
|
||||
this.rule = rule;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 题目导入VO(包含选项)
|
||||
*/
|
||||
public static class ItemImportVO
|
||||
{
|
||||
/** 题目信息 */
|
||||
private PsyScaleItem item;
|
||||
|
||||
/** 选项列表 */
|
||||
private List<PsyScaleOption> options;
|
||||
|
||||
public PsyScaleItem getItem()
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
public void setItem(PsyScaleItem item)
|
||||
{
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public List<PsyScaleOption> getOptions()
|
||||
{
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setOptions(List<PsyScaleOption> options)
|
||||
{
|
||||
this.options = options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.ddnai.system.domain.psychology.vo;
|
||||
|
||||
import com.ddnai.system.domain.psychology.PsyWarningRule;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 预警规则导入VO(支持factorCode映射)
|
||||
*
|
||||
* @author ddnai
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class WarningRuleImportVO extends PsyWarningRule
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 因子编码(用于导入时映射到factorId) */
|
||||
@JsonProperty("factorCode")
|
||||
private String factorCode;
|
||||
|
||||
public String getFactorCode()
|
||||
{
|
||||
return factorCode;
|
||||
}
|
||||
|
||||
public void setFactorCode(String factorCode)
|
||||
{
|
||||
this.factorCode = factorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为PsyWarningRule对象
|
||||
*/
|
||||
public PsyWarningRule toPsyWarningRule()
|
||||
{
|
||||
PsyWarningRule warningRule = new PsyWarningRule();
|
||||
warningRule.setScaleId(this.getScaleId());
|
||||
warningRule.setFactorId(this.getFactorId());
|
||||
warningRule.setRuleName(this.getRuleName());
|
||||
warningRule.setWarningLevel(this.getWarningLevel());
|
||||
warningRule.setScoreMin(this.getScoreMin());
|
||||
warningRule.setScoreMax(this.getScoreMax());
|
||||
warningRule.setPercentileMin(this.getPercentileMin());
|
||||
warningRule.setPercentileMax(this.getPercentileMax());
|
||||
warningRule.setAutoRelief(this.getAutoRelief());
|
||||
warningRule.setReliefCondition(this.getReliefCondition());
|
||||
warningRule.setStatus(this.getStatus());
|
||||
warningRule.setCreateBy(this.getCreateBy());
|
||||
warningRule.setCreateTime(this.getCreateTime());
|
||||
warningRule.setUpdateBy(this.getUpdateBy());
|
||||
warningRule.setUpdateTime(this.getUpdateTime());
|
||||
return warningRule;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,5 +89,21 @@ public interface PsyAssessmentMapper
|
|||
* @return 结果
|
||||
*/
|
||||
public int resumeAssessment(PsyAssessment assessment);
|
||||
|
||||
/**
|
||||
* 根据量表ID查询测评记录数量
|
||||
*
|
||||
* @param scaleId 量表ID
|
||||
* @return 测评记录数量
|
||||
*/
|
||||
public int countAssessmentByScaleId(Long scaleId);
|
||||
|
||||
/**
|
||||
* 根据量表ID批量删除测评记录
|
||||
*
|
||||
* @param scaleIds 量表ID数组
|
||||
* @return 删除数量
|
||||
*/
|
||||
public int deleteAssessmentByScaleIds(Long[] scaleIds);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,5 +73,13 @@ public interface PsyScaleMapper
|
|||
* @return 结果
|
||||
*/
|
||||
public int checkScaleCodeUnique(String scaleCode);
|
||||
|
||||
/**
|
||||
* 检查量表是否被测评记录使用
|
||||
*
|
||||
* @param scaleId 量表ID
|
||||
* @return 测评记录数量
|
||||
*/
|
||||
public int countAssessmentByScaleId(Long scaleId);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,893 @@
|
|||
package com.ddnai.system.service.impl.psychology;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.text.PDFTextStripper;
|
||||
import org.apache.poi.hwpf.HWPFDocument;
|
||||
import org.apache.poi.hwpf.extractor.WordExtractor;
|
||||
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.ddnai.system.domain.psychology.PsyFactor;
|
||||
import com.ddnai.system.domain.psychology.PsyFactorRule;
|
||||
import com.ddnai.system.domain.psychology.PsyScale;
|
||||
import com.ddnai.system.domain.psychology.PsyScaleItem;
|
||||
import com.ddnai.system.domain.psychology.PsyScaleOption;
|
||||
import com.ddnai.system.domain.psychology.vo.ScaleImportVO;
|
||||
import com.ddnai.system.service.psychology.IDocumentParseService;
|
||||
|
||||
/**
|
||||
* 文档解析服务实现
|
||||
*
|
||||
* @author ddnai
|
||||
*/
|
||||
@Service
|
||||
public class DocumentParseServiceImpl implements IDocumentParseService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(DocumentParseServiceImpl.class);
|
||||
|
||||
// 题目识别正则:匹配 "1." 或 "1、" 或 "(1)" 或 "1 " 开头的题目
|
||||
private static final Pattern ITEM_PATTERN = Pattern.compile("^\\s*(\\d+)[.、)\\s]\\s*(.+?)(?=\\s*\\d+[.、)\\s]|$)", Pattern.MULTILINE | Pattern.DOTALL);
|
||||
|
||||
// 选项识别正则:匹配多种格式的选项
|
||||
// 支持:A. 没有 B. 很轻 或 A没有 B很轻 或 (A)没有 (B)很轻 或 ①没有 ②很轻
|
||||
private static final Pattern OPTION_PATTERN = Pattern.compile(
|
||||
"(?:^|\\s)([A-E①②③④⑤⑥⑦⑧⑨⑩]|[一二三四五六七八九十]|\\d+)[.、)\\s]\\s*([^A-E①②③④⑤⑥⑦⑧⑨⑩一二三四五六七八九十\\d.、\\)]+?)(?=\\s*[A-E①②③④⑤⑥⑦⑧⑨⑩一二三四五六七八九十\\d][.、)\\s]|\\s*$|\\r?\\n)",
|
||||
Pattern.MULTILINE | Pattern.DOTALL
|
||||
);
|
||||
|
||||
// SCL-90常见选项模式(5级评分)
|
||||
private static final String[] SCL90_OPTIONS = {"没有", "很轻", "中等", "偏重", "严重"};
|
||||
|
||||
// 因子识别正则:匹配因子名称(支持多种格式)
|
||||
private static final Pattern FACTOR_PATTERN = Pattern.compile(
|
||||
"(?:因子|维度|因素|F[\\s]*[\\d一二三四五六七八九十])[\\s]*(?:\\d+|一|二|三|四|五|六|七|八|九|十)[::\\s]*([^\\r\\n]+?)(?:\\r?\\n|$)",
|
||||
Pattern.MULTILINE
|
||||
);
|
||||
|
||||
// SCL-90因子定义
|
||||
private static final Map<String, String> SCL90_FACTORS = new HashMap<>();
|
||||
static {
|
||||
SCL90_FACTORS.put("躯体化", "F1");
|
||||
SCL90_FACTORS.put("强迫", "F2");
|
||||
SCL90_FACTORS.put("人际关系", "F3");
|
||||
SCL90_FACTORS.put("抑郁", "F4");
|
||||
SCL90_FACTORS.put("焦虑", "F5");
|
||||
SCL90_FACTORS.put("敌对", "F6");
|
||||
SCL90_FACTORS.put("恐怖", "F7");
|
||||
SCL90_FACTORS.put("偏执", "F8");
|
||||
SCL90_FACTORS.put("精神病性", "F9");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported(String filename)
|
||||
{
|
||||
if (StringUtils.isBlank(filename))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
String lowerName = filename.toLowerCase();
|
||||
return lowerName.endsWith(".pdf") || lowerName.endsWith(".docx") || lowerName.endsWith(".doc");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String extractText(MultipartFile file) throws Exception
|
||||
{
|
||||
String filename = file.getOriginalFilename();
|
||||
if (filename == null)
|
||||
{
|
||||
throw new IllegalArgumentException("文件名不能为空");
|
||||
}
|
||||
|
||||
String lowerName = filename.toLowerCase();
|
||||
InputStream inputStream = file.getInputStream();
|
||||
|
||||
try
|
||||
{
|
||||
if (lowerName.endsWith(".pdf"))
|
||||
{
|
||||
return extractTextFromPdf(inputStream);
|
||||
}
|
||||
else if (lowerName.endsWith(".docx"))
|
||||
{
|
||||
return extractTextFromDocx(inputStream);
|
||||
}
|
||||
else if (lowerName.endsWith(".doc"))
|
||||
{
|
||||
return extractTextFromDoc(inputStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("不支持的文件格式:" + filename);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (inputStream != null)
|
||||
{
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScaleImportVO parseDocument(MultipartFile file) throws Exception
|
||||
{
|
||||
log.info("开始解析文档:{}", file.getOriginalFilename());
|
||||
String text = extractText(file);
|
||||
log.debug("提取文本长度:{} 字符", text.length());
|
||||
|
||||
if (StringUtils.isBlank(text))
|
||||
{
|
||||
throw new RuntimeException("无法从文档中提取文本内容,请检查文档格式");
|
||||
}
|
||||
|
||||
ScaleImportVO result = parseTextToScale(text, file.getOriginalFilename());
|
||||
|
||||
log.info("解析完成 - 量表:{},题目数:{},因子数:{}",
|
||||
result.getScale().getScaleName(),
|
||||
result.getItems() != null ? result.getItems().size() : 0,
|
||||
result.getFactors() != null ? result.getFactors().size() : 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从PDF提取文本
|
||||
*/
|
||||
private String extractTextFromPdf(InputStream inputStream) throws Exception
|
||||
{
|
||||
try (PDDocument document = PDDocument.load(inputStream))
|
||||
{
|
||||
PDFTextStripper stripper = new PDFTextStripper();
|
||||
stripper.setStartPage(1);
|
||||
stripper.setEndPage(document.getNumberOfPages());
|
||||
return stripper.getText(document);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从DOCX提取文本
|
||||
*/
|
||||
private String extractTextFromDocx(InputStream inputStream) throws Exception
|
||||
{
|
||||
XWPFDocument document = new XWPFDocument(inputStream);
|
||||
try
|
||||
{
|
||||
XWPFWordExtractor extractor = new XWPFWordExtractor(document);
|
||||
try
|
||||
{
|
||||
return extractor.getText();
|
||||
}
|
||||
finally
|
||||
{
|
||||
extractor.close();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
document.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从DOC提取文本
|
||||
*/
|
||||
private String extractTextFromDoc(InputStream inputStream) throws Exception
|
||||
{
|
||||
try (HWPFDocument document = new HWPFDocument(inputStream))
|
||||
{
|
||||
WordExtractor extractor = new WordExtractor(document);
|
||||
return extractor.getText();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文本解析为量表数据结构
|
||||
*/
|
||||
private ScaleImportVO parseTextToScale(String text, String filename)
|
||||
{
|
||||
ScaleImportVO importVO = new ScaleImportVO();
|
||||
|
||||
// 检查是否为SCL-90量表
|
||||
boolean isSCL90 = isSCL90Scale(text, filename);
|
||||
|
||||
// 1. 创建量表基本信息
|
||||
PsyScale scale = new PsyScale();
|
||||
scale.setScaleName(extractScaleName(text, filename));
|
||||
scale.setScaleCode(generateScaleCode(filename));
|
||||
scale.setScaleType(isSCL90 ? "symptom" : "general"); // SCL-90属于症状评估类
|
||||
scale.setStatus("0"); // 默认正常状态
|
||||
scale.setItemCount(null); // 由系统自动计算
|
||||
importVO.setScale(scale);
|
||||
|
||||
// 2. 解析题目
|
||||
List<ScaleImportVO.ItemImportVO> items;
|
||||
if (isSCL90)
|
||||
{
|
||||
items = parseSCL90Items(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
items = parseItems(text);
|
||||
}
|
||||
importVO.setItems(items);
|
||||
|
||||
// 3. 解析因子
|
||||
List<ScaleImportVO.FactorImportVO> factors;
|
||||
if (isSCL90)
|
||||
{
|
||||
factors = parseSCL90Factors(text, items);
|
||||
}
|
||||
else
|
||||
{
|
||||
factors = parseFactors(text, items);
|
||||
}
|
||||
importVO.setFactors(factors);
|
||||
|
||||
return importVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为SCL-90量表
|
||||
*/
|
||||
private boolean isSCL90Scale(String text, String filename)
|
||||
{
|
||||
String lowerText = text.toLowerCase();
|
||||
String lowerFilename = filename != null ? filename.toLowerCase() : "";
|
||||
|
||||
// 检查文件名和文本中是否包含SCL-90相关关键词
|
||||
return lowerFilename.contains("scl") ||
|
||||
lowerFilename.contains("scl-90") ||
|
||||
lowerFilename.contains("scl90") ||
|
||||
lowerText.contains("scl-90") ||
|
||||
lowerText.contains("scl90") ||
|
||||
lowerText.contains("症状自评量表") ||
|
||||
(lowerText.contains("90") && lowerText.contains("症状"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取量表名称
|
||||
*/
|
||||
private String extractScaleName(String text, String filename)
|
||||
{
|
||||
// 尝试从文件名提取
|
||||
if (filename != null)
|
||||
{
|
||||
String name = filename.replaceAll("\\.(pdf|docx|doc)$", "").trim();
|
||||
if (StringUtils.isNotBlank(name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试从文本第一行提取
|
||||
String[] lines = text.split("\\r?\\n");
|
||||
for (String line : lines)
|
||||
{
|
||||
line = line.trim();
|
||||
if (StringUtils.isNotBlank(line) && line.length() > 2 && line.length() < 100)
|
||||
{
|
||||
// 跳过常见的标题行
|
||||
if (!line.matches("^第[一二三四五六七八九十]+[章节部分].*$")
|
||||
&& !line.matches("^\\d+[.、].*$"))
|
||||
{
|
||||
return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "导入的量表";
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成量表编码
|
||||
*/
|
||||
private String generateScaleCode(String filename)
|
||||
{
|
||||
if (filename != null)
|
||||
{
|
||||
String code = filename.replaceAll("\\.(pdf|docx|doc)$", "")
|
||||
.replaceAll("[^a-zA-Z0-9\\u4e00-\\u9fa5]", "")
|
||||
.trim();
|
||||
if (StringUtils.isNotBlank(code) && code.length() <= 50)
|
||||
{
|
||||
return code;
|
||||
}
|
||||
}
|
||||
return "SCALE_" + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析SCL-90题目列表(特殊处理)
|
||||
*/
|
||||
private List<ScaleImportVO.ItemImportVO> parseSCL90Items(String text)
|
||||
{
|
||||
List<ScaleImportVO.ItemImportVO> items = new ArrayList<>();
|
||||
|
||||
// SCL-90通常有90个题目,使用更精确的正则匹配
|
||||
// 匹配模式:数字 + 点/空格 + 题目内容(可能包含换行)
|
||||
Pattern scl90ItemPattern = Pattern.compile(
|
||||
"^\\s*(\\d+)[.、]?\\s+([^\\d]+?)(?=\\s*\\d+[.、]|\\s*指导语|\\s*说明|$)",
|
||||
Pattern.MULTILINE | Pattern.DOTALL
|
||||
);
|
||||
|
||||
Matcher itemMatcher = scl90ItemPattern.matcher(text);
|
||||
Map<Integer, String> itemMap = new HashMap<>();
|
||||
|
||||
while (itemMatcher.find())
|
||||
{
|
||||
try
|
||||
{
|
||||
int itemNum = Integer.parseInt(itemMatcher.group(1));
|
||||
String content = itemMatcher.group(2).trim();
|
||||
|
||||
// 过滤掉明显不是题目的内容
|
||||
if (content.length() < 3 || content.length() > 500)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 跳过指导语、说明等
|
||||
if (content.contains("指导语") || content.contains("说明") ||
|
||||
content.contains("评分标准") || content.contains("注意事项"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemMap.put(itemNum, content);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.debug("解析题目失败:{}", e.getMessage());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("识别到 {} 个SCL-90题目", itemMap.size());
|
||||
|
||||
// 按题目序号排序并创建题目对象
|
||||
itemMap.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.forEach(entry -> {
|
||||
try
|
||||
{
|
||||
int itemNumber = entry.getKey();
|
||||
String content = entry.getValue();
|
||||
|
||||
// 提取题目内容(去掉选项部分)
|
||||
String itemContent = extractItemContent(content);
|
||||
|
||||
// 创建题目
|
||||
PsyScaleItem item = new PsyScaleItem();
|
||||
item.setItemNumber(itemNumber);
|
||||
item.setItemContent(itemContent);
|
||||
item.setItemType("single");
|
||||
item.setRequired("1");
|
||||
item.setReverseScore("0");
|
||||
item.setSortOrder(itemNumber);
|
||||
|
||||
// 解析选项(SCL-90使用5级评分)
|
||||
List<PsyScaleOption> options = parseSCL90Options(content);
|
||||
|
||||
// 如果没有解析到选项,使用默认的5级选项
|
||||
if (options.isEmpty())
|
||||
{
|
||||
options = createDefaultSCL90Options();
|
||||
}
|
||||
|
||||
ScaleImportVO.ItemImportVO itemVO = new ScaleImportVO.ItemImportVO();
|
||||
itemVO.setItem(item);
|
||||
itemVO.setOptions(options);
|
||||
|
||||
items.add(itemVO);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.warn("创建题目失败,题目序号:{},错误:{}", entry.getKey(), e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析题目列表(通用方法)
|
||||
*/
|
||||
private List<ScaleImportVO.ItemImportVO> parseItems(String text)
|
||||
{
|
||||
List<ScaleImportVO.ItemImportVO> items = new ArrayList<>();
|
||||
|
||||
Matcher itemMatcher = ITEM_PATTERN.matcher(text);
|
||||
int itemNumber = 1;
|
||||
Map<Integer, String> itemMap = new HashMap<>();
|
||||
|
||||
while (itemMatcher.find())
|
||||
{
|
||||
try
|
||||
{
|
||||
int num = Integer.parseInt(itemMatcher.group(1));
|
||||
String content = itemMatcher.group(2).trim();
|
||||
|
||||
// 过滤无效内容
|
||||
if (content.length() < 3 || content.length() > 500)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemMap.put(num, content);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.debug("解析题目失败:{}", e.getMessage());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("识别到 {} 个题目", itemMap.size());
|
||||
|
||||
// 按题目序号排序并创建题目对象
|
||||
itemMap.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.forEach(entry -> {
|
||||
try
|
||||
{
|
||||
int num = entry.getKey();
|
||||
String content = entry.getValue();
|
||||
|
||||
// 提取题目内容
|
||||
String itemContent = extractItemContent(content);
|
||||
|
||||
// 创建题目
|
||||
PsyScaleItem item = new PsyScaleItem();
|
||||
item.setItemNumber(num);
|
||||
item.setItemContent(itemContent);
|
||||
item.setItemType("single");
|
||||
item.setRequired("1");
|
||||
item.setReverseScore("0");
|
||||
item.setSortOrder(num);
|
||||
|
||||
// 解析选项
|
||||
List<PsyScaleOption> options = parseOptions(content);
|
||||
|
||||
ScaleImportVO.ItemImportVO itemVO = new ScaleImportVO.ItemImportVO();
|
||||
itemVO.setItem(item);
|
||||
itemVO.setOptions(options);
|
||||
|
||||
items.add(itemVO);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.warn("创建题目失败,题目序号:{},错误:{}", entry.getKey(), e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建SCL-90默认的5级选项
|
||||
*/
|
||||
private List<PsyScaleOption> createDefaultSCL90Options()
|
||||
{
|
||||
List<PsyScaleOption> options = new ArrayList<>();
|
||||
String[] optionTexts = {"没有", "很轻", "中等", "偏重", "严重"};
|
||||
String[] optionCodes = {"A", "B", "C", "D", "E"};
|
||||
|
||||
for (int i = 0; i < optionTexts.length; i++)
|
||||
{
|
||||
PsyScaleOption option = new PsyScaleOption();
|
||||
option.setOptionCode(optionCodes[i]);
|
||||
option.setOptionContent(optionTexts[i]);
|
||||
option.setOptionScore(BigDecimal.valueOf(i)); // 0-4分
|
||||
option.setSortOrder(i + 1);
|
||||
options.add(option);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析SCL-90选项(5级评分)
|
||||
*/
|
||||
private List<PsyScaleOption> parseSCL90Options(String text)
|
||||
{
|
||||
List<PsyScaleOption> options = new ArrayList<>();
|
||||
|
||||
// 尝试识别SCL-90的5个选项
|
||||
for (int i = 0; i < SCL90_OPTIONS.length; i++)
|
||||
{
|
||||
String optionText = SCL90_OPTIONS[i];
|
||||
if (text.contains(optionText))
|
||||
{
|
||||
PsyScaleOption option = new PsyScaleOption();
|
||||
option.setOptionCode(String.valueOf((char)('A' + i)));
|
||||
option.setOptionContent(optionText);
|
||||
option.setOptionScore(BigDecimal.valueOf(i)); // 0-4分
|
||||
option.setSortOrder(i + 1);
|
||||
options.add(option);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有识别到,使用默认选项
|
||||
if (options.isEmpty())
|
||||
{
|
||||
return createDefaultSCL90Options();
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取题目内容(去掉选项部分)
|
||||
*/
|
||||
private String extractItemContent(String fullText)
|
||||
{
|
||||
// 找到第一个选项的位置
|
||||
Matcher optionMatcher = OPTION_PATTERN.matcher(fullText);
|
||||
if (optionMatcher.find())
|
||||
{
|
||||
int optionStart = optionMatcher.start();
|
||||
return fullText.substring(0, optionStart).trim();
|
||||
}
|
||||
return fullText.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析选项(通用方法)
|
||||
*/
|
||||
private List<PsyScaleOption> parseOptions(String text)
|
||||
{
|
||||
List<PsyScaleOption> options = new ArrayList<>();
|
||||
|
||||
// 方法1:使用正则表达式匹配
|
||||
Matcher optionMatcher = OPTION_PATTERN.matcher(text);
|
||||
Map<Integer, PsyScaleOption> optionMap = new HashMap<>();
|
||||
|
||||
while (optionMatcher.find())
|
||||
{
|
||||
try
|
||||
{
|
||||
String optionCode = optionMatcher.group(1).trim();
|
||||
String optionContent = optionMatcher.group(2).trim();
|
||||
|
||||
if (optionContent.length() < 1 || optionContent.length() > 100)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 转换选项编码(如果是中文数字或特殊字符)
|
||||
String code = convertOptionCode(optionCode);
|
||||
int order = getOptionOrder(code);
|
||||
|
||||
PsyScaleOption option = new PsyScaleOption();
|
||||
option.setOptionCode(code);
|
||||
option.setOptionContent(optionContent);
|
||||
|
||||
// 尝试从选项中提取分值(如果选项内容包含分数)
|
||||
BigDecimal score = extractScoreFromOption(optionContent, order);
|
||||
option.setOptionScore(score);
|
||||
option.setSortOrder(order);
|
||||
|
||||
optionMap.put(order, option);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.debug("解析选项失败:{}", e.getMessage());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果正则匹配失败,尝试识别常见的选项模式
|
||||
if (optionMap.isEmpty())
|
||||
{
|
||||
optionMap = tryParseCommonOptions(text);
|
||||
}
|
||||
|
||||
// 转换为列表并按顺序排序
|
||||
options.addAll(optionMap.values());
|
||||
options.sort((a, b) -> Integer.compare(a.getSortOrder(), b.getSortOrder()));
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换选项编码
|
||||
*/
|
||||
private String convertOptionCode(String code)
|
||||
{
|
||||
if (code.matches("[A-Z]"))
|
||||
{
|
||||
return code;
|
||||
}
|
||||
else if (code.matches("\\d+"))
|
||||
{
|
||||
int num = Integer.parseInt(code);
|
||||
return String.valueOf((char)('A' + num - 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 中文数字或其他字符,转换为A-Z
|
||||
Map<String, String> codeMap = new HashMap<>();
|
||||
codeMap.put("一", "A"); codeMap.put("二", "B"); codeMap.put("三", "C");
|
||||
codeMap.put("四", "D"); codeMap.put("五", "E"); codeMap.put("六", "F");
|
||||
codeMap.put("①", "A"); codeMap.put("②", "B"); codeMap.put("③", "C");
|
||||
codeMap.put("④", "D"); codeMap.put("⑤", "E");
|
||||
|
||||
return codeMap.getOrDefault(code, "A");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选项顺序
|
||||
*/
|
||||
private int getOptionOrder(String code)
|
||||
{
|
||||
if (code != null && code.length() == 1 && code.matches("[A-Z]"))
|
||||
{
|
||||
return code.charAt(0) - 'A' + 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从选项内容中提取分值
|
||||
*/
|
||||
private BigDecimal extractScoreFromOption(String content, int defaultScore)
|
||||
{
|
||||
// 尝试从括号中提取分值,如 "没有(0分)" 或 "A. 选项(1)"
|
||||
Pattern scorePattern = Pattern.compile("[((](\\d+(?:\\.\\d+)?)[分))]?");
|
||||
Matcher scoreMatcher = scorePattern.matcher(content);
|
||||
if (scoreMatcher.find())
|
||||
{
|
||||
try
|
||||
{
|
||||
return BigDecimal.valueOf(Double.parseDouble(scoreMatcher.group(1)));
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
// 忽略
|
||||
}
|
||||
}
|
||||
|
||||
// 默认使用顺序作为分值
|
||||
return BigDecimal.valueOf(defaultScore - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试解析常见选项模式
|
||||
*/
|
||||
private Map<Integer, PsyScaleOption> tryParseCommonOptions(String text)
|
||||
{
|
||||
Map<Integer, PsyScaleOption> options = new HashMap<>();
|
||||
|
||||
// 常见的选项模式
|
||||
String[] commonPatterns = {
|
||||
"没有|很轻|中等|偏重|严重",
|
||||
"是|否",
|
||||
"同意|不同意|不确定",
|
||||
"非常同意|同意|不确定|不同意|非常不同意"
|
||||
};
|
||||
|
||||
for (String pattern : commonPatterns)
|
||||
{
|
||||
Pattern p = Pattern.compile(pattern);
|
||||
Matcher m = p.matcher(text);
|
||||
int order = 1;
|
||||
|
||||
while (m.find() && order <= 5)
|
||||
{
|
||||
String content = m.group().trim();
|
||||
if (!content.isEmpty())
|
||||
{
|
||||
PsyScaleOption option = new PsyScaleOption();
|
||||
option.setOptionCode(String.valueOf((char)('A' + order - 1)));
|
||||
option.setOptionContent(content);
|
||||
option.setOptionScore(BigDecimal.valueOf(order - 1));
|
||||
option.setSortOrder(order);
|
||||
options.put(order, option);
|
||||
order++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.isEmpty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析SCL-90因子(9个因子)
|
||||
*/
|
||||
private List<ScaleImportVO.FactorImportVO> parseSCL90Factors(String text, List<ScaleImportVO.ItemImportVO> items)
|
||||
{
|
||||
List<ScaleImportVO.FactorImportVO> factors = new ArrayList<>();
|
||||
|
||||
// SCL-90的9个因子定义
|
||||
String[][] scl90FactorDefs = {
|
||||
{"F1", "躯体化", "1,4,12,27,40,42,48,49,52,53,56,58"},
|
||||
{"F2", "强迫症状", "3,9,10,28,38,45,46,51,55,65"},
|
||||
{"F3", "人际关系敏感", "6,21,34,36,37,41,61,69,73"},
|
||||
{"F4", "抑郁", "5,14,15,20,22,26,29,30,31,32,54,71,79"},
|
||||
{"F5", "焦虑", "2,17,23,33,39,57,72,78,80,86"},
|
||||
{"F6", "敌对", "11,24,63,67,74,81"},
|
||||
{"F7", "恐怖", "13,25,47,50,70,75,82"},
|
||||
{"F8", "偏执", "8,18,43,68,76,83"},
|
||||
{"F9", "精神病性", "7,16,35,62,77,84,85,87,88,90"}
|
||||
};
|
||||
|
||||
// 尝试从文本中识别因子名称
|
||||
Map<String, String> factorNameMap = new HashMap<>();
|
||||
for (String[] def : scl90FactorDefs)
|
||||
{
|
||||
factorNameMap.put(def[0], def[1]);
|
||||
|
||||
// 尝试在文本中查找因子名称
|
||||
String factorName = def[1];
|
||||
Pattern namePattern = Pattern.compile(
|
||||
"(?:因子|维度|因素|F\\s*\\d+)[^::]*[::]?\\s*" + factorName,
|
||||
Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
Matcher nameMatcher = namePattern.matcher(text);
|
||||
if (nameMatcher.find())
|
||||
{
|
||||
// 如果找到,使用找到的名称
|
||||
String foundName = nameMatcher.group();
|
||||
if (foundName.length() > factorName.length())
|
||||
{
|
||||
factorNameMap.put(def[0], factorName); // 保持简洁名称
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建因子对象
|
||||
for (int i = 0; i < scl90FactorDefs.length; i++)
|
||||
{
|
||||
String[] def = scl90FactorDefs[i];
|
||||
String factorCode = def[0];
|
||||
String factorName = factorNameMap.get(factorCode);
|
||||
|
||||
PsyFactor factor = new PsyFactor();
|
||||
factor.setFactorCode(factorCode);
|
||||
factor.setFactorName(factorName);
|
||||
factor.setFactorOrder(i + 1);
|
||||
factor.setFactorDescription("SCL-90 " + factorName + "因子");
|
||||
|
||||
// 解析题目编号并创建计分规则(这里简化处理,实际需要根据题目序号映射)
|
||||
List<ScaleImportVO.FactorRuleImportVO> rules = new ArrayList<>();
|
||||
String[] itemNumbers = def[2].split(",");
|
||||
for (String itemNumStr : itemNumbers)
|
||||
{
|
||||
try
|
||||
{
|
||||
Integer itemNumber = Integer.parseInt(itemNumStr.trim());
|
||||
ScaleImportVO.FactorRuleImportVO ruleVO = new ScaleImportVO.FactorRuleImportVO();
|
||||
ruleVO.setItemNumber(itemNumber);
|
||||
|
||||
// 创建默认的计分规则(简单求和)
|
||||
PsyFactorRule rule = new PsyFactorRule();
|
||||
rule.setWeight(BigDecimal.ONE); // 权重为1
|
||||
rule.setCalculationType("sum"); // 求和
|
||||
ruleVO.setRule(rule);
|
||||
|
||||
rules.add(ruleVO);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
log.debug("解析题目编号失败:{}", itemNumStr);
|
||||
}
|
||||
}
|
||||
|
||||
ScaleImportVO.FactorImportVO factorVO = new ScaleImportVO.FactorImportVO();
|
||||
factorVO.setFactor(factor);
|
||||
factorVO.setRules(rules);
|
||||
|
||||
factors.add(factorVO);
|
||||
}
|
||||
|
||||
log.info("解析SCL-90因子完成,共 {} 个因子", factors.size());
|
||||
|
||||
return factors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析因子(通用方法)
|
||||
*/
|
||||
private List<ScaleImportVO.FactorImportVO> parseFactors(String text, List<ScaleImportVO.ItemImportVO> items)
|
||||
{
|
||||
List<ScaleImportVO.FactorImportVO> factors = new ArrayList<>();
|
||||
|
||||
// 使用正则表达式匹配因子
|
||||
Matcher factorMatcher = FACTOR_PATTERN.matcher(text);
|
||||
Map<Integer, String> factorMap = new HashMap<>();
|
||||
|
||||
while (factorMatcher.find())
|
||||
{
|
||||
try
|
||||
{
|
||||
String factorName = factorMatcher.group(1).trim();
|
||||
|
||||
// 尝试提取因子序号
|
||||
int factorOrder = extractFactorOrder(factorName, factorMap.size() + 1);
|
||||
|
||||
factorMap.put(factorOrder, factorName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.debug("解析因子失败:{}", e.getMessage());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("识别到 {} 个因子", factorMap.size());
|
||||
|
||||
// 创建因子对象
|
||||
factorMap.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.forEach(entry -> {
|
||||
try
|
||||
{
|
||||
int order = entry.getKey();
|
||||
String name = entry.getValue();
|
||||
|
||||
PsyFactor factor = new PsyFactor();
|
||||
factor.setFactorCode("F" + order);
|
||||
factor.setFactorName(name);
|
||||
factor.setFactorOrder(order);
|
||||
|
||||
ScaleImportVO.FactorImportVO factorVO = new ScaleImportVO.FactorImportVO();
|
||||
factorVO.setFactor(factor);
|
||||
factorVO.setRules(new ArrayList<>()); // 计分规则需要手动配置
|
||||
|
||||
factors.add(factorVO);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.warn("创建因子失败,因子序号:{},错误:{}", entry.getKey(), e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
return factors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取因子序号
|
||||
*/
|
||||
private int extractFactorOrder(String factorName, int defaultOrder)
|
||||
{
|
||||
// 尝试从因子名称中提取数字
|
||||
Pattern numPattern = Pattern.compile("\\d+");
|
||||
Matcher numMatcher = numPattern.matcher(factorName);
|
||||
if (numMatcher.find())
|
||||
{
|
||||
try
|
||||
{
|
||||
return Integer.parseInt(numMatcher.group());
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
// 忽略
|
||||
}
|
||||
}
|
||||
|
||||
return defaultOrder;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package com.ddnai.system.service.impl.psychology;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
@ -116,9 +116,17 @@ public class PsyAssessmentReportServiceImpl implements IPsyAssessmentReportServi
|
|||
|
||||
// 2. 获取量表信息
|
||||
PsyScale scale = scaleService.selectScaleById(assessment.getScaleId());
|
||||
if (scale == null)
|
||||
{
|
||||
throw new RuntimeException("量表不存在");
|
||||
}
|
||||
|
||||
// 3. 获取所有答案
|
||||
List<PsyAssessmentAnswer> answers = answerService.selectAnswerListByAssessmentId(assessmentId);
|
||||
if (answers == null || answers.isEmpty())
|
||||
{
|
||||
throw new RuntimeException("测评答案为空,无法生成报告");
|
||||
}
|
||||
|
||||
// 4. 获取因子列表和计分规则
|
||||
List<PsyFactor> factors = factorService.selectFactorListByScaleId(assessment.getScaleId());
|
||||
|
|
@ -137,10 +145,16 @@ public class PsyAssessmentReportServiceImpl implements IPsyAssessmentReportServi
|
|||
List<PsyFactorRule> rules = factorRulesMap.get(factor.getFactorId());
|
||||
if (rules == null || rules.isEmpty())
|
||||
{
|
||||
System.out.println("因子 " + factor.getFactorName() + " (ID: " + factor.getFactorId() + ") 没有计分规则,跳过");
|
||||
continue;
|
||||
}
|
||||
|
||||
BigDecimal factorScore = calculateFactorScore(factor, rules, answers);
|
||||
if (factorScore.compareTo(BigDecimal.ZERO) > 0 || rules.stream().anyMatch(rule -> {
|
||||
PsyAssessmentAnswer answer = findAnswerByItemId(answers, rule.getItemId());
|
||||
return answer != null;
|
||||
}))
|
||||
{
|
||||
totalScore = totalScore.add(factorScore);
|
||||
|
||||
PsyFactorScore score = new PsyFactorScore();
|
||||
|
|
@ -150,6 +164,7 @@ public class PsyAssessmentReportServiceImpl implements IPsyAssessmentReportServi
|
|||
score.setCreateTime(DateUtils.getNowDate());
|
||||
factorScores.add(score);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 批量保存因子得分
|
||||
if (!factorScores.isEmpty())
|
||||
|
|
@ -159,6 +174,10 @@ public class PsyAssessmentReportServiceImpl implements IPsyAssessmentReportServi
|
|||
// 保存新得分
|
||||
factorScoreService.batchInsertFactorScore(factorScores);
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("警告:没有计算出任何因子得分,答案数量: " + answers.size() + ", 因子数量: " + factors.size());
|
||||
}
|
||||
|
||||
// 7. 更新测评总分
|
||||
assessment.setTotalScore(totalScore);
|
||||
|
|
@ -166,13 +185,33 @@ public class PsyAssessmentReportServiceImpl implements IPsyAssessmentReportServi
|
|||
assessmentService.updateAssessment(assessment);
|
||||
|
||||
// 8. 生成报告内容
|
||||
String reportContent = generateReportContent(scale, factors, factorScores, assessment);
|
||||
String reportContent = generateReportContent(scale, factors, factorScores, assessment, answers);
|
||||
|
||||
// 9. 生成报告摘要
|
||||
String summary = generateSummary(factors, factorScores, assessment);
|
||||
String summary = generateSummary(factors, factorScores, assessment, answers);
|
||||
|
||||
// 10. 保存报告
|
||||
PsyAssessmentReport report = new PsyAssessmentReport();
|
||||
// 10. 检查是否已存在报告,如果存在则更新,否则新建
|
||||
PsyAssessmentReport existingReport = reportMapper.selectReportByAssessmentId(assessmentId);
|
||||
PsyAssessmentReport report;
|
||||
|
||||
if (existingReport != null)
|
||||
{
|
||||
// 更新现有报告
|
||||
report = existingReport;
|
||||
report.setReportType("standard");
|
||||
report.setReportTitle(scale.getScaleName() + "测评报告");
|
||||
report.setReportContent(reportContent);
|
||||
report.setSummary(summary);
|
||||
report.setIsGenerated("1");
|
||||
report.setGenerateTime(DateUtils.getNowDate());
|
||||
report.setUpdateTime(DateUtils.getNowDate());
|
||||
reportMapper.updateReport(report);
|
||||
System.out.println("更新已存在的报告,reportId: " + report.getReportId());
|
||||
}
|
||||
else
|
||||
{
|
||||
// 创建新报告
|
||||
report = new PsyAssessmentReport();
|
||||
report.setAssessmentId(assessmentId);
|
||||
report.setReportType("standard");
|
||||
report.setReportTitle(scale.getScaleName() + "测评报告");
|
||||
|
|
@ -183,6 +222,8 @@ public class PsyAssessmentReportServiceImpl implements IPsyAssessmentReportServi
|
|||
report.setCreateBy("system");
|
||||
report.setCreateTime(DateUtils.getNowDate());
|
||||
reportMapper.insertReport(report);
|
||||
System.out.println("创建新报告,reportId: " + report.getReportId() + ", assessmentId: " + assessmentId);
|
||||
}
|
||||
|
||||
// 11. 自动预警检测(在报告生成后触发)
|
||||
try
|
||||
|
|
@ -205,17 +246,37 @@ public class PsyAssessmentReportServiceImpl implements IPsyAssessmentReportServi
|
|||
private BigDecimal calculateFactorScore(PsyFactor factor, List<PsyFactorRule> rules, List<PsyAssessmentAnswer> answers)
|
||||
{
|
||||
BigDecimal score = BigDecimal.ZERO;
|
||||
int matchedCount = 0;
|
||||
|
||||
for (PsyFactorRule rule : rules)
|
||||
{
|
||||
PsyAssessmentAnswer answer = findAnswerByItemId(answers, rule.getItemId());
|
||||
if (answer == null)
|
||||
if (rule.getItemId() == null)
|
||||
{
|
||||
System.out.println("警告:因子 " + factor.getFactorName() + " 的计分规则缺少 itemId");
|
||||
continue;
|
||||
}
|
||||
|
||||
PsyAssessmentAnswer answer = findAnswerByItemId(answers, rule.getItemId());
|
||||
if (answer == null)
|
||||
{
|
||||
System.out.println("警告:因子 " + factor.getFactorName() + " 的计分规则中题目ID " + rule.getItemId() + " 没有找到对应的答案");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (answer.getAnswerScore() == null)
|
||||
{
|
||||
System.out.println("警告:题目ID " + rule.getItemId() + " 的答案得分为空");
|
||||
continue;
|
||||
}
|
||||
|
||||
matchedCount++;
|
||||
|
||||
// 根据计分方式计算
|
||||
String calcType = rule.getCalculationType();
|
||||
if (calcType == null || calcType.isEmpty())
|
||||
{
|
||||
calcType = "sum"; // 默认求和
|
||||
}
|
||||
BigDecimal weight = rule.getWeight() != null ? rule.getWeight() : BigDecimal.ONE;
|
||||
|
||||
switch (calcType)
|
||||
|
|
@ -224,7 +285,7 @@ public class PsyAssessmentReportServiceImpl implements IPsyAssessmentReportServi
|
|||
score = score.add(answer.getAnswerScore().multiply(weight));
|
||||
break;
|
||||
case "average":
|
||||
// 平均分需要特殊处理
|
||||
// 平均分需要特殊处理,先求和,最后除以数量
|
||||
score = score.add(answer.getAnswerScore().multiply(weight));
|
||||
break;
|
||||
case "max":
|
||||
|
|
@ -246,6 +307,17 @@ public class PsyAssessmentReportServiceImpl implements IPsyAssessmentReportServi
|
|||
}
|
||||
}
|
||||
|
||||
// 如果是平均分计算,需要除以匹配的数量
|
||||
if (matchedCount > 0 && rules.size() > 0)
|
||||
{
|
||||
String calcType = rules.get(0).getCalculationType();
|
||||
if (calcType != null && "average".equals(calcType))
|
||||
{
|
||||
score = score.divide(BigDecimal.valueOf(matchedCount), 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("因子 " + factor.getFactorName() + " (ID: " + factor.getFactorId() + ") 得分: " + score + ", 匹配规则数: " + matchedCount + "/" + rules.size());
|
||||
return score;
|
||||
}
|
||||
|
||||
|
|
@ -263,18 +335,34 @@ public class PsyAssessmentReportServiceImpl implements IPsyAssessmentReportServi
|
|||
/**
|
||||
* 生成报告内容
|
||||
*/
|
||||
private String generateReportContent(PsyScale scale, List<PsyFactor> factors, List<PsyFactorScore> factorScores, PsyAssessment assessment)
|
||||
private String generateReportContent(PsyScale scale, List<PsyFactor> factors, List<PsyFactorScore> factorScores, PsyAssessment assessment, List<PsyAssessmentAnswer> answers)
|
||||
{
|
||||
StringBuilder content = new StringBuilder();
|
||||
content.append("<div class='report-container'>");
|
||||
content.append("<h1>").append(scale.getScaleName()).append("测评报告</h1>");
|
||||
content.append("<p class='report-info'>测评时间:").append(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, assessment.getSubmitTime())).append("</p>");
|
||||
content.append("<p class='report-info'>被测评人:").append(assessment.getAssesseeName()).append("</p>");
|
||||
content.append("<p class='report-info'>被测评人:").append(assessment.getAssesseeName() != null ? assessment.getAssesseeName() : "未填写").append("</p>");
|
||||
content.append("<p class='report-info'>总题目数:").append(answers != null ? answers.size() : 0).append("</p>");
|
||||
content.append("<p class='report-info'>总分:").append(assessment.getTotalScore() != null ? assessment.getTotalScore() : BigDecimal.ZERO).append("</p>");
|
||||
|
||||
// 因子得分表格
|
||||
content.append("<h2>因子得分</h2>");
|
||||
content.append("<table class='score-table'>");
|
||||
content.append("<thead><tr><th>因子名称</th><th>得分</th><th>解释</th></tr></thead>");
|
||||
|
||||
if (factorScores == null || factorScores.isEmpty())
|
||||
{
|
||||
content.append("<p style='color: #f56c6c;'>警告:未能计算出因子得分。可能原因:</p>");
|
||||
content.append("<ul>");
|
||||
content.append("<li>因子没有配置计分规则</li>");
|
||||
content.append("<li>计分规则中的题目ID与答案中的题目ID不匹配</li>");
|
||||
content.append("<li>答案得分数据异常</li>");
|
||||
content.append("</ul>");
|
||||
content.append("<p>因子数量:").append(factors != null ? factors.size() : 0).append("</p>");
|
||||
content.append("<p>答案数量:").append(answers != null ? answers.size() : 0).append("</p>");
|
||||
}
|
||||
else
|
||||
{
|
||||
content.append("<table class='score-table' style='width: 100%; border-collapse: collapse; margin: 20px 0;'>");
|
||||
content.append("<thead><tr style='background-color: #f5f7fa;'><th style='padding: 10px; border: 1px solid #ddd;'>因子名称</th><th style='padding: 10px; border: 1px solid #ddd;'>得分</th><th style='padding: 10px; border: 1px solid #ddd;'>解释</th></tr></thead>");
|
||||
content.append("<tbody>");
|
||||
|
||||
for (PsyFactorScore factorScore : factorScores)
|
||||
|
|
@ -299,32 +387,56 @@ public class PsyAssessmentReportServiceImpl implements IPsyAssessmentReportServi
|
|||
{
|
||||
interpretationText = interpretation.getInterpretationContent() != null
|
||||
? interpretation.getInterpretationContent() : "";
|
||||
if (interpretation.getSuggestions() != null && !interpretation.getSuggestions().isEmpty())
|
||||
{
|
||||
if (!interpretationText.isEmpty())
|
||||
{
|
||||
interpretationText += "<br/>";
|
||||
}
|
||||
interpretationText += "<strong>建议:</strong>" + interpretation.getSuggestions();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
interpretationText = "暂无解释";
|
||||
}
|
||||
|
||||
content.append("<tr>");
|
||||
content.append("<td>").append(factor.getFactorName()).append("</td>");
|
||||
content.append("<td>").append(factorScore.getFactorScore()).append("</td>");
|
||||
content.append("<td>").append(interpretationText).append("</td>");
|
||||
content.append("<td style='padding: 10px; border: 1px solid #ddd;'>").append(factor.getFactorName()).append("</td>");
|
||||
content.append("<td style='padding: 10px; border: 1px solid #ddd; text-align: center;'>").append(factorScore.getFactorScore()).append("</td>");
|
||||
content.append("<td style='padding: 10px; border: 1px solid #ddd;'>").append(interpretationText).append("</td>");
|
||||
content.append("</tr>");
|
||||
}
|
||||
|
||||
content.append("</tbody>");
|
||||
content.append("</table>");
|
||||
}
|
||||
|
||||
// 总体结论
|
||||
content.append("<h2>总体结论</h2>");
|
||||
if (assessment.getTotalScore() != null)
|
||||
{
|
||||
PsyResultInterpretation overallInterpretation = interpretationService.selectInterpretationByScore(
|
||||
scale.getScaleId(), null, assessment.getTotalScore());
|
||||
|
||||
if (overallInterpretation != null)
|
||||
{
|
||||
content.append("<p>").append(overallInterpretation.getInterpretationContent()).append("</p>");
|
||||
if (overallInterpretation.getSuggestions() != null)
|
||||
content.append("<p>").append(overallInterpretation.getInterpretationContent() != null ? overallInterpretation.getInterpretationContent() : "暂无结论").append("</p>");
|
||||
if (overallInterpretation.getSuggestions() != null && !overallInterpretation.getSuggestions().isEmpty())
|
||||
{
|
||||
content.append("<h3>建议</h3>");
|
||||
content.append("<p>").append(overallInterpretation.getSuggestions()).append("</p>");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
content.append("<p>暂无总体结论,请配置总体解释规则。</p>");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
content.append("<p style='color: #f56c6c;'>警告:总分为空,无法生成总体结论。</p>");
|
||||
}
|
||||
|
||||
content.append("</div>");
|
||||
return content.toString();
|
||||
|
|
@ -333,16 +445,21 @@ public class PsyAssessmentReportServiceImpl implements IPsyAssessmentReportServi
|
|||
/**
|
||||
* 生成报告摘要
|
||||
*/
|
||||
private String generateSummary(List<PsyFactor> factors, List<PsyFactorScore> factorScores, PsyAssessment assessment)
|
||||
private String generateSummary(List<PsyFactor> factors, List<PsyFactorScore> factorScores, PsyAssessment assessment, List<PsyAssessmentAnswer> answers)
|
||||
{
|
||||
StringBuilder summary = new StringBuilder();
|
||||
summary.append("本次测评完成于").append(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, assessment.getSubmitTime()));
|
||||
summary.append(",共评估了").append(factors.size()).append("个因子。");
|
||||
summary.append("总分:").append(assessment.getTotalScore()).append("。");
|
||||
summary.append(",共评估了").append(factors != null ? factors.size() : 0).append("个因子");
|
||||
summary.append(",完成").append(answers != null ? answers.size() : 0).append("道题目。");
|
||||
summary.append("总分:").append(assessment.getTotalScore() != null ? assessment.getTotalScore() : BigDecimal.ZERO).append("。");
|
||||
|
||||
if (!factorScores.isEmpty())
|
||||
if (factorScores != null && !factorScores.isEmpty())
|
||||
{
|
||||
summary.append("各因子得分情况良好,详见报告正文。");
|
||||
summary.append("成功计算出").append(factorScores.size()).append("个因子的得分,详见报告正文。");
|
||||
}
|
||||
else
|
||||
{
|
||||
summary.append("警告:未能计算出因子得分,请检查因子计分规则配置。");
|
||||
}
|
||||
|
||||
return summary.toString();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,36 @@
|
|||
package com.ddnai.system.service.impl.psychology;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.ddnai.system.domain.psychology.PsyScale;
|
||||
import com.ddnai.system.domain.psychology.PsyFactor;
|
||||
import com.ddnai.system.domain.psychology.PsyScaleItem;
|
||||
import com.ddnai.system.domain.psychology.PsyScaleOption;
|
||||
import com.ddnai.system.domain.psychology.PsyFactorRule;
|
||||
import com.ddnai.system.domain.psychology.PsyResultInterpretation;
|
||||
import com.ddnai.system.domain.psychology.PsyWarningRule;
|
||||
import com.ddnai.system.domain.psychology.vo.ScaleImportVO;
|
||||
import com.ddnai.system.mapper.psychology.PsyScaleMapper;
|
||||
import com.ddnai.system.service.psychology.IPsyScaleService;
|
||||
import com.ddnai.system.service.psychology.IPsyFactorService;
|
||||
import com.ddnai.system.service.psychology.IPsyScaleItemService;
|
||||
import com.ddnai.system.service.psychology.IPsyScaleOptionService;
|
||||
import com.ddnai.system.service.psychology.IPsyFactorRuleService;
|
||||
import com.ddnai.system.service.psychology.IPsyResultInterpretationService;
|
||||
import com.ddnai.system.service.psychology.IPsyWarningRuleService;
|
||||
import com.ddnai.system.service.psychology.IPsyAssessmentService;
|
||||
import com.ddnai.system.mapper.psychology.PsyAssessmentMapper;
|
||||
import com.ddnai.system.mapper.psychology.PsyAssessmentAnswerMapper;
|
||||
import com.ddnai.system.mapper.psychology.PsyAssessmentReportMapper;
|
||||
import com.ddnai.system.mapper.psychology.PsyFactorScoreMapper;
|
||||
import com.ddnai.system.mapper.psychology.PsyScalePermissionMapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 心理量表 服务层实现
|
||||
|
|
@ -15,9 +40,47 @@ import com.ddnai.system.service.psychology.IPsyScaleService;
|
|||
@Service
|
||||
public class PsyScaleServiceImpl implements IPsyScaleService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(PsyScaleServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private PsyScaleMapper scaleMapper;
|
||||
|
||||
@Autowired
|
||||
private IPsyFactorService factorService;
|
||||
|
||||
@Autowired
|
||||
private IPsyScaleItemService itemService;
|
||||
|
||||
@Autowired
|
||||
private IPsyScaleOptionService optionService;
|
||||
|
||||
@Autowired
|
||||
private IPsyFactorRuleService factorRuleService;
|
||||
|
||||
@Autowired
|
||||
private IPsyResultInterpretationService interpretationService;
|
||||
|
||||
@Autowired
|
||||
private IPsyWarningRuleService warningRuleService;
|
||||
|
||||
@Autowired
|
||||
private PsyAssessmentMapper assessmentMapper;
|
||||
|
||||
@Autowired
|
||||
private IPsyAssessmentService assessmentService;
|
||||
|
||||
@Autowired
|
||||
private PsyAssessmentAnswerMapper assessmentAnswerMapper;
|
||||
|
||||
@Autowired
|
||||
private PsyAssessmentReportMapper assessmentReportMapper;
|
||||
|
||||
@Autowired
|
||||
private PsyFactorScoreMapper factorScoreMapper;
|
||||
|
||||
@Autowired
|
||||
private PsyScalePermissionMapper scalePermissionMapper;
|
||||
|
||||
/**
|
||||
* 查询量表信息
|
||||
*
|
||||
|
|
@ -73,11 +136,131 @@ public class PsyScaleServiceImpl implements IPsyScaleService
|
|||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int deleteScaleByIds(Long[] scaleIds)
|
||||
{
|
||||
// 删除每个量表的关联数据
|
||||
for (Long scaleId : scaleIds)
|
||||
{
|
||||
// 1. 查询该量表的所有测评记录
|
||||
com.ddnai.system.domain.psychology.PsyAssessment assessmentQuery =
|
||||
new com.ddnai.system.domain.psychology.PsyAssessment();
|
||||
assessmentQuery.setScaleId(scaleId);
|
||||
List<com.ddnai.system.domain.psychology.PsyAssessment> assessments =
|
||||
assessmentService.selectAssessmentList(assessmentQuery);
|
||||
|
||||
if (assessments != null && !assessments.isEmpty())
|
||||
{
|
||||
Long[] assessmentIds = assessments.stream()
|
||||
.map(com.ddnai.system.domain.psychology.PsyAssessment::getAssessmentId)
|
||||
.toArray(Long[]::new);
|
||||
|
||||
// 2. 删除测评答案
|
||||
for (Long assessmentId : assessmentIds)
|
||||
{
|
||||
assessmentAnswerMapper.deleteAnswerByAssessmentId(assessmentId);
|
||||
}
|
||||
|
||||
// 3. 删除测评报告
|
||||
for (Long assessmentId : assessmentIds)
|
||||
{
|
||||
com.ddnai.system.domain.psychology.PsyAssessmentReport reportByAssessment =
|
||||
assessmentReportMapper.selectReportByAssessmentId(assessmentId);
|
||||
if (reportByAssessment != null)
|
||||
{
|
||||
assessmentReportMapper.deleteReportById(reportByAssessment.getReportId());
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 删除因子得分
|
||||
for (Long assessmentId : assessmentIds)
|
||||
{
|
||||
factorScoreMapper.deleteFactorScoreByAssessmentId(assessmentId);
|
||||
}
|
||||
|
||||
// 5. 删除测评记录
|
||||
assessmentMapper.deleteAssessmentByScaleIds(new Long[]{scaleId});
|
||||
}
|
||||
|
||||
// 6. 删除量表权限
|
||||
scalePermissionMapper.deletePermissionByScaleId(scaleId);
|
||||
}
|
||||
|
||||
// 删除量表相关的其他数据(因子、题目、选项、规则等)
|
||||
// 注意:这些数据的删除应该在Mapper的deleteScaleByIds中通过外键级联删除,或者在Service中手动删除
|
||||
// 由于数据库外键约束,我们需要手动删除关联数据
|
||||
|
||||
for (Long scaleId : scaleIds)
|
||||
{
|
||||
// 删除因子计分规则(通过因子删除)
|
||||
List<PsyFactor> factors = factorService.selectFactorListByScaleId(scaleId);
|
||||
if (factors != null && !factors.isEmpty())
|
||||
{
|
||||
for (PsyFactor factor : factors)
|
||||
{
|
||||
factorRuleService.deleteRulesByFactorId(factor.getFactorId());
|
||||
}
|
||||
Long[] factorIds = factors.stream().map(PsyFactor::getFactorId).toArray(Long[]::new);
|
||||
factorService.deleteFactorByIds(factorIds);
|
||||
}
|
||||
|
||||
// 删除题目选项(通过题目删除)
|
||||
List<PsyScaleItem> items = itemService.selectItemListByScaleId(scaleId);
|
||||
if (items != null && !items.isEmpty())
|
||||
{
|
||||
for (PsyScaleItem item : items)
|
||||
{
|
||||
optionService.deleteOptionsByItemId(item.getItemId());
|
||||
}
|
||||
Long[] itemIds = items.stream().map(PsyScaleItem::getItemId).toArray(Long[]::new);
|
||||
itemService.deleteItemByIds(itemIds);
|
||||
}
|
||||
|
||||
// 删除解释配置
|
||||
PsyResultInterpretation interpretation = new PsyResultInterpretation();
|
||||
interpretation.setScaleId(scaleId);
|
||||
List<com.ddnai.system.domain.psychology.PsyResultInterpretation> interpretations =
|
||||
interpretationService.selectInterpretationList(interpretation);
|
||||
if (interpretations != null && !interpretations.isEmpty())
|
||||
{
|
||||
Long[] interpretationIds = interpretations.stream()
|
||||
.map(com.ddnai.system.domain.psychology.PsyResultInterpretation::getInterpretationId)
|
||||
.toArray(Long[]::new);
|
||||
interpretationService.deleteInterpretationByIds(interpretationIds);
|
||||
}
|
||||
|
||||
// 删除预警规则
|
||||
com.ddnai.system.domain.psychology.PsyWarningRule warningRule =
|
||||
new com.ddnai.system.domain.psychology.PsyWarningRule();
|
||||
warningRule.setScaleId(scaleId);
|
||||
List<com.ddnai.system.domain.psychology.PsyWarningRule> warningRules =
|
||||
warningRuleService.selectWarningRuleList(warningRule);
|
||||
if (warningRules != null && !warningRules.isEmpty())
|
||||
{
|
||||
Long[] ruleIds = warningRules.stream()
|
||||
.map(com.ddnai.system.domain.psychology.PsyWarningRule::getRuleId)
|
||||
.toArray(Long[]::new);
|
||||
warningRuleService.deleteWarningRuleByIds(ruleIds);
|
||||
}
|
||||
}
|
||||
|
||||
// 最后删除量表
|
||||
return scaleMapper.deleteScaleByIds(scaleIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查量表是否被使用
|
||||
*
|
||||
* @param scaleId 量表ID
|
||||
* @return 是否被使用
|
||||
*/
|
||||
@Override
|
||||
public boolean isScaleInUse(Long scaleId)
|
||||
{
|
||||
int count = scaleMapper.countAssessmentByScaleId(scaleId);
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查量表编码是否唯一
|
||||
*
|
||||
|
|
@ -90,5 +273,370 @@ public class PsyScaleServiceImpl implements IPsyScaleService
|
|||
int count = scaleMapper.checkScaleCodeUnique(scaleCode);
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入量表(原有方法,保持向后兼容)
|
||||
*
|
||||
* @param importData 导入数据
|
||||
* @param username 操作人
|
||||
* @return 新插入的量表ID
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long importScale(ScaleImportVO importData, String username)
|
||||
{
|
||||
// 调用支持factorCode映射的版本,传入null作为rawJsonData
|
||||
return importScale(importData, null, username);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入量表(支持factorCode映射)
|
||||
*
|
||||
* @param importData 导入数据
|
||||
* @param rawJsonData 原始JSON数据(用于提取factorCode,可为null)
|
||||
* @param username 操作人
|
||||
* @return 新插入的量表ID
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long importScale(ScaleImportVO importData, java.util.Map<String, Object> rawJsonData, String username)
|
||||
{
|
||||
if (importData == null || importData.getScale() == null)
|
||||
{
|
||||
throw new RuntimeException("导入数据不能为空");
|
||||
}
|
||||
|
||||
PsyScale scale = importData.getScale();
|
||||
|
||||
// 检查量表编码是否已存在
|
||||
if (!checkScaleCodeUnique(scale.getScaleCode()))
|
||||
{
|
||||
throw new RuntimeException("量表编码已存在:" + scale.getScaleCode());
|
||||
}
|
||||
|
||||
// 设置创建人
|
||||
scale.setCreateBy(username);
|
||||
scale.setItemCount(null); // 题目数量由系统自动计算,不导入
|
||||
|
||||
// 1. 插入量表基本信息
|
||||
int insertResult = scaleMapper.insertScale(scale);
|
||||
Long scaleId = scale.getScaleId();
|
||||
|
||||
// 验证scaleId是否成功获取
|
||||
if (scaleId == null)
|
||||
{
|
||||
log.error("插入量表失败,无法获取量表ID。insertResult: {}", insertResult);
|
||||
throw new RuntimeException("插入量表失败,无法获取量表ID。请检查数据库配置和主键生成策略。");
|
||||
}
|
||||
|
||||
log.info("插入量表成功,scaleId: {}, 量表名称: {}", scaleId, scale.getScaleName());
|
||||
|
||||
// 2. 建立因子映射(旧因子编码 -> 新因子ID)
|
||||
Map<String, Long> factorCodeMap = new HashMap<>();
|
||||
if (importData.getFactors() != null && !importData.getFactors().isEmpty())
|
||||
{
|
||||
log.info("开始导入因子,共 {} 个", importData.getFactors().size());
|
||||
for (ScaleImportVO.FactorImportVO factorVO : importData.getFactors())
|
||||
{
|
||||
PsyFactor factor = factorVO.getFactor();
|
||||
if (factor != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保scaleId已设置
|
||||
if (factor.getScaleId() == null)
|
||||
{
|
||||
factor.setScaleId(scaleId);
|
||||
}
|
||||
// 确保创建人已设置
|
||||
if (StringUtils.isBlank(factor.getCreateBy()))
|
||||
{
|
||||
factor.setCreateBy(username);
|
||||
}
|
||||
|
||||
log.debug("插入因子: {} - {}", factor.getFactorCode(), factor.getFactorName());
|
||||
factorService.insertFactor(factor);
|
||||
|
||||
// 验证因子ID是否成功获取
|
||||
if (factor.getFactorId() == null)
|
||||
{
|
||||
log.error("插入因子失败,无法获取因子ID。因子编码: {}", factor.getFactorCode());
|
||||
throw new RuntimeException("插入因子失败,无法获取因子ID: " + factor.getFactorCode());
|
||||
}
|
||||
|
||||
// 保存因子编码映射
|
||||
if (StringUtils.isNotBlank(factor.getFactorCode()))
|
||||
{
|
||||
factorCodeMap.put(factor.getFactorCode(), factor.getFactorId());
|
||||
log.debug("因子映射: {} -> {}", factor.getFactorCode(), factor.getFactorId());
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("插入因子失败: {} - {}, 错误: {}",
|
||||
factor.getFactorCode(), factor.getFactorName(), e.getMessage(), e);
|
||||
throw new RuntimeException("插入因子失败: " + factor.getFactorName() + ", " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("因子导入完成,共导入 {} 个因子", factorCodeMap.size());
|
||||
}
|
||||
|
||||
// 3. 建立题目映射(旧题目序号 -> 新题目ID)
|
||||
Map<Integer, Long> itemNumberMap = new HashMap<>();
|
||||
if (importData.getItems() != null && !importData.getItems().isEmpty())
|
||||
{
|
||||
log.info("开始导入题目,共 {} 个", importData.getItems().size());
|
||||
int successCount = 0;
|
||||
for (ScaleImportVO.ItemImportVO itemVO : importData.getItems())
|
||||
{
|
||||
PsyScaleItem item = itemVO.getItem();
|
||||
if (item != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保scaleId已设置
|
||||
if (item.getScaleId() == null)
|
||||
{
|
||||
item.setScaleId(scaleId);
|
||||
}
|
||||
// 确保创建人已设置
|
||||
if (StringUtils.isBlank(item.getCreateBy()))
|
||||
{
|
||||
item.setCreateBy(username);
|
||||
}
|
||||
|
||||
log.debug("插入题目: {} - {}", item.getItemNumber(),
|
||||
item.getItemContent() != null && item.getItemContent().length() > 20
|
||||
? item.getItemContent().substring(0, 20) + "..."
|
||||
: item.getItemContent());
|
||||
|
||||
itemService.insertItem(item);
|
||||
|
||||
Long itemId = item.getItemId();
|
||||
Integer itemNumber = item.getItemNumber();
|
||||
|
||||
// 验证题目ID是否成功获取
|
||||
if (itemId == null)
|
||||
{
|
||||
log.error("插入题目失败,无法获取题目ID。题目序号: {}", itemNumber);
|
||||
throw new RuntimeException("插入题目失败,无法获取题目ID:题目 " + itemNumber);
|
||||
}
|
||||
|
||||
// 保存题目序号映射
|
||||
if (itemNumber != null)
|
||||
{
|
||||
itemNumberMap.put(itemNumber, itemId);
|
||||
}
|
||||
|
||||
// 插入题目选项
|
||||
if (itemVO.getOptions() != null && !itemVO.getOptions().isEmpty())
|
||||
{
|
||||
for (PsyScaleOption option : itemVO.getOptions())
|
||||
{
|
||||
option.setItemId(itemId);
|
||||
if (StringUtils.isBlank(option.getCreateBy()))
|
||||
{
|
||||
option.setCreateBy(username);
|
||||
}
|
||||
}
|
||||
optionService.saveOptions(itemId, itemVO.getOptions());
|
||||
log.debug("题目 {} 的 {} 个选项已保存", itemNumber, itemVO.getOptions().size());
|
||||
}
|
||||
|
||||
successCount++;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("插入题目失败: 题目序号 {}, 错误: {}",
|
||||
item.getItemNumber(), e.getMessage(), e);
|
||||
throw new RuntimeException("插入题目失败: 题目 " + item.getItemNumber() + ", " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("题目导入完成,成功导入 {} 个题目", successCount);
|
||||
}
|
||||
|
||||
// 4. 插入计分规则(需要映射因子和题目ID)
|
||||
if (importData.getFactors() != null && !importData.getFactors().isEmpty())
|
||||
{
|
||||
log.info("开始导入计分规则");
|
||||
int ruleCount = 0;
|
||||
for (ScaleImportVO.FactorImportVO factorVO : importData.getFactors())
|
||||
{
|
||||
if (factorVO.getRules() != null && !factorVO.getRules().isEmpty())
|
||||
{
|
||||
Long factorId = factorCodeMap.get(factorVO.getFactor().getFactorCode());
|
||||
if (factorId != null)
|
||||
{
|
||||
List<PsyFactorRule> rules = new java.util.ArrayList<>();
|
||||
for (ScaleImportVO.FactorRuleImportVO ruleVO : factorVO.getRules())
|
||||
{
|
||||
PsyFactorRule rule = ruleVO.getRule();
|
||||
if (rule != null)
|
||||
{
|
||||
// 通过题目序号映射到新的题目ID
|
||||
Integer itemNumber = ruleVO.getItemNumber();
|
||||
if (itemNumber != null)
|
||||
{
|
||||
Long newItemId = itemNumberMap.get(itemNumber);
|
||||
if (newItemId != null)
|
||||
{
|
||||
rule.setItemId(newItemId);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.warn("找不到题目序号 {} 对应的题目ID,跳过该计分规则", itemNumber);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
rule.setFactorId(factorId);
|
||||
if (StringUtils.isBlank(rule.getCreateBy()))
|
||||
{
|
||||
rule.setCreateBy(username);
|
||||
}
|
||||
rules.add(rule);
|
||||
ruleCount++;
|
||||
}
|
||||
}
|
||||
if (!rules.isEmpty())
|
||||
{
|
||||
factorRuleService.saveRules(factorId, rules);
|
||||
log.debug("因子 {} 的 {} 条计分规则已保存", factorVO.getFactor().getFactorCode(), rules.size());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log.warn("找不到因子编码 {} 对应的因子ID,跳过计分规则", factorVO.getFactor().getFactorCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("计分规则导入完成,共导入 {} 条规则", ruleCount);
|
||||
}
|
||||
|
||||
// 5. 插入解释配置(需要映射因子ID)
|
||||
if (importData.getInterpretations() != null && !importData.getInterpretations().isEmpty())
|
||||
{
|
||||
log.info("开始导入解释配置,共 {} 条", importData.getInterpretations().size());
|
||||
int interpretationCount = 0;
|
||||
|
||||
// 从原始JSON中提取factorCode信息并映射到factorId
|
||||
for (int i = 0; i < importData.getInterpretations().size(); i++)
|
||||
{
|
||||
PsyResultInterpretation interpretation = importData.getInterpretations().get(i);
|
||||
interpretation.setScaleId(scaleId);
|
||||
|
||||
// 如果factorId为null,尝试从原始JSON中获取factorCode并映射
|
||||
if (interpretation.getFactorId() == null && rawJsonData != null && rawJsonData.containsKey("interpretations"))
|
||||
{
|
||||
Object interpretationsObj = rawJsonData.get("interpretations");
|
||||
if (interpretationsObj instanceof java.util.List)
|
||||
{
|
||||
java.util.List<?> rawInterpretations = (java.util.List<?>) interpretationsObj;
|
||||
if (i < rawInterpretations.size())
|
||||
{
|
||||
Object rawInterpretationObj = rawInterpretations.get(i);
|
||||
if (rawInterpretationObj instanceof java.util.Map)
|
||||
{
|
||||
java.util.Map<?, ?> rawMap = (java.util.Map<?, ?>) rawInterpretationObj;
|
||||
if (rawMap.containsKey("factorCode") && rawMap.get("factorCode") != null)
|
||||
{
|
||||
String factorCode = rawMap.get("factorCode").toString();
|
||||
Long mappedFactorId = factorCodeMap.get(factorCode);
|
||||
if (mappedFactorId != null)
|
||||
{
|
||||
interpretation.setFactorId(mappedFactorId);
|
||||
log.debug("通过factorCode {} 映射到factorId {}", factorCode, mappedFactorId);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.warn("找不到factorCode {} 对应的factorId,将作为总体解释", factorCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(interpretation.getCreateBy()))
|
||||
{
|
||||
interpretation.setCreateBy(username);
|
||||
}
|
||||
try
|
||||
{
|
||||
interpretationService.insertInterpretation(interpretation);
|
||||
interpretationCount++;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("插入解释配置失败: {}, 错误: {}", interpretation.getInterpretationTitle(), e.getMessage());
|
||||
}
|
||||
}
|
||||
log.info("解释配置导入完成,成功导入 {} 条", interpretationCount);
|
||||
}
|
||||
|
||||
// 6. 插入预警规则(需要映射因子ID)
|
||||
if (importData.getWarningRules() != null && !importData.getWarningRules().isEmpty())
|
||||
{
|
||||
log.info("开始导入预警规则,共 {} 条", importData.getWarningRules().size());
|
||||
int warningRuleCount = 0;
|
||||
|
||||
// 从原始JSON中提取factorCode信息
|
||||
for (int i = 0; i < importData.getWarningRules().size(); i++)
|
||||
{
|
||||
PsyWarningRule warningRule = importData.getWarningRules().get(i);
|
||||
warningRule.setScaleId(scaleId);
|
||||
|
||||
// 如果factorId为null,尝试从原始JSON中获取factorCode并映射
|
||||
if (warningRule.getFactorId() == null && rawJsonData != null && rawJsonData.containsKey("warningRules"))
|
||||
{
|
||||
Object warningRulesObj = rawJsonData.get("warningRules");
|
||||
if (warningRulesObj instanceof java.util.List)
|
||||
{
|
||||
java.util.List<?> rawWarningRules = (java.util.List<?>) warningRulesObj;
|
||||
if (i < rawWarningRules.size())
|
||||
{
|
||||
Object rawWarningRuleObj = rawWarningRules.get(i);
|
||||
if (rawWarningRuleObj instanceof java.util.Map)
|
||||
{
|
||||
java.util.Map<?, ?> rawMap = (java.util.Map<?, ?>) rawWarningRuleObj;
|
||||
if (rawMap.containsKey("factorCode") && rawMap.get("factorCode") != null)
|
||||
{
|
||||
String factorCode = rawMap.get("factorCode").toString();
|
||||
Long mappedFactorId = factorCodeMap.get(factorCode);
|
||||
if (mappedFactorId != null)
|
||||
{
|
||||
warningRule.setFactorId(mappedFactorId);
|
||||
log.debug("通过factorCode {} 映射到factorId {}", factorCode, mappedFactorId);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.warn("找不到factorCode {} 对应的factorId,将作为总体预警规则", factorCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(warningRule.getCreateBy()))
|
||||
{
|
||||
warningRule.setCreateBy(username);
|
||||
}
|
||||
try
|
||||
{
|
||||
warningRuleService.insertWarningRule(warningRule);
|
||||
warningRuleCount++;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("插入预警规则失败: {}, 错误: {}", warningRule.getRuleName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
log.info("预警规则导入完成,成功导入 {} 条", warningRuleCount);
|
||||
}
|
||||
|
||||
log.info("量表导入完成,scaleId: {}", scaleId);
|
||||
return scaleId;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package com.ddnai.system.service.psychology;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.ddnai.system.domain.psychology.vo.ScaleImportVO;
|
||||
|
||||
/**
|
||||
* 文档解析服务接口
|
||||
* 用于解析PDF、DOCX等格式的量表文档
|
||||
*
|
||||
* @author ddnai
|
||||
*/
|
||||
public interface IDocumentParseService
|
||||
{
|
||||
/**
|
||||
* 解析文档并转换为量表导入数据
|
||||
*
|
||||
* @param file 上传的文件(PDF或DOCX)
|
||||
* @return 量表导入数据
|
||||
* @throws Exception 解析异常
|
||||
*/
|
||||
ScaleImportVO parseDocument(MultipartFile file) throws Exception;
|
||||
|
||||
/**
|
||||
* 判断文件类型是否支持
|
||||
*
|
||||
* @param filename 文件名
|
||||
* @return 是否支持
|
||||
*/
|
||||
boolean isSupported(String filename);
|
||||
|
||||
/**
|
||||
* 提取文档文本内容
|
||||
*
|
||||
* @param file 上传的文件
|
||||
* @return 文档文本内容
|
||||
* @throws Exception 解析异常
|
||||
*/
|
||||
String extractText(MultipartFile file) throws Exception;
|
||||
}
|
||||
|
||||
|
|
@ -57,5 +57,33 @@ public interface IPsyScaleService
|
|||
* @return 结果
|
||||
*/
|
||||
public boolean checkScaleCodeUnique(String scaleCode);
|
||||
|
||||
/**
|
||||
* 导入量表
|
||||
*
|
||||
* @param importData 导入数据
|
||||
* @param username 操作人
|
||||
* @return 结果
|
||||
*/
|
||||
public Long importScale(com.ddnai.system.domain.psychology.vo.ScaleImportVO importData, String username);
|
||||
|
||||
/**
|
||||
* 导入量表(支持factorCode映射)
|
||||
*
|
||||
* @param importData 导入数据
|
||||
* @param rawJsonData 原始JSON数据(用于提取factorCode)
|
||||
* @param username 操作人
|
||||
* @return 结果
|
||||
*/
|
||||
public Long importScale(com.ddnai.system.domain.psychology.vo.ScaleImportVO importData,
|
||||
java.util.Map<String, Object> rawJsonData, String username);
|
||||
|
||||
/**
|
||||
* 检查量表是否被使用
|
||||
*
|
||||
* @param scaleId 量表ID
|
||||
* @return 是否被使用
|
||||
*/
|
||||
public boolean isScaleInUse(Long scaleId);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -164,5 +164,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
</foreach>
|
||||
</delete>
|
||||
|
||||
<select id="countAssessmentByScaleId" parameterType="Long" resultType="int">
|
||||
select count(1) from psy_assessment where scale_id = #{scaleId}
|
||||
</select>
|
||||
|
||||
<delete id="deleteAssessmentByScaleIds" parameterType="String">
|
||||
delete from psy_assessment where scale_id in
|
||||
<foreach item="scaleId" collection="array" open="(" separator="," close=")">
|
||||
#{scaleId}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
|
||||
<insert id="insertFactor" parameterType="com.ddnai.system.domain.psychology.PsyFactor" useGeneratedKeys="true" keyProperty="factorId">
|
||||
insert into psy_factor (
|
||||
<if test="scaleId != null">scale_id, </if>
|
||||
scale_id,
|
||||
<if test="factorCode != null and factorCode != ''">factor_code, </if>
|
||||
<if test="factorName != null and factorName != ''">factor_name, </if>
|
||||
<if test="factorEnName != null">factor_en_name, </if>
|
||||
|
|
@ -62,7 +62,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||
create_time
|
||||
)values(
|
||||
<if test="scaleId != null">#{scaleId}, </if>
|
||||
#{scaleId},
|
||||
<if test="factorCode != null and factorCode != ''">#{factorCode}, </if>
|
||||
<if test="factorName != null and factorName != ''">#{factorName}, </if>
|
||||
<if test="factorEnName != null">#{factorEnName}, </if>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
order by s.sort_order, s.create_time desc
|
||||
</select>
|
||||
|
||||
<insert id="insertScale" parameterType="com.ddnai.system.domain.psychology.PsyScale">
|
||||
<insert id="insertScale" parameterType="com.ddnai.system.domain.psychology.PsyScale" useGeneratedKeys="true" keyProperty="scaleId">
|
||||
insert into psy_scale (
|
||||
<if test="scaleCode != null and scaleCode != ''">scale_code, </if>
|
||||
<if test="scaleName != null and scaleName != ''">scale_name, </if>
|
||||
|
|
@ -153,5 +153,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
select count(1) from psy_scale where scale_code = #{scaleCode}
|
||||
</select>
|
||||
|
||||
<select id="countAssessmentByScaleId" parameterType="Long" resultType="int">
|
||||
select count(1) from psy_assessment where scale_id = #{scaleId}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
|
|
|
|||
152
z_Project change/17-管理员快速填充功能说明.md
Normal file
152
z_Project change/17-管理员快速填充功能说明.md
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
# 管理员快速填充功能说明
|
||||
|
||||
## 📋 功能概述
|
||||
|
||||
管理员在进行量表测评时,可以使用"快速填充"功能,自动选择所有题目的特定选项并提交测评。此功能主要用于测试和快速验证量表功能。
|
||||
|
||||
## 🎯 功能特性
|
||||
|
||||
### 1. 管理员专用
|
||||
- 只有管理员(userId === 1 或 roles 包含 'admin')才能看到和使用此功能
|
||||
- 普通用户无法看到快速填充按钮
|
||||
|
||||
### 2. 快速填充策略
|
||||
系统提供4种填充策略:
|
||||
|
||||
#### ① 填充第一个选项并提交
|
||||
- 为所有题目选择第一个选项(选项A或第一个选项)
|
||||
- 适用于测试最低分或基础功能
|
||||
|
||||
#### ② 填充中间选项并提交
|
||||
- 为所有题目选择中间选项
|
||||
- 例如:5个选项中选择第3个,4个选项中选择第2个
|
||||
|
||||
#### ③ 填充最后一个选项并提交
|
||||
- 为所有题目选择最后一个选项
|
||||
- 适用于测试最高分或极端情况
|
||||
|
||||
#### ④ 随机填充并提交
|
||||
- 为每个题目随机选择一个选项
|
||||
- 适用于模拟真实答题情况
|
||||
|
||||
### 3. 自动处理
|
||||
- 自动处理单选题、多选题、矩阵题
|
||||
- 自动计算得分
|
||||
- 自动保存所有答案
|
||||
- 自动提交测评并生成报告
|
||||
|
||||
## 📝 使用方法
|
||||
|
||||
### 步骤1:进入测评页面
|
||||
1. 登录系统(管理员账户)
|
||||
2. 进入 `心理测评管理` → `测评记录`
|
||||
3. 点击 **"开始测评"** 或 **"继续测评"**
|
||||
|
||||
### 步骤2:使用快速填充
|
||||
1. 在测评页面顶部,找到 **"快速填充"** 按钮
|
||||
2. 点击下拉箭头,选择填充策略:
|
||||
- 填充第一个选项并提交
|
||||
- 填充中间选项并提交
|
||||
- 填充最后一个选项并提交
|
||||
- 随机填充并提交
|
||||
|
||||
### 步骤3:确认并执行
|
||||
1. 系统会弹出确认对话框
|
||||
2. 点击 **"确定"** 后,系统会:
|
||||
- 自动填充所有题目
|
||||
- 保存所有答案
|
||||
- 提交测评
|
||||
- 生成测评报告
|
||||
3. 完成后自动跳转到测评记录页面
|
||||
|
||||
## ⚙️ 技术实现
|
||||
|
||||
### 前端实现
|
||||
- **文件位置**:`ruoyi-ui/src/views/psychology/assessment/taking.vue`
|
||||
- **关键方法**:
|
||||
- `handleQuickFill(command)`: 处理快速填充命令
|
||||
- `performQuickFill(command)`: 执行快速填充逻辑
|
||||
- `saveAnswerToServerPromise(answer)`: 保存答案到服务器
|
||||
|
||||
### 权限控制
|
||||
- 使用 `isAdmin` 计算属性判断管理员身份
|
||||
- 通过 `v-if="isAdmin"` 控制按钮显示
|
||||
- 在方法中再次验证权限
|
||||
|
||||
### 填充逻辑
|
||||
```javascript
|
||||
// 选项选择策略
|
||||
switch (command) {
|
||||
case 'first': // 第一个选项
|
||||
case 'middle': // 中间选项
|
||||
case 'last': // 最后一个选项
|
||||
case 'random': // 随机选项
|
||||
}
|
||||
|
||||
// 自动保存和提交
|
||||
Promise.all(fillPromises).then(() => {
|
||||
submitAssessment(this.assessmentId)
|
||||
})
|
||||
```
|
||||
|
||||
## 🔒 安全特性
|
||||
|
||||
1. **权限验证**:双重验证管理员身份
|
||||
2. **确认对话框**:防止误操作
|
||||
3. **错误处理**:完善的错误处理和提示
|
||||
4. **日志记录**:记录填充操作的日志
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **不可撤销**:快速填充并提交后,测评结果无法修改
|
||||
2. **覆盖已有答案**:如果题目已有答案,快速填充会覆盖原有答案
|
||||
3. **多选题处理**:多选题目前只选择第一个选项(可根据需要调整)
|
||||
4. **测试用途**:此功能主要用于测试,不建议用于真实测评数据
|
||||
|
||||
## 📊 使用场景
|
||||
|
||||
### 适用场景
|
||||
- ✅ 测试量表功能是否正常
|
||||
- ✅ 快速生成测试报告
|
||||
- ✅ 验证报告生成功能
|
||||
- ✅ 系统功能演示
|
||||
|
||||
### 不适用场景
|
||||
- ❌ 真实用户测评
|
||||
- ❌ 正式的数据收集
|
||||
- ❌ 需要准确答案的测评
|
||||
|
||||
## 🔧 自定义配置
|
||||
|
||||
如果需要修改填充策略,可以编辑 `performQuickFill` 方法中的 `optionStrategy` 函数。
|
||||
|
||||
例如,修改多选题策略为选择所有选项:
|
||||
```javascript
|
||||
else if (item.itemType === 'multiple') {
|
||||
// 多选题,选择所有选项
|
||||
selectedOptions = options.map(opt => opt.optionId);
|
||||
const totalScore = options.reduce((sum, opt) => sum + (opt.optionScore || 0), 0);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## ✅ 验证清单
|
||||
|
||||
使用前请确认:
|
||||
- [ ] 当前用户是管理员
|
||||
- [ ] 测评状态为"进行中"
|
||||
- [ ] 量表包含题目
|
||||
- [ ] 所有题目都有选项
|
||||
|
||||
## 📚 相关文件
|
||||
|
||||
- `ruoyi-ui/src/views/psychology/assessment/taking.vue` - 测评页面
|
||||
- `ruoyi-ui/src/api/psychology/assessment.js` - 测评API
|
||||
|
||||
## 🎉 更新日志
|
||||
|
||||
- **2025-01-XX**:添加管理员快速填充功能
|
||||
- 支持4种填充策略
|
||||
- 自动保存和提交
|
||||
- 完善的权限控制和错误处理
|
||||
|
||||
692
z_Project change/量表示例/16-量表导入JSON标准格式说明.md
Normal file
692
z_Project change/量表示例/16-量表导入JSON标准格式说明.md
Normal file
|
|
@ -0,0 +1,692 @@
|
|||
# 量表导入JSON标准格式说明
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本文档详细说明如何使用JSON格式导入量表数据到系统中。JSON格式适用于批量导入完整的量表数据,包括量表基本信息、题目、选项、因子、计分规则等。
|
||||
|
||||
## 🎯 导入方式
|
||||
|
||||
系统支持三种导入方式:
|
||||
|
||||
1. **JSON文本导入**:在导入界面直接粘贴JSON文本
|
||||
2. **JSON文件导入**:上传.json格式的文件
|
||||
3. **PDF/DOCX文档导入**:系统自动解析文档并转换为JSON格式(自动识别)
|
||||
|
||||
---
|
||||
|
||||
## 📝 JSON数据结构
|
||||
|
||||
### 完整JSON结构
|
||||
|
||||
```json
|
||||
{
|
||||
"scale": {
|
||||
// 量表基本信息
|
||||
},
|
||||
"items": [
|
||||
// 题目列表(包含选项)
|
||||
],
|
||||
"factors": [
|
||||
// 因子列表(包含计分规则)
|
||||
],
|
||||
"interpretations": [
|
||||
// 结果解释配置(可选)
|
||||
],
|
||||
"warningRules": [
|
||||
// 预警规则配置(可选)
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 详细字段说明
|
||||
|
||||
### 1. 量表基本信息 (scale)
|
||||
|
||||
```json
|
||||
{
|
||||
"scaleCode": "SCL_90", // 量表编码(必填,唯一)
|
||||
"scaleName": "症状自评量表SCL-90", // 量表名称(必填)
|
||||
"scaleEnName": "Symptom Checklist 90", // 量表英文名称(可选)
|
||||
"scaleType": "symptom", // 量表类型(可选)
|
||||
"scaleVersion": "1.0", // 量表版本(可选)
|
||||
"scaleIntro": "SCL-90量表简介", // 量表简介(可选)
|
||||
"scaleDescription": "SCL-90详细描述", // 量表描述(可选)
|
||||
"itemCount": 90, // 题目数量(可选,系统自动计算)
|
||||
"estimatedTime": 30, // 预计完成时间(分钟)(可选)
|
||||
"targetPopulation": "一般人群", // 适用人群(可选)
|
||||
"author": "Derogatis", // 作者(可选)
|
||||
"source": "心理卫生评定量表手册", // 来源(可选)
|
||||
"reference": "参考文献", // 参考文献(可选)
|
||||
"status": "0", // 状态:0-正常,1-停用(必填,默认"0")
|
||||
"sortOrder": 0 // 排序顺序(可选,默认0)
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
- `scaleCode`:必须唯一,建议使用英文大写字母和下划线,如 `SCL_90`、`EPQ_001`
|
||||
- `scaleName`:量表的完整中文名称
|
||||
- `scaleType`:量表类型,如 `symptom`(症状)、`personality`(人格)、`emotion`(情绪)等
|
||||
- `status`:`"0"` 表示正常(启用),`"1"` 表示停用
|
||||
|
||||
---
|
||||
|
||||
### 2. 题目列表 (items)
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"item": {
|
||||
"itemNumber": 1, // 题目序号(必填)
|
||||
"itemContent": "头痛", // 题目内容(必填)
|
||||
"itemType": "single", // 题目类型:single-单选,multiple-多选(必填)
|
||||
"required": "1", // 是否必答:0-否,1-是(必填,默认"1")
|
||||
"reverseScore": "0", // 是否反向计分:0-否,1-是(必填,默认"0")
|
||||
"sortOrder": 1 // 排序顺序(可选)
|
||||
},
|
||||
"options": [
|
||||
// 选项列表
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
- `itemNumber`:题目序号,从1开始,建议连续
|
||||
- `itemContent`:题目的完整文字内容
|
||||
- `itemType`:`"single"` 表示单选题,`"multiple"` 表示多选题
|
||||
- `required`:`"1"` 表示必答,`"0"` 表示可选
|
||||
- `reverseScore`:`"1"` 表示反向计分(得分越高表示程度越低),`"0"` 表示正常计分
|
||||
|
||||
---
|
||||
|
||||
### 3. 选项列表 (options)
|
||||
|
||||
每个题目包含一个选项数组:
|
||||
|
||||
```json
|
||||
{
|
||||
"options": [
|
||||
{
|
||||
"optionCode": "A", // 选项编码(必填)
|
||||
"optionContent": "没有", // 选项内容(必填)
|
||||
"optionScore": 0, // 选项分数(必填)
|
||||
"sortOrder": 1 // 排序顺序(可选)
|
||||
},
|
||||
{
|
||||
"optionCode": "B",
|
||||
"optionContent": "很轻",
|
||||
"optionScore": 1,
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"optionCode": "C",
|
||||
"optionContent": "中等",
|
||||
"optionScore": 2,
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"optionCode": "D",
|
||||
"optionContent": "偏重",
|
||||
"optionScore": 3,
|
||||
"sortOrder": 4
|
||||
},
|
||||
{
|
||||
"optionCode": "E",
|
||||
"optionContent": "严重",
|
||||
"optionScore": 4,
|
||||
"sortOrder": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
- `optionCode`:选项编码,通常使用 A、B、C、D、E 或 1、2、3、4、5
|
||||
- `optionContent`:选项的文字描述
|
||||
- `optionScore`:该选项对应的分数(数字类型)
|
||||
- `sortOrder`:选项的显示顺序,数字越小越靠前
|
||||
|
||||
---
|
||||
|
||||
### 4. 因子列表 (factors)
|
||||
|
||||
```json
|
||||
{
|
||||
"factors": [
|
||||
{
|
||||
"factor": {
|
||||
"factorCode": "F1", // 因子编码(必填)
|
||||
"factorName": "躯体化", // 因子名称(必填)
|
||||
"factorEnName": "Somatization", // 因子英文名称(可选)
|
||||
"factorDescription": "躯体化因子描述", // 因子描述(可选)
|
||||
"factorOrder": 1 // 因子排序(可选)
|
||||
},
|
||||
"rules": [
|
||||
// 计分规则列表
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
- `factorCode`:因子编码,建议使用 F1、F2、F3 等格式
|
||||
- `factorName`:因子的中文名称
|
||||
- `factorOrder`:因子的排序顺序
|
||||
|
||||
---
|
||||
|
||||
### 5. 计分规则 (rules)
|
||||
|
||||
每个因子包含一个计分规则数组:
|
||||
|
||||
```json
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"itemNumber": 1, // 题目序号(必填,用于映射)
|
||||
"rule": {
|
||||
"optionIds": "1,2,3,4,5", // 选项ID列表,逗号分隔(可选,留空表示所有选项)
|
||||
"weight": 1.0, // 权重(必填,默认1.0)
|
||||
"calculationType": "sum" // 计算方式:sum-求和,avg-平均,max-最大,min-最小(必填)
|
||||
}
|
||||
},
|
||||
{
|
||||
"itemNumber": 4,
|
||||
"rule": {
|
||||
"optionIds": "",
|
||||
"weight": 1.0,
|
||||
"calculationType": "sum"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
- `itemNumber`:题目序号,用于将规则映射到对应的题目
|
||||
- `optionIds`:选项ID列表,逗号分隔。如果为空字符串,表示使用该题目的所有选项
|
||||
- `weight`:权重,用于加权计算(默认1.0)
|
||||
- `calculationType`:
|
||||
- `"sum"`:求和(所有选中选项的分数之和 × 权重)
|
||||
- `"avg"`:平均(所有选中选项的分数平均值 × 权重)
|
||||
- `"max"`:最大(所有选中选项的分数最大值 × 权重)
|
||||
- `"min"`:最小(所有选中选项的分数最小值 × 权重)
|
||||
|
||||
**注意**:`itemNumber` 必须与 `items` 数组中的题目序号对应。
|
||||
|
||||
---
|
||||
|
||||
### 6. 结果解释 (interpretations) - 可选
|
||||
|
||||
```json
|
||||
{
|
||||
"interpretations": [
|
||||
{
|
||||
"scaleId": null, // 量表ID(导入时自动设置,无需填写)
|
||||
"factorId": null, // 因子ID(导入时自动设置,可选)
|
||||
"scoreMin": 0, // 分数下限(必填)
|
||||
"scoreMax": 10, // 分数上限(必填)
|
||||
"level": "低", // 等级(可选)
|
||||
"levelName": "正常范围", // 等级名称(可选)
|
||||
"interpretationTitle": "正常范围", // 解释标题(必填)
|
||||
"interpretationContent": "您的得分在正常范围内", // 解释内容(可选)
|
||||
"suggestions": "继续保持良好的心理状态", // 建议指导(可选)
|
||||
"sortOrder": 1 // 排序顺序(可选)
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
- `scoreMin` 和 `scoreMax`:定义分数范围,系统会根据实际得分匹配对应的解释
|
||||
- `factorId`:如果留空(null),表示针对量表总体;如果填写,表示针对特定因子
|
||||
|
||||
---
|
||||
|
||||
### 7. 预警规则 (warningRules) - 可选
|
||||
|
||||
```json
|
||||
{
|
||||
"warningRules": [
|
||||
{
|
||||
"scaleId": null, // 量表ID(导入时自动设置,无需填写)
|
||||
"factorId": null, // 因子ID(导入时自动设置,可选)
|
||||
"ruleName": "重度抑郁预警", // 规则名称(必填)
|
||||
"warningLevel": "高", // 预警等级:低、中、高、紧急(必填)
|
||||
"scoreMin": 30, // 分数下限(可选)
|
||||
"scoreMax": 40, // 分数上限(可选)
|
||||
"percentileMin": 90, // 百分位下限(可选)
|
||||
"percentileMax": 100, // 百分位上限(可选)
|
||||
"autoResolve": "0", // 是否自动解除:0-否,1-是(可选)
|
||||
"resolveCondition": "", // 解除条件(可选)
|
||||
"status": "0" // 状态:0-正常,1-停用(必填)
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📄 完整JSON示例
|
||||
|
||||
### 示例1:简单的5题量表(无因子)
|
||||
|
||||
```json
|
||||
{
|
||||
"scale": {
|
||||
"scaleCode": "TEST_001",
|
||||
"scaleName": "测试量表",
|
||||
"scaleType": "general",
|
||||
"status": "0",
|
||||
"itemCount": 5
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"item": {
|
||||
"itemNumber": 1,
|
||||
"itemContent": "您是否经常感到焦虑?",
|
||||
"itemType": "single",
|
||||
"required": "1",
|
||||
"reverseScore": "0",
|
||||
"sortOrder": 1
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"optionCode": "A",
|
||||
"optionContent": "从不",
|
||||
"optionScore": 0,
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"optionCode": "B",
|
||||
"optionContent": "偶尔",
|
||||
"optionScore": 1,
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"optionCode": "C",
|
||||
"optionContent": "经常",
|
||||
"optionScore": 2,
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"optionCode": "D",
|
||||
"optionContent": "总是",
|
||||
"optionScore": 3,
|
||||
"sortOrder": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"item": {
|
||||
"itemNumber": 2,
|
||||
"itemContent": "您是否容易紧张?",
|
||||
"itemType": "single",
|
||||
"required": "1",
|
||||
"reverseScore": "0",
|
||||
"sortOrder": 2
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"optionCode": "A",
|
||||
"optionContent": "从不",
|
||||
"optionScore": 0,
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"optionCode": "B",
|
||||
"optionContent": "偶尔",
|
||||
"optionScore": 1,
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"optionCode": "C",
|
||||
"optionContent": "经常",
|
||||
"optionScore": 2,
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"optionCode": "D",
|
||||
"optionContent": "总是",
|
||||
"optionScore": 3,
|
||||
"sortOrder": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"factors": [],
|
||||
"interpretations": [
|
||||
{
|
||||
"scoreMin": 0,
|
||||
"scoreMax": 5,
|
||||
"level": "低",
|
||||
"levelName": "正常",
|
||||
"interpretationTitle": "正常范围",
|
||||
"interpretationContent": "您的焦虑水平在正常范围内"
|
||||
},
|
||||
{
|
||||
"scoreMin": 6,
|
||||
"scoreMax": 10,
|
||||
"level": "中",
|
||||
"levelName": "轻度",
|
||||
"interpretationTitle": "轻度焦虑",
|
||||
"interpretationContent": "您可能存在轻度焦虑,建议适当放松"
|
||||
},
|
||||
{
|
||||
"scoreMin": 11,
|
||||
"scoreMax": 15,
|
||||
"level": "高",
|
||||
"levelName": "中度",
|
||||
"interpretationTitle": "中度焦虑",
|
||||
"interpretationContent": "您可能存在中度焦虑,建议寻求专业帮助"
|
||||
}
|
||||
],
|
||||
"warningRules": []
|
||||
}
|
||||
```
|
||||
|
||||
### 示例2:SCL-90量表(含因子和计分规则)
|
||||
|
||||
```json
|
||||
{
|
||||
"scale": {
|
||||
"scaleCode": "SCL_90",
|
||||
"scaleName": "症状自评量表SCL-90",
|
||||
"scaleEnName": "Symptom Checklist 90",
|
||||
"scaleType": "symptom",
|
||||
"scaleVersion": "1.0",
|
||||
"scaleIntro": "SCL-90是一个包含90个项目的症状自评量表",
|
||||
"scaleDescription": "详细描述...",
|
||||
"itemCount": 90,
|
||||
"estimatedTime": 30,
|
||||
"targetPopulation": "一般人群",
|
||||
"author": "Derogatis",
|
||||
"source": "心理卫生评定量表手册",
|
||||
"status": "0"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"item": {
|
||||
"itemNumber": 1,
|
||||
"itemContent": "头痛",
|
||||
"itemType": "single",
|
||||
"required": "1",
|
||||
"reverseScore": "0",
|
||||
"sortOrder": 1
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"optionCode": "A",
|
||||
"optionContent": "没有",
|
||||
"optionScore": 0,
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"optionCode": "B",
|
||||
"optionContent": "很轻",
|
||||
"optionScore": 1,
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"optionCode": "C",
|
||||
"optionContent": "中等",
|
||||
"optionScore": 2,
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"optionCode": "D",
|
||||
"optionContent": "偏重",
|
||||
"optionScore": 3,
|
||||
"sortOrder": 4
|
||||
},
|
||||
{
|
||||
"optionCode": "E",
|
||||
"optionContent": "严重",
|
||||
"optionScore": 4,
|
||||
"sortOrder": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"item": {
|
||||
"itemNumber": 2,
|
||||
"itemContent": "神经过敏,心中不踏实",
|
||||
"itemType": "single",
|
||||
"required": "1",
|
||||
"reverseScore": "0",
|
||||
"sortOrder": 2
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"optionCode": "A",
|
||||
"optionContent": "没有",
|
||||
"optionScore": 0,
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"optionCode": "B",
|
||||
"optionContent": "很轻",
|
||||
"optionScore": 1,
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"optionCode": "C",
|
||||
"optionContent": "中等",
|
||||
"optionScore": 2,
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"optionCode": "D",
|
||||
"optionContent": "偏重",
|
||||
"optionScore": 3,
|
||||
"sortOrder": 4
|
||||
},
|
||||
{
|
||||
"optionCode": "E",
|
||||
"optionContent": "严重",
|
||||
"optionScore": 4,
|
||||
"sortOrder": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"factors": [
|
||||
{
|
||||
"factor": {
|
||||
"factorCode": "F1",
|
||||
"factorName": "躯体化",
|
||||
"factorDescription": "躯体化因子",
|
||||
"factorOrder": 1
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"itemNumber": 1,
|
||||
"rule": {
|
||||
"optionIds": "",
|
||||
"weight": 1.0,
|
||||
"calculationType": "sum"
|
||||
}
|
||||
},
|
||||
{
|
||||
"itemNumber": 4,
|
||||
"rule": {
|
||||
"optionIds": "",
|
||||
"weight": 1.0,
|
||||
"calculationType": "sum"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"factor": {
|
||||
"factorCode": "F5",
|
||||
"factorName": "焦虑",
|
||||
"factorDescription": "焦虑因子",
|
||||
"factorOrder": 5
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"itemNumber": 2,
|
||||
"rule": {
|
||||
"optionIds": "",
|
||||
"weight": 1.0,
|
||||
"calculationType": "sum"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"interpretations": [
|
||||
{
|
||||
"factorId": null,
|
||||
"scoreMin": 0,
|
||||
"scoreMax": 160,
|
||||
"level": "低",
|
||||
"levelName": "正常",
|
||||
"interpretationTitle": "正常范围",
|
||||
"interpretationContent": "您的总体得分在正常范围内"
|
||||
},
|
||||
{
|
||||
"factorId": null,
|
||||
"scoreMin": 161,
|
||||
"scoreMax": 250,
|
||||
"level": "中",
|
||||
"levelName": "轻度",
|
||||
"interpretationTitle": "轻度症状",
|
||||
"interpretationContent": "您可能存在轻度心理症状"
|
||||
}
|
||||
],
|
||||
"warningRules": [
|
||||
{
|
||||
"factorId": null,
|
||||
"ruleName": "重度症状预警",
|
||||
"warningLevel": "高",
|
||||
"scoreMin": 250,
|
||||
"scoreMax": 360,
|
||||
"status": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 使用步骤
|
||||
|
||||
### 方式1:JSON文本导入
|
||||
|
||||
1. 进入 `心理测评管理` → `量表管理`
|
||||
2. 点击 **"导入"** 按钮
|
||||
3. 在弹出对话框中,切换到 **"JSON文本"** 标签页
|
||||
4. 将JSON文本粘贴到文本框中
|
||||
5. 点击 **"确定"** 按钮导入
|
||||
|
||||
### 方式2:JSON文件导入
|
||||
|
||||
1. 将JSON数据保存为 `.json` 文件
|
||||
2. 进入 `心理测评管理` → `量表管理`
|
||||
3. 点击 **"导入"** 按钮
|
||||
4. 在弹出对话框中,切换到 **"文件上传"** 标签页
|
||||
5. 选择JSON文件并上传
|
||||
6. 点击 **"确定"** 按钮导入
|
||||
|
||||
### 方式3:PDF/DOCX文档导入(自动解析)
|
||||
|
||||
1. 准备PDF或DOCX格式的量表文档
|
||||
2. 进入 `心理测评管理` → `量表管理`
|
||||
3. 点击 **"导入"** 按钮
|
||||
4. 在弹出对话框中,切换到 **"文件上传"** 标签页
|
||||
5. 选择PDF/DOCX文件并上传
|
||||
6. 点击 **"预览解析结果"** 查看系统自动识别的结果
|
||||
7. 根据需要调整JSON数据
|
||||
8. 点击 **"确定"** 按钮导入
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 数据完整性
|
||||
- `scale` 对象必须包含 `scaleCode` 和 `scaleName`
|
||||
- 每个题目必须至少包含1个选项
|
||||
- 如果配置了因子,必须为每个因子配置至少1条计分规则
|
||||
|
||||
### 2. 数据唯一性
|
||||
- `scaleCode` 必须在系统中唯一
|
||||
- 同一量表内的 `factorCode` 必须唯一
|
||||
- 同一题目内的 `optionCode` 必须唯一
|
||||
|
||||
### 3. 数据映射
|
||||
- 计分规则中的 `itemNumber` 必须与 `items` 数组中的题目序号对应
|
||||
- 如果配置了 `interpretations` 或 `warningRules`,其中的 `factorId` 会在导入时自动映射
|
||||
|
||||
### 4. 字段类型
|
||||
- 数字字段(`itemNumber`、`optionScore`、`weight`等)使用数字类型,不要加引号
|
||||
- 字符串字段(`scaleCode`、`scaleName`等)使用双引号
|
||||
- 布尔值字段(`status`、`required`等)使用字符串 `"0"` 或 `"1"`
|
||||
|
||||
### 5. 可选字段
|
||||
- `interpretations` 和 `warningRules` 是可选的,可以省略或设置为空数组 `[]`
|
||||
- 如果量表没有因子,`factors` 可以设置为空数组 `[]`
|
||||
- 大部分字段都有默认值,可以省略
|
||||
|
||||
---
|
||||
|
||||
## 🔍 常见错误
|
||||
|
||||
### 错误1:JSON格式错误
|
||||
**错误信息**:`JSON格式错误:...`
|
||||
**解决方法**:
|
||||
- 检查JSON语法,确保所有括号、引号匹配
|
||||
- 使用JSON验证工具(如 jsonlint.com)验证JSON格式
|
||||
- 确保没有多余的逗号
|
||||
|
||||
### 错误2:量表编码已存在
|
||||
**错误信息**:`量表编码已存在:XXX`
|
||||
**解决方法**:
|
||||
- 修改 `scaleCode` 为唯一值
|
||||
- 或先删除系统中已存在的量表
|
||||
|
||||
### 错误3:题目序号映射失败
|
||||
**错误信息**:`找不到题目序号 X 对应的题目ID`
|
||||
**解决方法**:
|
||||
- 检查 `factors[].rules[].itemNumber` 是否与 `items[].item.itemNumber` 对应
|
||||
- 确保题目序号从1开始,连续编号
|
||||
|
||||
### 错误4:必填字段缺失
|
||||
**错误信息**:`XXX不能为空`
|
||||
**解决方法**:
|
||||
- 检查必填字段是否都已填写
|
||||
- 参考本文档的字段说明,补充缺失的字段
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [新量表导入完整操作指南](./15-新量表导入完整操作指南.md) - 手动操作步骤
|
||||
- [ScaleImportVO.java](../../ry-news-system/src/main/java/com/ddnai/system/domain/psychology/vo/ScaleImportVO.java) - 数据结构定义
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验证清单
|
||||
|
||||
导入前请检查:
|
||||
|
||||
- [ ] JSON格式正确(可以使用JSON验证工具验证)
|
||||
- [ ] `scaleCode` 唯一且符合命名规范
|
||||
- [ ] `scaleName` 已填写
|
||||
- [ ] 所有题目都有 `itemNumber` 和 `itemContent`
|
||||
- [ ] 所有题目都至少包含1个选项
|
||||
- [ ] 所有选项都有 `optionCode`、`optionContent` 和 `optionScore`
|
||||
- [ ] 如果有因子,所有因子都有 `factorCode` 和 `factorName`
|
||||
- [ ] 如果有因子,所有计分规则的 `itemNumber` 都能在题目列表中找到
|
||||
- [ ] `status` 字段设置为 `"0"`(正常)或 `"1"`(停用)
|
||||
|
||||
---
|
||||
|
||||
**最后更新**:2025-01-XX
|
||||
|
||||
73
z_Project change/量表示例/README.md
Normal file
73
z_Project change/量表示例/README.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# 量表示例文件说明
|
||||
|
||||
本文件夹包含用于测试量表导入功能的示例文件。
|
||||
|
||||
## 文件说明
|
||||
|
||||
1. **scale-import-example.json** - JSON格式导入标准示例(SCL-90量表示例,包含5个题目和因子配置)
|
||||
2. **SCL90症状自评量表(含常模).pdf** - SCL-90量表PDF文档(可用于PDF导入测试)
|
||||
3. **下载说明.md** - 量表下载来源说明
|
||||
|
||||
## 使用说明
|
||||
|
||||
### JSON格式导入(推荐)
|
||||
|
||||
1. **使用JSON示例文件**:
|
||||
- 打开 `scale-import-example.json` 查看标准JSON格式
|
||||
- 参考格式编写您自己的量表JSON数据
|
||||
- 在系统"量表导入"功能中选择"JSON文本"或"JSON文件"导入
|
||||
|
||||
2. **JSON格式说明**:
|
||||
- 详细格式说明请参考:`../16-量表导入JSON标准格式说明.md`
|
||||
- 该文档包含完整的字段说明、示例和注意事项
|
||||
|
||||
### PDF/DOCX文档导入
|
||||
|
||||
1. **获取完整的量表文档**(PDF或DOCX格式)
|
||||
- 可以从心理评估相关网站下载
|
||||
- 或者从心理测量工具包中获取
|
||||
|
||||
2. **推荐的量表下载来源**:
|
||||
- 优路教育:https://www.youlu.com/ziliao/detail/pack-52513
|
||||
- 心理卫生评定量表手册
|
||||
- 常用心理评估量表手册
|
||||
|
||||
3. **导入测试步骤**:
|
||||
- 将PDF或DOCX文件上传到系统的"量表导入"功能
|
||||
- 使用"预览解析结果"查看识别效果
|
||||
- 根据识别结果调整数据(可以切换到JSON文本模式手动调整)
|
||||
- 确认无误后导入系统
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 示例文件仅用于测试,不包含完整的量表内容
|
||||
2. 实际使用时应获取完整、正规的量表文档
|
||||
3. 请注意量表的版权和使用许可
|
||||
4. 建议使用格式规范的文档,以提高自动识别准确率
|
||||
|
||||
## 支持的文档格式
|
||||
|
||||
- **JSON(.json)** - 推荐方式,格式规范,导入准确
|
||||
- **PDF(.pdf)** - 自动解析,可能需手动调整
|
||||
- **Word 2007+(.docx)** - 自动解析,可能需手动调整
|
||||
- **Word 2003(.doc)** - 自动解析,可能需手动调整
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **推荐流程**:
|
||||
- 首先查看 `scale-import-example.json` 了解JSON格式
|
||||
- 参考 `../16-量表导入JSON标准格式说明.md` 了解详细规范
|
||||
- 使用JSON格式导入(最准确、最可靠)
|
||||
- 或使用PDF/DOCX导入后,在预览界面调整为JSON格式
|
||||
|
||||
2. **PDF/DOCX导入建议**:
|
||||
- 先预览解析结果,检查识别准确性
|
||||
- 如识别不准确,切换到JSON文本模式手动调整
|
||||
- 建议先预览再导入,确保数据准确性
|
||||
|
||||
3. **JSON格式导入优势**:
|
||||
- 格式规范,导入准确
|
||||
- 可以精确控制所有字段
|
||||
- 支持批量导入
|
||||
- 易于版本控制和备份
|
||||
|
||||
69
z_Project change/量表示例/SCL90导入说明.md
Normal file
69
z_Project change/量表示例/SCL90导入说明.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# SCL-90量表JSON导入说明
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本文档说明如何使用生成的`SCL90症状自评量表.json`文件导入完整的SCL-90量表数据。
|
||||
|
||||
## ✅ 自动映射支持
|
||||
|
||||
系统现已支持通过`factorCode`自动映射到`factorId`。**导入时会自动配置因子解释和预警规则**,无需手动配置!
|
||||
|
||||
## 📝 导入步骤
|
||||
|
||||
### 方式1:使用JSON文件导入(推荐)
|
||||
|
||||
1. 进入系统 → `心理测评管理` → `量表管理`
|
||||
2. 点击 **"导入"** 按钮
|
||||
3. 切换到 **"文件上传"** 标签页
|
||||
4. 选择 `SCL90症状自评量表.json` 文件
|
||||
5. 点击 **"确定"** 导入
|
||||
|
||||
### 方式2:使用JSON文本导入
|
||||
|
||||
1. 进入系统 → `心理测评管理` → `量表管理`
|
||||
2. 点击 **"导入"** 按钮
|
||||
3. 切换到 **"JSON文本"** 标签页
|
||||
4. 复制JSON文件内容并粘贴
|
||||
5. 点击 **"确定"** 导入
|
||||
|
||||
## 🎉 自动配置说明
|
||||
|
||||
系统会自动处理以下配置:
|
||||
|
||||
### 1. 自动配置因子解释
|
||||
|
||||
导入时会自动为每个因子创建4个级别的解释规则(正常、轻度、中度、重度),根据因子总分范围自动匹配。
|
||||
|
||||
### 2. 自动配置预警规则
|
||||
|
||||
导入时会自动创建以下预警规则:
|
||||
- **总体预警**:重度症状预警(总分250-360)和紧急症状预警(总分300-360)
|
||||
- **因子预警**:每个因子的重度预警(均分≥3.0)
|
||||
- **敏感因子预警**:F4(抑郁)、F5(焦虑)、F9(精神病性)的中度预警(均分2.0-2.9)
|
||||
|
||||
所有配置都会自动关联到对应的因子,无需手动配置!
|
||||
|
||||
## ✅ 验证清单
|
||||
|
||||
导入完成后,请检查:
|
||||
|
||||
- [ ] 量表基本信息正确(名称、编码、描述等)
|
||||
- [ ] 90个题目全部导入
|
||||
- [ ] 每个题目都有5个选项(0-4分)
|
||||
- [ ] 9个因子全部导入
|
||||
- [ ] 每个因子都有正确的计分规则
|
||||
- [ ] 因子解释规则已自动配置
|
||||
- [ ] 预警规则已自动配置
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [量表导入JSON标准格式说明](./16-量表导入JSON标准格式说明.md)
|
||||
- [JSON格式错误检查说明](./JSON格式错误检查说明.md)
|
||||
|
||||
## ✨ 功能特性
|
||||
|
||||
- ✅ 自动映射`factorCode`到`factorId`
|
||||
- ✅ 自动配置因子解释规则
|
||||
- ✅ 自动配置预警规则
|
||||
- ✅ 完整的错误处理和日志记录
|
||||
|
||||
BIN
z_Project change/量表示例/SCL90症状自评量表(含常模).pdf
Normal file
BIN
z_Project change/量表示例/SCL90症状自评量表(含常模).pdf
Normal file
Binary file not shown.
31
z_Project change/量表示例/convert-pdf-to-json.md
Normal file
31
z_Project change/量表示例/convert-pdf-to-json.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# PDF转JSON转换说明
|
||||
|
||||
由于无法直接在此环境中运行Java代码解析PDF,我将基于SCL-90标准量表结构创建一个完整的JSON文件。
|
||||
|
||||
## SCL-90标准结构
|
||||
|
||||
- **90个题目**
|
||||
- **9个因子**:F1躯体化、F2强迫、F3人际关系敏感、F4抑郁、F5焦虑、F6敌对、F7恐怖、F8偏执、F9精神病性
|
||||
- **5级评分**:0-4分(没有=0,很轻=1,中等=2,偏重=3,严重=4)
|
||||
|
||||
## 转换方法
|
||||
|
||||
系统已经实现了PDF解析功能,您可以通过以下方式转换:
|
||||
|
||||
### 方法1:使用系统导入功能(推荐)
|
||||
|
||||
1. 登录系统
|
||||
2. 进入 `心理测评管理` → `量表管理`
|
||||
3. 点击 **"导入"** 按钮
|
||||
4. 上传 `SCL90症状自评量表(含常模).pdf` 文件
|
||||
5. 点击 **"预览解析结果"** 查看系统自动解析的结果
|
||||
6. 切换到 **"JSON文本"** 标签页
|
||||
7. 复制JSON内容
|
||||
8. 保存到文件
|
||||
|
||||
### 方法2:手动创建JSON
|
||||
|
||||
根据SCL-90标准量表内容,手动创建完整的JSON文件。
|
||||
|
||||
我将为您创建一个基于SCL-90标准结构的完整JSON文件。
|
||||
|
||||
463
z_Project change/量表示例/create_scl90_json.py
Normal file
463
z_Project change/量表示例/create_scl90_json.py
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
生成SCL-90完整JSON文件的脚本
|
||||
包含90个题目、9个因子、计分规则、结果解释和预警规则
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
# SCL-90标准90个题目内容
|
||||
scl90_items = [
|
||||
"头痛", "神经过敏,心中不踏实", "头脑中有不必要的想法或字句盘旋", "头昏或昏倒", "对异性的兴趣减退",
|
||||
"对旁人责备求全", "感到别人能控制您的思想", "责怪别人制造麻烦", "忘记性大", "担心自己的衣饰整齐及仪态的端正",
|
||||
"容易烦恼和激动", "胸痛", "害怕空旷的场所或街道", "感到自己的精力下降,活动减慢", "想结束自己的生命",
|
||||
"听到旁人听不到的声音", "发抖", "感到大多数人都不可信任", "胃口不好", "容易哭泣",
|
||||
"同异性相处时感到害羞不自在", "感到受骗、中了圈套或有人想抓住您", "无缘无故地突然感到害怕", "自己不能控制地大发脾气", "怕单独出门",
|
||||
"经常责怪自己", "腰痛", "感到难以完成任务", "感到孤独", "感到苦闷",
|
||||
"过分担忧", "对事物不感兴趣", "感到害怕", "您的感情容易受到伤害", "感到旁人能知道您的私下想法",
|
||||
"感到别人不理解您、不同情您", "感到人们对您不友好、不喜欢您", "做事必须做得很慢以保证做得正确", "心跳得很厉害", "恶心或胃部不舒服",
|
||||
"感到比不上他人", "肌肉酸痛", "感到有人在监视您、谈论您", "难以入睡", "做事必须反复检查",
|
||||
"难以做出决定", "怕乘电车、公共汽车、地铁或火车", "呼吸有困难", "一阵阵发冷或发热", "因为感到害怕而避开某些东西、场合或活动",
|
||||
"脑子变空了", "身体发麻或刺痛", "喉咙有梗塞感", "感到前途没有希望", "不能集中注意",
|
||||
"感到身体的某一部分软弱无力", "感到紧张或容易紧张", "感到手或脚发重", "想到死亡的事", "吃得太多",
|
||||
"当别人看着您或谈论您时感到不自在", "有一些不属于您自己的想法", "有想打人或伤害他人的冲动", "醒得太早", "必须反复洗手、点数或触摸某些东西",
|
||||
"睡得不稳不深", "有想摔坏或破坏东西的冲动", "有一些别人没有的想法或念头", "感到对别人神经过敏", "在商店或电影院等人多的地方感到不自在",
|
||||
"感到任何事情都很困难", "一阵阵恐惧或惊恐", "感到在公共场合吃东西很不舒服", "经常与人争论", "单独一人时神经很紧张",
|
||||
"别人对您的成绩没有做出恰当的评价", "即使和别人在一起也感到孤单", "感到坐立不安心神不定", "感到自己没有什么价值", "感到熟悉的东西变成陌生或不像是真的",
|
||||
"大叫或摔东西", "害怕会在公共场合昏倒", "感到别人想占您的便宜", "为一些有关\"性\"的想法而很苦恼", "您认为应该因为自己的过错而受到惩罚",
|
||||
"感到要很快把事情做完", "感到自己的身体有严重问题", "从未感到和其他人很亲近", "感到自己有罪", "感到自己的脑子有毛病"
|
||||
]
|
||||
|
||||
# SCL-90因子定义(因子代码、名称、包含的题目编号)
|
||||
factors_data = [
|
||||
{"code": "F1", "name": "躯体化", "items": [1,4,12,27,40,42,48,49,52,53,56,58]},
|
||||
{"code": "F2", "name": "强迫症状", "items": [3,9,10,28,38,45,46,51,55,65]},
|
||||
{"code": "F3", "name": "人际关系敏感", "items": [6,21,34,36,37,41,61,69,73]},
|
||||
{"code": "F4", "name": "抑郁", "items": [5,14,15,20,22,26,29,30,31,32,54,71,79]},
|
||||
{"code": "F5", "name": "焦虑", "items": [2,17,23,33,39,57,72,78,80,86]},
|
||||
{"code": "F6", "name": "敌对", "items": [11,24,63,67,74,81]},
|
||||
{"code": "F7", "name": "恐怖", "items": [13,25,47,50,70,75,82]},
|
||||
{"code": "F8", "name": "偏执", "items": [8,18,43,68,76,83]},
|
||||
{"code": "F9", "name": "精神病性", "items": [7,16,35,62,77,84,85,87,88,90]}
|
||||
]
|
||||
|
||||
# 因子英文名称映射
|
||||
factor_en_names = {
|
||||
"F1": "Somatization",
|
||||
"F2": "Obsessive-Compulsive",
|
||||
"F3": "Interpersonal Sensitivity",
|
||||
"F4": "Depression",
|
||||
"F5": "Anxiety",
|
||||
"F6": "Hostility",
|
||||
"F7": "Phobic Anxiety",
|
||||
"F8": "Paranoid Ideation",
|
||||
"F9": "Psychoticism"
|
||||
}
|
||||
|
||||
# 生成量表基本信息
|
||||
scale_info = {
|
||||
"scaleCode": "SCL_90",
|
||||
"scaleName": "症状自评量表SCL-90",
|
||||
"scaleEnName": "Symptom Checklist 90",
|
||||
"scaleType": "symptom",
|
||||
"scaleVersion": "1.0",
|
||||
"scaleIntro": "SCL-90是一个包含90个项目的症状自评量表,用于评估个体的心理症状水平",
|
||||
"scaleDescription": "SCL-90量表包含90个项目,涵盖9个症状因子:躯体化、强迫症状、人际关系敏感、抑郁、焦虑、敌对、恐怖、偏执、精神病性。采用5级评分(0-4分),是心理健康评估的重要工具。",
|
||||
"itemCount": 90,
|
||||
"estimatedTime": 30,
|
||||
"targetPopulation": "一般人群",
|
||||
"author": "Derogatis",
|
||||
"source": "心理卫生评定量表手册",
|
||||
"reference": "Derogatis, L. R. (1977). SCL-90: Administration, scoring, and procedures manual. Clinical Psychometric Research.",
|
||||
"status": "0",
|
||||
"sortOrder": 0
|
||||
}
|
||||
|
||||
# 生成题目列表(包含选项)
|
||||
items_list = []
|
||||
for i, content in enumerate(scl90_items, 1):
|
||||
item_data = {
|
||||
"item": {
|
||||
"itemNumber": i,
|
||||
"itemContent": content,
|
||||
"itemType": "single",
|
||||
"required": "1",
|
||||
"reverseScore": "0",
|
||||
"sortOrder": i
|
||||
},
|
||||
"options": [
|
||||
{"optionCode": "A", "optionContent": "没有", "optionScore": 0, "sortOrder": 1},
|
||||
{"optionCode": "B", "optionContent": "很轻", "optionScore": 1, "sortOrder": 2},
|
||||
{"optionCode": "C", "optionContent": "中等", "optionScore": 2, "sortOrder": 3},
|
||||
{"optionCode": "D", "optionContent": "偏重", "optionScore": 3, "sortOrder": 4},
|
||||
{"optionCode": "E", "optionContent": "严重", "optionScore": 4, "sortOrder": 5}
|
||||
]
|
||||
}
|
||||
items_list.append(item_data)
|
||||
|
||||
# 生成因子列表(包含计分规则)
|
||||
factors_list = []
|
||||
for idx, factor_data in enumerate(factors_data, 1):
|
||||
factor_obj = {
|
||||
"factor": {
|
||||
"factorCode": factor_data["code"],
|
||||
"factorName": factor_data["name"],
|
||||
"factorEnName": factor_en_names.get(factor_data["code"], ""),
|
||||
"factorDescription": "SCL-90 {}因子,包含{}个题目".format(factor_data["name"], len(factor_data["items"])),
|
||||
"factorOrder": idx
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"itemNumber": item_num,
|
||||
"rule": {
|
||||
"optionIds": "",
|
||||
"weight": 1.0,
|
||||
"calculationType": "sum"
|
||||
}
|
||||
}
|
||||
for item_num in factor_data["items"]
|
||||
]
|
||||
}
|
||||
factors_list.append(factor_obj)
|
||||
|
||||
# SCL-90因子常模和解释规则
|
||||
# 因子均分标准:<1.0正常,1.0-1.9轻度,2.0-2.9中度,≥3.0重度
|
||||
# 因子总分 = 因子均分 × 题目数量
|
||||
|
||||
# 因子题目数量和总分范围
|
||||
factor_ranges = {
|
||||
"F1": {"itemCount": 12, "maxScore": 48}, # 躯体化
|
||||
"F2": {"itemCount": 10, "maxScore": 40}, # 强迫症状
|
||||
"F3": {"itemCount": 9, "maxScore": 36}, # 人际关系敏感
|
||||
"F4": {"itemCount": 13, "maxScore": 52}, # 抑郁
|
||||
"F5": {"itemCount": 10, "maxScore": 40}, # 焦虑
|
||||
"F6": {"itemCount": 6, "maxScore": 24}, # 敌对
|
||||
"F7": {"itemCount": 7, "maxScore": 28}, # 恐怖
|
||||
"F8": {"itemCount": 6, "maxScore": 24}, # 偏执
|
||||
"F9": {"itemCount": 10, "maxScore": 40} # 精神病性
|
||||
}
|
||||
|
||||
# 生成总体解释配置(总分范围0-360)
|
||||
interpretations_list = [
|
||||
{
|
||||
"factorId": None,
|
||||
"scoreRangeMin": 0,
|
||||
"scoreRangeMax": 160,
|
||||
"level": "低",
|
||||
"levelName": "正常",
|
||||
"interpretationTitle": "正常范围",
|
||||
"interpretationContent": "您的总体得分在正常范围内,心理健康状况良好。",
|
||||
"suggestions": "继续保持良好的心理状态,注意日常生活中的压力管理。",
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"factorId": None,
|
||||
"scoreRangeMin": 161,
|
||||
"scoreRangeMax": 250,
|
||||
"level": "中",
|
||||
"levelName": "轻度",
|
||||
"interpretationTitle": "轻度症状",
|
||||
"interpretationContent": "您可能存在轻度的心理症状,建议适当关注自己的心理健康。",
|
||||
"suggestions": "建议通过放松训练、运动、社交等方式缓解压力,如症状持续可考虑咨询专业人士。",
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"factorId": None,
|
||||
"scoreRangeMin": 251,
|
||||
"scoreRangeMax": 360,
|
||||
"level": "高",
|
||||
"levelName": "中重度",
|
||||
"interpretationTitle": "中重度症状",
|
||||
"interpretationContent": "您的得分显示可能存在中重度的心理症状,建议寻求专业心理帮助。",
|
||||
"suggestions": "强烈建议咨询心理医生或心理治疗师,进行专业的心理评估和治疗。",
|
||||
"sortOrder": 3
|
||||
}
|
||||
]
|
||||
|
||||
# 为每个因子生成解释配置(基于因子总分)
|
||||
# 因子均分 < 1.0 为正常,1.0-1.9 为轻度,2.0-2.9 为中度,≥ 3.0 为重度
|
||||
factor_interpretations = {
|
||||
"F1": {
|
||||
"name": "躯体化",
|
||||
"descriptions": {
|
||||
"正常": "您的躯体化因子得分在正常范围内,躯体不适感较轻。",
|
||||
"轻度": "您可能存在轻度的躯体化症状,建议关注身体健康状况。",
|
||||
"中度": "您存在中度的躯体化症状,建议进行身体检查并关注心理健康。",
|
||||
"重度": "您存在重度的躯体化症状,强烈建议寻求医疗和心理双重帮助。"
|
||||
},
|
||||
"suggestions": {
|
||||
"正常": "继续保持良好的生活习惯,定期体检。",
|
||||
"轻度": "建议关注身体健康,适当进行体检,同时注意心理压力的管理。",
|
||||
"中度": "建议进行全面的身体检查,排除器质性病变,并寻求心理帮助。",
|
||||
"重度": "强烈建议尽快就医,进行全面的身体和心理评估,制定综合治疗方案。"
|
||||
}
|
||||
},
|
||||
"F2": {
|
||||
"name": "强迫症状",
|
||||
"descriptions": {
|
||||
"正常": "您的强迫症状因子得分在正常范围内,强迫思维和行为较少。",
|
||||
"轻度": "您可能存在轻度的强迫症状,偶尔出现不必要的想法或行为。",
|
||||
"中度": "您存在中度的强迫症状,可能影响日常生活和工作效率。",
|
||||
"重度": "您存在重度的强迫症状,严重影响日常生活,需要专业治疗。"
|
||||
},
|
||||
"suggestions": {
|
||||
"正常": "继续保持良好的心理状态。",
|
||||
"轻度": "建议学习放松技巧,减少不必要的担心和重复行为。",
|
||||
"中度": "建议寻求心理治疗,学习认知行为疗法等方法来缓解强迫症状。",
|
||||
"重度": "强烈建议寻求专业心理治疗,可能需要药物治疗配合心理治疗。"
|
||||
}
|
||||
},
|
||||
"F3": {
|
||||
"name": "人际关系敏感",
|
||||
"descriptions": {
|
||||
"正常": "您的人际关系敏感因子得分在正常范围内,人际交往较为自然。",
|
||||
"轻度": "您可能在人际交往中偶尔感到不适或敏感。",
|
||||
"中度": "您存在中度的人际关系敏感,可能影响社交能力和人际关系。",
|
||||
"重度": "您存在重度的人际关系敏感,严重影响社交和人际关系,需要帮助。"
|
||||
},
|
||||
"suggestions": {
|
||||
"正常": "继续保持良好的人际交往。",
|
||||
"轻度": "建议增加社交活动,培养自信,学习沟通技巧。",
|
||||
"中度": "建议寻求心理咨询,学习改善人际关系的方法和技巧。",
|
||||
"重度": "强烈建议寻求专业心理治疗,改善社交恐惧和人际关系问题。"
|
||||
}
|
||||
},
|
||||
"F4": {
|
||||
"name": "抑郁",
|
||||
"descriptions": {
|
||||
"正常": "您的抑郁因子得分在正常范围内,情绪状态良好。",
|
||||
"轻度": "您可能存在轻度的抑郁情绪,偶尔感到沮丧或失落。",
|
||||
"中度": "您存在中度的抑郁症状,可能影响日常生活和工作。",
|
||||
"重度": "您存在重度的抑郁症状,严重影响生活功能,需要立即寻求帮助。"
|
||||
},
|
||||
"suggestions": {
|
||||
"正常": "继续保持良好的情绪状态。",
|
||||
"轻度": "建议增加运动,培养兴趣爱好,保持规律的作息。",
|
||||
"中度": "建议寻求心理咨询或心理治疗,必要时考虑药物治疗。",
|
||||
"重度": "强烈建议立即寻求专业帮助,可能需要药物治疗和心理治疗相结合。"
|
||||
}
|
||||
},
|
||||
"F5": {
|
||||
"name": "焦虑",
|
||||
"descriptions": {
|
||||
"正常": "您的焦虑因子得分在正常范围内,焦虑水平较低。",
|
||||
"轻度": "您可能存在轻度的焦虑情绪,偶尔感到紧张或担心。",
|
||||
"中度": "您存在中度的焦虑症状,可能影响日常生活和工作。",
|
||||
"重度": "您存在重度的焦虑症状,严重影响生活功能,需要专业治疗。"
|
||||
},
|
||||
"suggestions": {
|
||||
"正常": "继续保持良好的心理状态。",
|
||||
"轻度": "建议学习放松技巧,进行深呼吸和冥想练习。",
|
||||
"中度": "建议寻求心理咨询,学习焦虑管理技巧,必要时考虑药物治疗。",
|
||||
"重度": "强烈建议寻求专业治疗,可能需要药物治疗配合心理治疗。"
|
||||
}
|
||||
},
|
||||
"F6": {
|
||||
"name": "敌对",
|
||||
"descriptions": {
|
||||
"正常": "您的敌对因子得分在正常范围内,情绪控制良好。",
|
||||
"轻度": "您可能存在轻度的敌对情绪,偶尔感到愤怒或烦躁。",
|
||||
"中度": "您存在中度的敌对情绪,可能影响人际关系。",
|
||||
"重度": "您存在重度的敌对情绪,严重影响人际关系和社会功能。"
|
||||
},
|
||||
"suggestions": {
|
||||
"正常": "继续保持良好的情绪管理。",
|
||||
"轻度": "建议学习情绪管理技巧,进行适当的运动来释放压力。",
|
||||
"中度": "建议寻求心理咨询,学习愤怒管理和冲突解决技巧。",
|
||||
"重度": "强烈建议寻求专业心理治疗,改善情绪控制和人际交往能力。"
|
||||
}
|
||||
},
|
||||
"F7": {
|
||||
"name": "恐怖",
|
||||
"descriptions": {
|
||||
"正常": "您的恐怖因子得分在正常范围内,恐惧情绪较少。",
|
||||
"轻度": "您可能存在轻度的恐怖情绪,对某些情境感到轻微不安。",
|
||||
"中度": "您存在中度的恐怖情绪,可能影响正常生活和工作。",
|
||||
"重度": "您存在重度的恐怖情绪,严重影响正常生活,需要专业治疗。"
|
||||
},
|
||||
"suggestions": {
|
||||
"正常": "继续保持良好的心理状态。",
|
||||
"轻度": "建议逐步面对恐惧,进行脱敏训练。",
|
||||
"中度": "建议寻求心理咨询,进行系统脱敏治疗。",
|
||||
"重度": "强烈建议寻求专业心理治疗,可能需要暴露疗法和药物治疗。"
|
||||
}
|
||||
},
|
||||
"F8": {
|
||||
"name": "偏执",
|
||||
"descriptions": {
|
||||
"正常": "您的偏执因子得分在正常范围内,信任感良好。",
|
||||
"轻度": "您可能存在轻度的偏执倾向,偶尔对他人产生怀疑。",
|
||||
"中度": "您存在中度的偏执倾向,可能影响人际关系和信任。",
|
||||
"重度": "您存在重度的偏执倾向,严重影响人际关系和社会功能。"
|
||||
},
|
||||
"suggestions": {
|
||||
"正常": "继续保持良好的人际信任。",
|
||||
"轻度": "建议增强自信,学习信任他人,改善人际关系。",
|
||||
"中度": "建议寻求心理咨询,改善偏执思维,学习正确的认知方式。",
|
||||
"重度": "强烈建议寻求专业心理治疗,可能需要认知行为疗法和药物治疗。"
|
||||
}
|
||||
},
|
||||
"F9": {
|
||||
"name": "精神病性",
|
||||
"descriptions": {
|
||||
"正常": "您的精神病性因子得分在正常范围内,思维清晰。",
|
||||
"轻度": "您可能存在轻微的精神病性症状,偶尔出现异常想法。",
|
||||
"中度": "您存在中度的精神病性症状,可能影响思维和判断。",
|
||||
"重度": "您存在重度的精神病性症状,严重影响思维功能,需要立即就医。"
|
||||
},
|
||||
"suggestions": {
|
||||
"正常": "继续保持良好的心理状态。",
|
||||
"轻度": "建议关注心理健康,如有持续异常思维请及时咨询。",
|
||||
"中度": "强烈建议寻求精神科医生的专业评估和治疗。",
|
||||
"重度": "紧急建议立即寻求精神科专业治疗,可能需要住院治疗。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 为每个因子添加解释配置
|
||||
for factor_code, factor_info in factor_interpretations.items():
|
||||
if factor_code in factor_ranges:
|
||||
item_count = factor_ranges[factor_code]["itemCount"]
|
||||
max_score = factor_ranges[factor_code]["maxScore"]
|
||||
|
||||
# 正常:均分 < 1.0,总分 < 题目数
|
||||
interpretations_list.append({
|
||||
"factorId": None, # 导入时会通过factorCode映射设置
|
||||
"factorCode": factor_code, # 用于映射到factorId的临时字段
|
||||
"scoreRangeMin": 0,
|
||||
"scoreRangeMax": item_count - 1,
|
||||
"level": "低",
|
||||
"levelName": "正常",
|
||||
"interpretationTitle": "{}因子正常".format(factor_info["name"]),
|
||||
"interpretationContent": factor_info["descriptions"]["正常"],
|
||||
"suggestions": factor_info["suggestions"]["正常"],
|
||||
"sortOrder": 1
|
||||
})
|
||||
|
||||
# 轻度:均分 1.0-1.9,总分 = 题目数 × 1.0 到 题目数 × 1.9
|
||||
min_light = item_count * 1.0
|
||||
max_light = int(item_count * 1.9)
|
||||
interpretations_list.append({
|
||||
"factorId": None,
|
||||
"factorCode": factor_code,
|
||||
"scoreRangeMin": int(min_light),
|
||||
"scoreRangeMax": max_light,
|
||||
"level": "中",
|
||||
"levelName": "轻度",
|
||||
"interpretationTitle": "{}因子轻度".format(factor_info["name"]),
|
||||
"interpretationContent": factor_info["descriptions"]["轻度"],
|
||||
"suggestions": factor_info["suggestions"]["轻度"],
|
||||
"sortOrder": 2
|
||||
})
|
||||
|
||||
# 中度:均分 2.0-2.9,总分 = 题目数 × 2.0 到 题目数 × 2.9
|
||||
min_moderate = item_count * 2.0
|
||||
max_moderate = int(item_count * 2.9)
|
||||
interpretations_list.append({
|
||||
"factorId": None,
|
||||
"factorCode": factor_code,
|
||||
"scoreRangeMin": int(min_moderate),
|
||||
"scoreRangeMax": max_moderate,
|
||||
"level": "高",
|
||||
"levelName": "中度",
|
||||
"interpretationTitle": "{}因子中度".format(factor_info["name"]),
|
||||
"interpretationContent": factor_info["descriptions"]["中度"],
|
||||
"suggestions": factor_info["suggestions"]["中度"],
|
||||
"sortOrder": 3
|
||||
})
|
||||
|
||||
# 重度:均分 ≥ 3.0,总分 ≥ 题目数 × 3.0
|
||||
min_severe = int(item_count * 3.0)
|
||||
interpretations_list.append({
|
||||
"factorId": None,
|
||||
"factorCode": factor_code,
|
||||
"scoreRangeMin": min_severe,
|
||||
"scoreRangeMax": max_score,
|
||||
"level": "高",
|
||||
"levelName": "重度",
|
||||
"interpretationTitle": "{}因子重度".format(factor_info["name"]),
|
||||
"interpretationContent": factor_info["descriptions"]["重度"],
|
||||
"suggestions": factor_info["suggestions"]["重度"],
|
||||
"sortOrder": 4
|
||||
})
|
||||
|
||||
# 生成预警规则
|
||||
warning_rules_list = [
|
||||
# 总体预警规则
|
||||
{
|
||||
"factorId": None,
|
||||
"ruleName": "重度症状预警",
|
||||
"warningLevel": "高",
|
||||
"scoreMin": 250,
|
||||
"scoreMax": 360,
|
||||
"autoRelief": "0",
|
||||
"status": "0"
|
||||
},
|
||||
{
|
||||
"factorId": None,
|
||||
"ruleName": "紧急症状预警",
|
||||
"warningLevel": "紧急",
|
||||
"scoreMin": 300,
|
||||
"scoreMax": 360,
|
||||
"autoRelief": "0",
|
||||
"status": "0"
|
||||
}
|
||||
]
|
||||
|
||||
# 为每个因子添加预警规则(重度及以上需要预警)
|
||||
for factor_code, factor_info in factor_interpretations.items():
|
||||
if factor_code in factor_ranges:
|
||||
item_count = factor_ranges[factor_code]["itemCount"]
|
||||
max_score = factor_ranges[factor_code]["maxScore"]
|
||||
|
||||
# 重度预警(均分 ≥ 3.0)
|
||||
min_severe = int(item_count * 3.0)
|
||||
warning_rules_list.append({
|
||||
"factorId": None, # 导入时会通过factorCode映射设置
|
||||
"factorCode": factor_code, # 用于映射到factorId的临时字段
|
||||
"ruleName": "{}因子重度预警".format(factor_info["name"]),
|
||||
"warningLevel": "高",
|
||||
"scoreMin": min_severe,
|
||||
"scoreMax": max_score,
|
||||
"autoRelief": "0",
|
||||
"status": "0"
|
||||
})
|
||||
|
||||
# 中度预警(均分 2.0-2.9)- 对于某些敏感因子
|
||||
if factor_code in ["F4", "F5", "F9"]: # 抑郁、焦虑、精神病性需要中度预警
|
||||
min_moderate = int(item_count * 2.0)
|
||||
max_moderate = int(item_count * 2.9)
|
||||
warning_rules_list.append({
|
||||
"factorId": None,
|
||||
"factorCode": factor_code,
|
||||
"ruleName": "{}因子中度预警".format(factor_info["name"]),
|
||||
"warningLevel": "中",
|
||||
"scoreMin": min_moderate,
|
||||
"scoreMax": max_moderate,
|
||||
"autoRelief": "0",
|
||||
"status": "0"
|
||||
})
|
||||
|
||||
# 组合完整的JSON对象
|
||||
json_data = {
|
||||
"scale": scale_info,
|
||||
"items": items_list,
|
||||
"factors": factors_list,
|
||||
"interpretations": interpretations_list,
|
||||
"warningRules": warning_rules_list
|
||||
}
|
||||
|
||||
# 输出JSON文件
|
||||
output_file = "SCL90症状自评量表.json"
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(json_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print("✅ SCL-90 JSON文件已生成: {}".format(output_file))
|
||||
print("📊 统计信息:")
|
||||
print(" - 量表: {}".format(scale_info["scaleName"]))
|
||||
print(" - 题目数量: {}".format(len(items_list)))
|
||||
print(" - 因子数量: {}".format(len(factors_list)))
|
||||
print(" - 结果解释: {}".format(len(interpretations_list)))
|
||||
print(" - 预警规则: {}".format(len(warning_rules_list)))
|
||||
|
||||
124
z_Project change/量表示例/generate_scl90_json.js
Normal file
124
z_Project change/量表示例/generate_scl90_json.js
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// 生成SCL-90完整JSON文件的脚本
|
||||
// SCL-90标准90个题目内容
|
||||
|
||||
const scl90Items = [
|
||||
"头痛", "神经过敏,心中不踏实", "头脑中有不必要的想法或字句盘旋", "头昏或昏倒", "对异性的兴趣减退",
|
||||
"对旁人责备求全", "感到别人能控制您的思想", "责怪别人制造麻烦", "忘记性大", "担心自己的衣饰整齐及仪态的端正",
|
||||
"容易烦恼和激动", "胸痛", "害怕空旷的场所或街道", "感到自己的精力下降,活动减慢", "想结束自己的生命",
|
||||
"听到旁人听不到的声音", "发抖", "感到大多数人都不可信任", "胃口不好", "容易哭泣",
|
||||
"同异性相处时感到害羞不自在", "感到受骗、中了圈套或有人想抓住您", "无缘无故地 suddenly 感到害怕", "自己不能控制地大发脾气", "怕单独出门",
|
||||
"经常责怪自己", "腰痛", "感到难以完成任务", "感到孤独", "感到苦闷",
|
||||
"过分担忧", "对事物不感兴趣", "感到害怕", "您的感情容易受到伤害", "感到旁人能知道您的私下想法",
|
||||
"感到别人不理解您、不同情您", "感到人们对您不友好、不喜欢您", "做事必须做得很慢以保证做得正确", "心跳得很厉害", "恶心或胃部不舒服",
|
||||
"感到比不上他人", "肌肉酸痛", "感到有人在监视您、谈论您", "难以入睡", "做事必须反复检查",
|
||||
"难以做出决定", "怕乘电车、公共汽车、地铁或火车", "呼吸有困难", "一阵阵发冷或发热", "因为感到害怕而避开某些东西、场合或活动",
|
||||
"脑子变空了", "身体发麻或刺痛", "喉咙有梗塞感", "感到前途没有希望", "不能集中注意",
|
||||
"感到身体的某一部分软弱无力", "感到紧张或容易紧张", "感到手或脚发重", "想到死亡的事", "吃得太多",
|
||||
"当别人看着您或谈论您时感到不自在", "有一些不属于您自己的想法", "有想打人或伤害他人的冲动", "醒得太早", "必须反复洗手、点数或触摸某些东西",
|
||||
"睡得不稳不深", "有想摔坏或破坏东西的冲动", "有一些别人没有的想法或念头", "感到对别人神经过敏", "在商店或电影院等人多的地方感到不自在",
|
||||
"感到任何事情都很困难", "一阵阵恐惧或惊恐", "感到在公共场合吃东西很不舒服", "经常与人争论", "单独一人时神经很紧张",
|
||||
"别人对您的成绩没有做出恰当的评价", "即使和别人在一起也感到孤单", "感到坐立不安心神不定", "感到自己没有什么价值", "感到熟悉的东西变成陌生或不像是真的",
|
||||
"大叫或摔东西", "害怕会在公共场合昏倒", "感到别人想占您的便宜", "为一些有关"性"的想法而很苦恼", "您认为应该因为自己的过错而受到惩罚",
|
||||
"感到要很快把事情做完", "感到自己的身体有严重问题", "从未感到和其他人很亲近", "感到自己有罪", "感到自己的脑子有毛病"
|
||||
];
|
||||
|
||||
// SCL-90因子定义
|
||||
const factors = [
|
||||
{ code: "F1", name: "躯体化", items: [1,4,12,27,40,42,48,49,52,53,56,58] },
|
||||
{ code: "F2", name: "强迫症状", items: [3,9,10,28,38,45,46,51,55,65] },
|
||||
{ code: "F3", name: "人际关系敏感", items: [6,21,34,36,37,41,61,69,73] },
|
||||
{ code: "F4", name: "抑郁", items: [5,14,15,20,22,26,29,30,31,32,54,71,79] },
|
||||
{ code: "F5", name: "焦虑", items: [2,17,23,33,39,57,72,78,80,86] },
|
||||
{ code: "F6", name: "敌对", items: [11,24,63,67,74,81] },
|
||||
{ code: "F7", name: "恐怖", items: [13,25,47,50,70,75,82] },
|
||||
{ code: "F8", name: "偏执", items: [8,18,43,68,76,83] },
|
||||
{ code: "F9", name: "精神病性", items: [7,16,35,62,77,84,85,87,88,90] }
|
||||
];
|
||||
|
||||
// 生成JSON
|
||||
const json = {
|
||||
scale: {
|
||||
scaleCode: "SCL_90",
|
||||
scaleName: "症状自评量表SCL-90",
|
||||
scaleEnName: "Symptom Checklist 90",
|
||||
scaleType: "symptom",
|
||||
scaleVersion: "1.0",
|
||||
scaleIntro: "SCL-90是一个包含90个项目的症状自评量表,用于评估个体的心理症状水平",
|
||||
scaleDescription: "SCL-90量表包含90个项目,涵盖9个症状因子:躯体化、强迫症状、人际关系敏感、抑郁、焦虑、敌对、恐怖、偏执、精神病性。采用5级评分(0-4分),是心理健康评估的重要工具。",
|
||||
itemCount: 90,
|
||||
estimatedTime: 30,
|
||||
targetPopulation: "一般人群",
|
||||
author: "Derogatis",
|
||||
source: "心理卫生评定量表手册",
|
||||
reference: "Derogatis, L. R. (1977). SCL-90: Administration, scoring, and procedures manual. Clinical Psychometric Research.",
|
||||
status: "0",
|
||||
sortOrder: 0
|
||||
},
|
||||
items: scl90Items.map((content, index) => ({
|
||||
item: {
|
||||
itemNumber: index + 1,
|
||||
itemContent: content,
|
||||
itemType: "single",
|
||||
required: "1",
|
||||
reverseScore: "0",
|
||||
sortOrder: index + 1
|
||||
},
|
||||
options: [
|
||||
{ optionCode: "A", optionContent: "没有", optionScore: 0, sortOrder: 1 },
|
||||
{ optionCode: "B", optionContent: "很轻", optionScore: 1, sortOrder: 2 },
|
||||
{ optionCode: "C", optionContent: "中等", optionScore: 2, sortOrder: 3 },
|
||||
{ optionCode: "D", optionContent: "偏重", optionScore: 3, sortOrder: 4 },
|
||||
{ optionCode: "E", optionContent: "严重", optionScore: 4, sortOrder: 5 }
|
||||
]
|
||||
})),
|
||||
factors: factors.map((factor, index) => ({
|
||||
factor: {
|
||||
factorCode: factor.code,
|
||||
factorName: factor.name,
|
||||
factorEnName: getFactorEnName(factor.code),
|
||||
factorDescription: `SCL-90 ${factor.name}因子,包含${factor.items.length}个题目`,
|
||||
factorOrder: index + 1
|
||||
},
|
||||
rules: factor.items.map(itemNum => ({
|
||||
itemNumber: itemNum,
|
||||
rule: {
|
||||
optionIds: "",
|
||||
weight: 1.0,
|
||||
calculationType: "sum"
|
||||
}
|
||||
}))
|
||||
})),
|
||||
interpretations: [
|
||||
// 总体解释
|
||||
{ factorId: null, scoreRangeMin: 0, scoreRangeMax: 160, level: "低", levelName: "正常", interpretationTitle: "正常范围", interpretationContent: "您的总体得分在正常范围内,心理健康状况良好。", suggestions: "继续保持良好的心理状态,注意日常生活中的压力管理。", sortOrder: 1 },
|
||||
{ factorId: null, scoreRangeMin: 161, scoreRangeMax: 250, level: "中", levelName: "轻度", interpretationTitle: "轻度症状", interpretationContent: "您可能存在轻度的心理症状,建议适当关注自己的心理健康。", suggestions: "建议通过放松训练、运动、社交等方式缓解压力,如症状持续可考虑咨询专业人士。", sortOrder: 2 },
|
||||
{ factorId: null, scoreRangeMin: 251, scoreRangeMax: 360, level: "高", levelName: "中重度", interpretationTitle: "中重度症状", interpretationContent: "您的得分显示可能存在中重度的心理症状,建议寻求专业心理帮助。", suggestions: "强烈建议咨询心理医生或心理治疗师,进行专业的心理评估和治疗。", sortOrder: 3 }
|
||||
],
|
||||
warningRules: [
|
||||
{ factorId: null, ruleName: "重度症状预警", warningLevel: "高", scoreMin: 250, scoreMax: 360, autoRelief: "0", status: "0" },
|
||||
{ factorId: null, ruleName: "紧急症状预警", warningLevel: "紧急", scoreMin: 300, scoreMax: 360, autoRelief: "0", status: "0" }
|
||||
]
|
||||
};
|
||||
|
||||
function getFactorEnName(code) {
|
||||
const names = {
|
||||
"F1": "Somatization",
|
||||
"F2": "Obsessive-Compulsive",
|
||||
"F3": "Interpersonal Sensitivity",
|
||||
"F4": "Depression",
|
||||
"F5": "Anxiety",
|
||||
"F6": "Hostility",
|
||||
"F7": "Phobic Anxiety",
|
||||
"F8": "Paranoid Ideation",
|
||||
"F9": "Psychoticism"
|
||||
};
|
||||
return names[code] || "";
|
||||
}
|
||||
|
||||
// 输出JSON(需要Node.js环境运行)
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = json;
|
||||
} else {
|
||||
console.log(JSON.stringify(json, null, 2));
|
||||
}
|
||||
|
||||
388
z_Project change/量表示例/scale-import-example.json
Normal file
388
z_Project change/量表示例/scale-import-example.json
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
{
|
||||
"scale": {
|
||||
"scaleCode": "SCL_90_EXAMPLE",
|
||||
"scaleName": "症状自评量表SCL-90(示例)",
|
||||
"scaleEnName": "Symptom Checklist 90",
|
||||
"scaleType": "symptom",
|
||||
"scaleVersion": "1.0",
|
||||
"scaleIntro": "SCL-90是一个包含90个项目的症状自评量表,用于评估个体的心理症状水平",
|
||||
"scaleDescription": "SCL-90量表包含90个项目,涵盖9个症状因子,是心理健康评估的重要工具",
|
||||
"itemCount": 90,
|
||||
"estimatedTime": 30,
|
||||
"targetPopulation": "一般人群",
|
||||
"author": "Derogatis",
|
||||
"source": "心理卫生评定量表手册",
|
||||
"reference": "Derogatis, L. R. (1977). SCL-90: Administration, scoring, and procedures manual. Clinical Psychometric Research.",
|
||||
"status": "0",
|
||||
"sortOrder": 0
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"item": {
|
||||
"itemNumber": 1,
|
||||
"itemContent": "头痛",
|
||||
"itemType": "single",
|
||||
"required": "1",
|
||||
"reverseScore": "0",
|
||||
"sortOrder": 1
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"optionCode": "A",
|
||||
"optionContent": "没有",
|
||||
"optionScore": 0,
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"optionCode": "B",
|
||||
"optionContent": "很轻",
|
||||
"optionScore": 1,
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"optionCode": "C",
|
||||
"optionContent": "中等",
|
||||
"optionScore": 2,
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"optionCode": "D",
|
||||
"optionContent": "偏重",
|
||||
"optionScore": 3,
|
||||
"sortOrder": 4
|
||||
},
|
||||
{
|
||||
"optionCode": "E",
|
||||
"optionContent": "严重",
|
||||
"optionScore": 4,
|
||||
"sortOrder": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"item": {
|
||||
"itemNumber": 2,
|
||||
"itemContent": "神经过敏,心中不踏实",
|
||||
"itemType": "single",
|
||||
"required": "1",
|
||||
"reverseScore": "0",
|
||||
"sortOrder": 2
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"optionCode": "A",
|
||||
"optionContent": "没有",
|
||||
"optionScore": 0,
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"optionCode": "B",
|
||||
"optionContent": "很轻",
|
||||
"optionScore": 1,
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"optionCode": "C",
|
||||
"optionContent": "中等",
|
||||
"optionScore": 2,
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"optionCode": "D",
|
||||
"optionContent": "偏重",
|
||||
"optionScore": 3,
|
||||
"sortOrder": 4
|
||||
},
|
||||
{
|
||||
"optionCode": "E",
|
||||
"optionContent": "严重",
|
||||
"optionScore": 4,
|
||||
"sortOrder": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"item": {
|
||||
"itemNumber": 3,
|
||||
"itemContent": "头脑中有不必要的想法或字句盘旋",
|
||||
"itemType": "single",
|
||||
"required": "1",
|
||||
"reverseScore": "0",
|
||||
"sortOrder": 3
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"optionCode": "A",
|
||||
"optionContent": "没有",
|
||||
"optionScore": 0,
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"optionCode": "B",
|
||||
"optionContent": "很轻",
|
||||
"optionScore": 1,
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"optionCode": "C",
|
||||
"optionContent": "中等",
|
||||
"optionScore": 2,
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"optionCode": "D",
|
||||
"optionContent": "偏重",
|
||||
"optionScore": 3,
|
||||
"sortOrder": 4
|
||||
},
|
||||
{
|
||||
"optionCode": "E",
|
||||
"optionContent": "严重",
|
||||
"optionScore": 4,
|
||||
"sortOrder": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"item": {
|
||||
"itemNumber": 4,
|
||||
"itemContent": "头昏或昏倒",
|
||||
"itemType": "single",
|
||||
"required": "1",
|
||||
"reverseScore": "0",
|
||||
"sortOrder": 4
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"optionCode": "A",
|
||||
"optionContent": "没有",
|
||||
"optionScore": 0,
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"optionCode": "B",
|
||||
"optionContent": "很轻",
|
||||
"optionScore": 1,
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"optionCode": "C",
|
||||
"optionContent": "中等",
|
||||
"optionScore": 2,
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"optionCode": "D",
|
||||
"optionContent": "偏重",
|
||||
"optionScore": 3,
|
||||
"sortOrder": 4
|
||||
},
|
||||
{
|
||||
"optionCode": "E",
|
||||
"optionContent": "严重",
|
||||
"optionScore": 4,
|
||||
"sortOrder": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"item": {
|
||||
"itemNumber": 5,
|
||||
"itemContent": "对异性的兴趣减退",
|
||||
"itemType": "single",
|
||||
"required": "1",
|
||||
"reverseScore": "0",
|
||||
"sortOrder": 5
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"optionCode": "A",
|
||||
"optionContent": "没有",
|
||||
"optionScore": 0,
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"optionCode": "B",
|
||||
"optionContent": "很轻",
|
||||
"optionScore": 1,
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"optionCode": "C",
|
||||
"optionContent": "中等",
|
||||
"optionScore": 2,
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"optionCode": "D",
|
||||
"optionContent": "偏重",
|
||||
"optionScore": 3,
|
||||
"sortOrder": 4
|
||||
},
|
||||
{
|
||||
"optionCode": "E",
|
||||
"optionContent": "严重",
|
||||
"optionScore": 4,
|
||||
"sortOrder": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"factors": [
|
||||
{
|
||||
"factor": {
|
||||
"factorCode": "F1",
|
||||
"factorName": "躯体化",
|
||||
"factorEnName": "Somatization",
|
||||
"factorDescription": "反映躯体不适感,包括心血管、胃肠道、呼吸系统不适和头痛、背痛、肌肉酸痛等",
|
||||
"factorOrder": 1
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"itemNumber": 1,
|
||||
"rule": {
|
||||
"optionIds": "",
|
||||
"weight": 1.0,
|
||||
"calculationType": "sum"
|
||||
}
|
||||
},
|
||||
{
|
||||
"itemNumber": 4,
|
||||
"rule": {
|
||||
"optionIds": "",
|
||||
"weight": 1.0,
|
||||
"calculationType": "sum"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"factor": {
|
||||
"factorCode": "F2",
|
||||
"factorName": "强迫症状",
|
||||
"factorEnName": "Obsessive-Compulsive",
|
||||
"factorDescription": "反映强迫思维和强迫行为",
|
||||
"factorOrder": 2
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"itemNumber": 3,
|
||||
"rule": {
|
||||
"optionIds": "",
|
||||
"weight": 1.0,
|
||||
"calculationType": "sum"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"factor": {
|
||||
"factorCode": "F4",
|
||||
"factorName": "抑郁",
|
||||
"factorEnName": "Depression",
|
||||
"factorDescription": "反映与临床抑郁症状群相联系的广泛概念",
|
||||
"factorOrder": 4
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"itemNumber": 5,
|
||||
"rule": {
|
||||
"optionIds": "",
|
||||
"weight": 1.0,
|
||||
"calculationType": "sum"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"factor": {
|
||||
"factorCode": "F5",
|
||||
"factorName": "焦虑",
|
||||
"factorEnName": "Anxiety",
|
||||
"factorDescription": "反映与焦虑症状相联系的精神症状及体验",
|
||||
"factorOrder": 5
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"itemNumber": 2,
|
||||
"rule": {
|
||||
"optionIds": "",
|
||||
"weight": 1.0,
|
||||
"calculationType": "sum"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"interpretations": [
|
||||
{
|
||||
"scoreMin": 0,
|
||||
"scoreMax": 160,
|
||||
"level": "低",
|
||||
"levelName": "正常",
|
||||
"interpretationTitle": "正常范围",
|
||||
"interpretationContent": "您的总体得分在正常范围内,心理健康状况良好。",
|
||||
"suggestions": "继续保持良好的心理状态,注意日常生活中的压力管理。",
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"scoreMin": 161,
|
||||
"scoreMax": 250,
|
||||
"level": "中",
|
||||
"levelName": "轻度",
|
||||
"interpretationTitle": "轻度症状",
|
||||
"interpretationContent": "您可能存在轻度的心理症状,建议适当关注自己的心理健康。",
|
||||
"suggestions": "建议通过放松训练、运动、社交等方式缓解压力,如症状持续可考虑咨询专业人士。",
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"scoreMin": 251,
|
||||
"scoreMax": 360,
|
||||
"level": "高",
|
||||
"levelName": "中重度",
|
||||
"interpretationTitle": "中重度症状",
|
||||
"interpretationContent": "您的得分显示可能存在中重度的心理症状,建议寻求专业心理帮助。",
|
||||
"suggestions": "强烈建议咨询心理医生或心理治疗师,进行专业的心理评估和治疗。",
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"factorId": null,
|
||||
"scoreMin": 0,
|
||||
"scoreMax": 10,
|
||||
"level": "低",
|
||||
"levelName": "正常",
|
||||
"interpretationTitle": "躯体化因子正常",
|
||||
"interpretationContent": "您的躯体化因子得分在正常范围内。",
|
||||
"suggestions": "",
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"factorId": null,
|
||||
"scoreMin": 11,
|
||||
"scoreMax": 20,
|
||||
"level": "中",
|
||||
"levelName": "轻度",
|
||||
"interpretationTitle": "躯体化因子轻度",
|
||||
"interpretationContent": "您可能存在轻度的躯体化症状。",
|
||||
"suggestions": "建议关注身体健康,必要时进行体检。",
|
||||
"sortOrder": 2
|
||||
}
|
||||
],
|
||||
"warningRules": [
|
||||
{
|
||||
"ruleName": "重度症状预警",
|
||||
"warningLevel": "高",
|
||||
"scoreMin": 250,
|
||||
"scoreMax": 360,
|
||||
"status": "0"
|
||||
},
|
||||
{
|
||||
"ruleName": "紧急症状预警",
|
||||
"warningLevel": "紧急",
|
||||
"scoreMin": 300,
|
||||
"scoreMax": 360,
|
||||
"status": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
68
z_Project change/量表示例/下载说明.md
Normal file
68
z_Project change/量表示例/下载说明.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# 量表文件下载说明
|
||||
|
||||
## 推荐的下载来源
|
||||
|
||||
### 1. 优路教育(免费资源)
|
||||
**网址**:https://www.youlu.com/ziliao/detail/pack-52513
|
||||
|
||||
**包含的量表**:
|
||||
- SCL90症状自评量表(含常模).pdf
|
||||
- 焦虑自评量表.pdf
|
||||
- 自尊量表.pdf
|
||||
- PSTR心理压力量表.pdf
|
||||
- 青少年上网成瘾自评量表.pdf
|
||||
|
||||
**下载步骤**:
|
||||
1. 访问上述网址
|
||||
2. 注册/登录账号(可能需要)
|
||||
3. 点击"下载资料包"或单个文件下载
|
||||
4. 将下载的PDF文件保存到本文件夹
|
||||
|
||||
### 2. 其他推荐来源
|
||||
|
||||
#### 心理测量相关网站
|
||||
- 心理测量网
|
||||
- 中国心理卫生协会官网
|
||||
- 各高校心理学系网站
|
||||
|
||||
#### 电子书资源
|
||||
- 《心理卫生评定量表手册(增订版)》(汪向东等编著)
|
||||
- 《常用心理评估量表手册》
|
||||
- 各大学图书馆数据库
|
||||
|
||||
#### 学术数据库
|
||||
- 中国知网(CNKI)
|
||||
- 万方数据
|
||||
- 维普资讯
|
||||
|
||||
## 下载后的操作
|
||||
|
||||
1. **文件命名**:建议使用清晰的命名,如:
|
||||
- `SCL-90症状自评量表.pdf`
|
||||
- `SAS焦虑自评量表.pdf`
|
||||
- `SDS抑郁自评量表.pdf`
|
||||
|
||||
2. **文件位置**:将下载的PDF或DOCX文件保存到本文件夹(`z_Project change/量表示例/`)
|
||||
|
||||
3. **测试导入**:
|
||||
- 登录系统,进入"量表管理"
|
||||
- 点击"导入"按钮
|
||||
- 选择"文件上传"标签页
|
||||
- 上传PDF或DOCX文件
|
||||
- 使用"预览解析结果"查看识别效果
|
||||
- 根据需要进行调整后导入
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **版权问题**:请确保您有权使用下载的量表文件
|
||||
2. **文件格式**:建议使用PDF格式,识别准确率较高
|
||||
3. **文档质量**:清晰的扫描版或原生PDF文件识别效果更好
|
||||
4. **内容完整性**:确保文档包含完整的题目和选项
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. 先下载1-2个简单的量表(如SAS、SDS)进行测试
|
||||
2. 熟悉导入流程后再批量导入
|
||||
3. 对识别不准确的部分,可在预览界面手动调整
|
||||
4. 建议每次导入后检查数据完整性
|
||||
|
||||
Loading…
Reference in New Issue
Block a user