2025-11-26 14:23:53 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="comprehensive-assessment">
|
|
|
|
|
|
<el-card class="user-selector" shadow="never">
|
|
|
|
|
|
<el-form :inline="true" size="small">
|
|
|
|
|
|
<el-form-item label="选择用户">
|
2025-11-27 18:12:23 +08:00
|
|
|
|
<el-autocomplete
|
|
|
|
|
|
v-model="userSearchKeyword"
|
|
|
|
|
|
style="width: 320px"
|
2025-11-26 14:23:53 +08:00
|
|
|
|
clearable
|
2025-11-27 18:12:23 +08:00
|
|
|
|
:fetch-suggestions="searchUsers"
|
|
|
|
|
|
placeholder="输入姓名或信息编号搜索"
|
|
|
|
|
|
value-key="value"
|
|
|
|
|
|
:trigger-on-focus="false"
|
|
|
|
|
|
:debounce="400"
|
|
|
|
|
|
@select="handleUserSelect"
|
2025-11-26 14:23:53 +08:00
|
|
|
|
>
|
2025-11-27 18:12:23 +08:00
|
|
|
|
<template slot-scope="{ item }">
|
2025-11-26 14:23:53 +08:00
|
|
|
|
<div class="user-option">
|
|
|
|
|
|
<span class="name">{{ item.nickName || item.userName }}</span>
|
2025-11-27 18:12:23 +08:00
|
|
|
|
<span class="dept">{{ item.infoNumber ? `编号:${item.infoNumber}` : '' }}</span>
|
2025-11-26 14:23:53 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:23 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</el-autocomplete>
|
2025-11-26 14:23:53 +08:00
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item>
|
2025-11-27 18:12:23 +08:00
|
|
|
|
<el-button type="success" icon="el-icon-refresh" :disabled="!selectedUserId" @click="loadUserData">
|
2025-11-26 14:23:53 +08:00
|
|
|
|
载入
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button icon="el-icon-refresh" @click="resetSelection">重置</el-button>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
<el-card v-if="userProfile" class="user-info" shadow="never">
|
|
|
|
|
|
<el-descriptions :column="3" border size="small">
|
|
|
|
|
|
<el-descriptions-item label="姓名">{{ userProfile.userName || '-' }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="年龄">{{ calculateAge(userProfile.birthday) || '-' }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="信息编号">{{ userProfile.infoNumber || '-' }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="罪名">{{ userProfile.crimeName || '-' }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="刑期">{{ userProfile.sentenceTerm || '-' }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="刑期起日">{{ formatDate(userProfile.sentenceStartDate) || '-' }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="刑期止日">{{ formatDate(userProfile.sentenceEndDate) || '-' }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="文化程度">{{ userProfile.educationLevel || '-' }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="民族">{{ userProfile.nation || '-' }}</el-descriptions-item>
|
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
<el-card class="scale-list" shadow="never" v-loading="loading">
|
|
|
|
|
|
<div slot="header">
|
2025-11-28 17:31:13 +08:00
|
|
|
|
<span>量表/问卷列表(请勾选需要分析的量表/问卷)</span>
|
2025-11-26 14:23:53 +08:00
|
|
|
|
<div class="header-actions">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
plain
|
|
|
|
|
|
icon="el-icon-magic-stick"
|
|
|
|
|
|
size="mini"
|
|
|
|
|
|
:disabled="selectedReports.length === 0 || generating"
|
|
|
|
|
|
:loading="generating"
|
|
|
|
|
|
@click="generateComprehensiveReport"
|
|
|
|
|
|
>
|
|
|
|
|
|
AI生成综合报告
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-table
|
|
|
|
|
|
ref="reportTable"
|
|
|
|
|
|
:data="reportOptions"
|
|
|
|
|
|
@selection-change="handleReportSelection"
|
|
|
|
|
|
border
|
|
|
|
|
|
empty-text="暂无可用的测评报告"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-table-column type="selection" width="55" />
|
2025-11-27 18:12:23 +08:00
|
|
|
|
<el-table-column prop="scaleName" label="量表/问卷名称" min-width="220">
|
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
|
<el-tag
|
|
|
|
|
|
v-if="scope.row.sourceType === 'questionnaire'"
|
|
|
|
|
|
size="mini"
|
|
|
|
|
|
type="warning"
|
|
|
|
|
|
style="margin-right: 6px"
|
|
|
|
|
|
>问卷</el-tag>
|
|
|
|
|
|
<span>{{ scope.row.scaleName || '-' }}</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
2025-11-26 14:23:53 +08:00
|
|
|
|
<el-table-column prop="reportTitle" label="报告标题" min-width="240" :show-overflow-tooltip="true" />
|
|
|
|
|
|
<el-table-column label="测评时间" width="200" align="center">
|
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
|
{{ formatDateTime(scope.row.submitTime) }}
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="得分" width="100" align="center">
|
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
|
{{ scope.row.totalScore != null ? scope.row.totalScore : '-' }}
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
<el-empty
|
|
|
|
|
|
v-if="!loading && reportOptions.length === 0"
|
|
|
|
|
|
description="该用户暂无可用的量表报告"
|
|
|
|
|
|
></el-empty>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
title="综合评估报告"
|
|
|
|
|
|
:visible.sync="reportDialogVisible"
|
|
|
|
|
|
width="80%"
|
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
|
append-to-body
|
|
|
|
|
|
>
|
|
|
|
|
|
<div v-loading="generating" class="report-preview">
|
|
|
|
|
|
<div v-if="comprehensiveReport" v-html="comprehensiveReport" class="report-content"></div>
|
|
|
|
|
|
<div v-else class="empty-report">报告生成中,请稍候...</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div slot="footer">
|
2025-11-27 18:12:23 +08:00
|
|
|
|
<el-button
|
|
|
|
|
|
type="success"
|
|
|
|
|
|
icon="el-icon-printer"
|
|
|
|
|
|
:disabled="!comprehensiveReport"
|
|
|
|
|
|
@click="printReport"
|
|
|
|
|
|
>打印报告</el-button>
|
2025-11-26 14:23:53 +08:00
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
icon="el-icon-download"
|
|
|
|
|
|
:disabled="!comprehensiveReport"
|
|
|
|
|
|
@click="exportReport('word')"
|
|
|
|
|
|
>导出报告</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
2025-12-18 09:00:51 +08:00
|
|
|
|
import { getUserAssessmentSummary, getStudentOptions, listAssessment } from '@/api/psychology/assessment'
|
2025-11-27 18:12:23 +08:00
|
|
|
|
import { getProfileByUserId, listProfile } from '@/api/psychology/profile'
|
|
|
|
|
|
import { getReport, listReport } from '@/api/psychology/report'
|
2026-01-30 17:31:21 +08:00
|
|
|
|
import { parseTime } from '@/utils/common'
|
2025-11-26 14:23:53 +08:00
|
|
|
|
import axios from 'axios'
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: 'ComprehensiveAssessment',
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
selectedUserId: undefined,
|
2025-11-27 18:12:23 +08:00
|
|
|
|
userSearchKeyword: '',
|
|
|
|
|
|
userSearchLoading: false,
|
|
|
|
|
|
cachedUserOptions: [],
|
2025-11-26 14:23:53 +08:00
|
|
|
|
userProfile: null,
|
|
|
|
|
|
userSummary: null,
|
|
|
|
|
|
reportOptions: [],
|
|
|
|
|
|
selectedReports: [],
|
|
|
|
|
|
loading: false,
|
|
|
|
|
|
generating: false,
|
|
|
|
|
|
reportDialogVisible: false,
|
|
|
|
|
|
comprehensiveReport: '',
|
2025-12-20 18:05:11 +08:00
|
|
|
|
ragSourcesForReport: [], // RAG知识库来源
|
2025-12-21 18:57:14 +08:00
|
|
|
|
// ========== Ollama本地大模型配置(服务器部署)==========
|
|
|
|
|
|
API_URL: window.location.protocol === 'https:'
|
|
|
|
|
|
? '/ollama/api/chat'
|
|
|
|
|
|
: `http://${window.location.hostname}:11434/api/chat`,
|
|
|
|
|
|
API_KEY: '', // 本地模型不需要API Key
|
|
|
|
|
|
MODEL: 'deepseek-r1:32b'
|
2025-12-19 09:03:26 +08:00
|
|
|
|
|
2025-12-21 18:57:14 +08:00
|
|
|
|
// ========== 备用配置(Kimi API - 本地开发)==========
|
|
|
|
|
|
// API_URL: 'https://api.moonshot.cn/v1/chat/completions',
|
|
|
|
|
|
// API_KEY: 'sk-U9fdriPxwBcrpWW0Ite3N0eVtX7VxnqqqYUIBAdWd1hgEA9m',
|
|
|
|
|
|
// MODEL: 'moonshot-v1-32k'
|
2025-11-26 14:23:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
created() {
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
2025-11-27 18:12:23 +08:00
|
|
|
|
searchUsers(query, cb) {
|
|
|
|
|
|
this.fetchUserOptions(query)
|
|
|
|
|
|
.then((list) => cb(list))
|
|
|
|
|
|
.catch(() => cb([]))
|
|
|
|
|
|
},
|
|
|
|
|
|
fetchUserOptions(keyword) {
|
|
|
|
|
|
const trimmed = (keyword || '').trim()
|
|
|
|
|
|
if (!trimmed) {
|
|
|
|
|
|
return Promise.resolve([])
|
|
|
|
|
|
}
|
|
|
|
|
|
this.userSearchLoading = true
|
|
|
|
|
|
const studentPromise = getStudentOptions({ keyword: trimmed, limit: 20 })
|
|
|
|
|
|
.then((res) => this.normalizeStudentOptions(res.data || []))
|
|
|
|
|
|
.catch(() => [])
|
|
|
|
|
|
let profilePromise = Promise.resolve([])
|
2025-11-28 17:31:13 +08:00
|
|
|
|
// 如果是纯数字,通过信息编号搜索
|
2025-11-27 18:12:23 +08:00
|
|
|
|
if (/^\d+$/.test(trimmed)) {
|
|
|
|
|
|
profilePromise = listProfile({
|
|
|
|
|
|
infoNumber: trimmed,
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 20
|
|
|
|
|
|
})
|
|
|
|
|
|
.then((res) => this.normalizeProfileOptions(res.rows || []))
|
|
|
|
|
|
.catch(() => [])
|
2025-11-28 17:31:13 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 如果不是纯数字(输入姓名),通过姓名搜索档案以获取编号信息
|
|
|
|
|
|
profilePromise = listProfile({
|
|
|
|
|
|
userName: trimmed,
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 20
|
|
|
|
|
|
})
|
|
|
|
|
|
.then((res) => this.normalizeProfileOptions(res.rows || []))
|
|
|
|
|
|
.catch(() => [])
|
2025-11-27 18:12:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
return Promise.all([studentPromise, profilePromise])
|
|
|
|
|
|
.then(([studentList, profileList]) => {
|
2025-11-28 17:31:13 +08:00
|
|
|
|
// 合并时,优先使用档案数据(因为档案数据包含完整的infoNumber)
|
|
|
|
|
|
const merged = this.mergeUserOptions([...profileList, ...studentList])
|
2025-11-27 18:12:23 +08:00
|
|
|
|
this.cachedUserOptions = merged
|
|
|
|
|
|
return merged
|
2025-11-26 14:23:53 +08:00
|
|
|
|
})
|
|
|
|
|
|
.finally(() => {
|
2025-11-27 18:12:23 +08:00
|
|
|
|
this.userSearchLoading = false
|
2025-11-26 14:23:53 +08:00
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
buildUserLabel(option) {
|
|
|
|
|
|
if (!option) {
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
|
|
|
|
|
const name = option.nickName || option.userName || ''
|
2025-11-27 18:12:23 +08:00
|
|
|
|
const info = option.infoNumber ? `(编号:${option.infoNumber})` : ''
|
2025-11-26 14:23:53 +08:00
|
|
|
|
const dept = option.deptName ? ` - ${option.deptName}` : ''
|
2025-11-27 18:12:23 +08:00
|
|
|
|
return `${name}${info}${dept}`
|
2025-11-26 14:23:53 +08:00
|
|
|
|
},
|
2025-11-27 18:12:23 +08:00
|
|
|
|
handleUserSelect(option) {
|
|
|
|
|
|
if (!option || !option.userId) {
|
|
|
|
|
|
return
|
2025-11-26 14:23:53 +08:00
|
|
|
|
}
|
2025-11-27 18:12:23 +08:00
|
|
|
|
this.selectedUserId = option.userId
|
|
|
|
|
|
this.userSearchKeyword = this.buildUserLabel(option)
|
|
|
|
|
|
},
|
|
|
|
|
|
handleManualSearch() {
|
|
|
|
|
|
const keyword = (this.userSearchKeyword || '').trim()
|
|
|
|
|
|
if (!keyword) {
|
|
|
|
|
|
this.$message.warning('请输入姓名或信息编号')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-12-02 15:12:55 +08:00
|
|
|
|
|
2025-11-28 17:31:13 +08:00
|
|
|
|
// 从输入框中提取纯姓名或编号(去除括号、编号等格式)
|
|
|
|
|
|
let searchKeyword = keyword
|
|
|
|
|
|
// 如果包含编号格式,提取编号
|
|
|
|
|
|
const numberMatch = keyword.match(/编号[::]\s*(\d+)/)
|
|
|
|
|
|
if (numberMatch) {
|
|
|
|
|
|
searchKeyword = numberMatch[1]
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果包含姓名格式,提取姓名(去除括号和后面的内容)
|
|
|
|
|
|
const nameMatch = keyword.match(/^([^((]+)/)
|
|
|
|
|
|
if (nameMatch) {
|
|
|
|
|
|
searchKeyword = nameMatch[1].trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-02 15:12:55 +08:00
|
|
|
|
|
2025-11-28 17:31:13 +08:00
|
|
|
|
this.fetchUserOptions(searchKeyword).then((list) => {
|
2025-11-27 18:12:23 +08:00
|
|
|
|
if (!list.length) {
|
2025-11-28 17:31:13 +08:00
|
|
|
|
// 不显示错误消息,让用户继续搜索或选择
|
|
|
|
|
|
// 如果用户已经选择了用户,保持选择状态
|
2025-11-27 18:12:23 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (list.length === 1) {
|
2025-11-28 17:31:13 +08:00
|
|
|
|
// 找到唯一结果,自动选择
|
2025-11-27 18:12:23 +08:00
|
|
|
|
this.handleUserSelect(list[0])
|
|
|
|
|
|
} else {
|
2025-11-28 17:31:13 +08:00
|
|
|
|
// 如果找到多条,尝试精确匹配输入框的完整内容
|
|
|
|
|
|
const exactMatch = list.find(opt => {
|
|
|
|
|
|
const label = this.buildUserLabel(opt)
|
|
|
|
|
|
return label === keyword
|
|
|
|
|
|
})
|
|
|
|
|
|
if (exactMatch) {
|
|
|
|
|
|
// 精确匹配成功,自动选择
|
|
|
|
|
|
this.handleUserSelect(exactMatch)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 多条记录,提示用户从下拉列表选择
|
|
|
|
|
|
this.$message.info('找到多条记录,请从下拉列表选择具体用户')
|
|
|
|
|
|
}
|
2025-11-27 18:12:23 +08:00
|
|
|
|
}
|
2025-11-28 17:31:13 +08:00
|
|
|
|
}).catch(() => {
|
|
|
|
|
|
// 搜索失败时,不显示错误消息,让用户继续操作
|
2025-11-27 18:12:23 +08:00
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
normalizeStudentOptions(list) {
|
|
|
|
|
|
return list.map((item) => ({
|
|
|
|
|
|
userId: item.userId,
|
|
|
|
|
|
userName: item.userName,
|
|
|
|
|
|
nickName: item.nickName,
|
|
|
|
|
|
infoNumber: item.infoNumber,
|
|
|
|
|
|
deptName: item.deptName,
|
|
|
|
|
|
value: this.buildUserLabel(item)
|
|
|
|
|
|
}))
|
|
|
|
|
|
},
|
|
|
|
|
|
normalizeProfileOptions(rows) {
|
|
|
|
|
|
return rows
|
|
|
|
|
|
.filter((profile) => profile && profile.userId)
|
|
|
|
|
|
.map((profile) => ({
|
|
|
|
|
|
userId: profile.userId,
|
|
|
|
|
|
userName: profile.userName || profile.nickName,
|
|
|
|
|
|
nickName: profile.userName || profile.nickName,
|
|
|
|
|
|
infoNumber: profile.infoNumber,
|
|
|
|
|
|
deptName: profile.prisonArea || profile.deptName,
|
|
|
|
|
|
value: this.buildUserLabel({
|
|
|
|
|
|
userName: profile.userName || profile.nickName,
|
|
|
|
|
|
nickName: profile.userName || profile.nickName,
|
|
|
|
|
|
infoNumber: profile.infoNumber
|
|
|
|
|
|
})
|
|
|
|
|
|
}))
|
|
|
|
|
|
},
|
|
|
|
|
|
mergeUserOptions(list) {
|
|
|
|
|
|
const map = new Map()
|
|
|
|
|
|
list.forEach((item) => {
|
|
|
|
|
|
if (!item || !item.userId) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!map.has(item.userId)) {
|
|
|
|
|
|
map.set(item.userId, item)
|
2025-11-28 17:31:13 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 如果已存在该用户,优先保留有infoNumber的数据
|
|
|
|
|
|
const existing = map.get(item.userId)
|
|
|
|
|
|
if (!existing.infoNumber && item.infoNumber) {
|
|
|
|
|
|
// 如果已存在的没有infoNumber,而新项有,则更新
|
|
|
|
|
|
map.set(item.userId, item)
|
|
|
|
|
|
} else if (existing.infoNumber && !item.infoNumber) {
|
|
|
|
|
|
// 如果已存在的有infoNumber,而新项没有,则保留已存在的
|
|
|
|
|
|
// 不做任何操作
|
|
|
|
|
|
}
|
2025-11-27 18:12:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
return Array.from(map.values())
|
2025-11-26 14:23:53 +08:00
|
|
|
|
},
|
|
|
|
|
|
async loadUserData() {
|
|
|
|
|
|
if (!this.selectedUserId) {
|
|
|
|
|
|
this.$message.warning('请先选择用户')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
this.loading = true
|
|
|
|
|
|
try {
|
2025-12-18 09:00:51 +08:00
|
|
|
|
// 直接加载该用户的所有测评记录(不使用汇总接口,避免被过滤)
|
|
|
|
|
|
const assessmentResponse = await listAssessment({
|
|
|
|
|
|
userId: this.selectedUserId,
|
|
|
|
|
|
status: '1', // 只加载已完成的测评
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 1000
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const assessments = assessmentResponse.rows || []
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有量表报告(包含reportId)
|
|
|
|
|
|
const scaleReportsResponse = await listReport({
|
|
|
|
|
|
userId: this.selectedUserId,
|
|
|
|
|
|
sourceType: 'assessment',
|
|
|
|
|
|
isGenerated: '1',
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 1000
|
|
|
|
|
|
})
|
|
|
|
|
|
const reportMap = new Map()
|
|
|
|
|
|
if (scaleReportsResponse.rows) {
|
|
|
|
|
|
scaleReportsResponse.rows.forEach(report => {
|
|
|
|
|
|
if (report.assessmentId) {
|
|
|
|
|
|
reportMap.set(report.assessmentId, report)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为报告列表格式
|
|
|
|
|
|
const scaleReports = []
|
|
|
|
|
|
for (const assessment of assessments) {
|
|
|
|
|
|
// 检查是否有报告
|
|
|
|
|
|
if (assessment.hasReport) {
|
|
|
|
|
|
const report = reportMap.get(assessment.assessmentId)
|
|
|
|
|
|
scaleReports.push({
|
|
|
|
|
|
key: `${assessment.scaleId}-${assessment.assessmentId}`,
|
|
|
|
|
|
scaleId: assessment.scaleId,
|
|
|
|
|
|
scaleName: assessment.scaleName,
|
|
|
|
|
|
assessmentId: assessment.assessmentId,
|
|
|
|
|
|
reportId: report ? report.reportId : null,
|
|
|
|
|
|
reportTitle: report ? report.reportTitle : `${assessment.scaleName}测评报告`,
|
|
|
|
|
|
submitTime: assessment.submitTime || assessment.startTime,
|
|
|
|
|
|
totalScore: assessment.totalScore,
|
|
|
|
|
|
summary: report ? report.summary : '',
|
|
|
|
|
|
status: assessment.status,
|
|
|
|
|
|
sourceType: 'assessment'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户基本信息(用于显示)
|
2025-11-26 14:23:53 +08:00
|
|
|
|
const summaryResponse = await getUserAssessmentSummary(this.selectedUserId)
|
|
|
|
|
|
this.userSummary = summaryResponse.data || null
|
2025-12-18 09:00:51 +08:00
|
|
|
|
|
|
|
|
|
|
// 加载问卷报告
|
2025-11-27 18:12:23 +08:00
|
|
|
|
const questionnaireReports = await this.loadQuestionnaireReports(this.selectedUserId)
|
2025-12-18 09:00:51 +08:00
|
|
|
|
|
|
|
|
|
|
// 合并量表和问卷报告
|
2025-11-27 18:12:23 +08:00
|
|
|
|
const combinedReports = [...scaleReports, ...questionnaireReports].sort((a, b) => {
|
|
|
|
|
|
const timeA = a.submitTime ? new Date(a.submitTime).getTime() : 0
|
|
|
|
|
|
const timeB = b.submitTime ? new Date(b.submitTime).getTime() : 0
|
|
|
|
|
|
return timeB - timeA
|
|
|
|
|
|
})
|
|
|
|
|
|
this.reportOptions = combinedReports
|
2025-11-26 14:23:53 +08:00
|
|
|
|
this.selectedReports = []
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
|
if (this.$refs.reportTable && this.$refs.reportTable.doLayout) {
|
|
|
|
|
|
this.$refs.reportTable.doLayout()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 加载用户档案信息
|
|
|
|
|
|
try {
|
|
|
|
|
|
const profileResponse = await getProfileByUserId(this.selectedUserId)
|
|
|
|
|
|
this.userProfile = profileResponse.data || null
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.warn('获取用户档案失败:', error)
|
|
|
|
|
|
this.userProfile = null
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载用户数据失败:', error)
|
|
|
|
|
|
this.$message.error('加载用户数据失败')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.loading = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
resetSelection() {
|
|
|
|
|
|
this.selectedUserId = undefined
|
2025-11-27 18:12:23 +08:00
|
|
|
|
this.userSearchKeyword = ''
|
2025-11-26 14:23:53 +08:00
|
|
|
|
this.userProfile = null
|
|
|
|
|
|
this.userSummary = null
|
|
|
|
|
|
this.reportOptions = []
|
|
|
|
|
|
this.selectedReports = []
|
|
|
|
|
|
this.comprehensiveReport = ''
|
|
|
|
|
|
},
|
2025-11-27 18:12:23 +08:00
|
|
|
|
async loadQuestionnaireReports(userId) {
|
|
|
|
|
|
if (!userId) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await listReport({
|
|
|
|
|
|
userId: userId,
|
|
|
|
|
|
sourceType: 'questionnaire',
|
|
|
|
|
|
isGenerated: '1',
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 1000
|
|
|
|
|
|
})
|
|
|
|
|
|
const rows = response.rows || []
|
|
|
|
|
|
return rows.map((row) => ({
|
|
|
|
|
|
key: `questionnaire-${row.reportId}`,
|
|
|
|
|
|
scaleId: row.sourceId,
|
|
|
|
|
|
scaleName: row.reportTitle || '问卷报告',
|
|
|
|
|
|
assessmentId: row.sourceId,
|
|
|
|
|
|
reportId: row.reportId,
|
|
|
|
|
|
reportTitle: row.reportTitle || '问卷报告',
|
|
|
|
|
|
submitTime: row.generateTime || row.createTime,
|
|
|
|
|
|
totalScore: row.totalScore || row.score || '-',
|
|
|
|
|
|
summary: row.summary || '',
|
|
|
|
|
|
status: row.isGenerated === '1' ? '1' : '0',
|
|
|
|
|
|
sourceType: 'questionnaire'
|
|
|
|
|
|
}))
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载问卷报告失败:', error)
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-11-26 14:23:53 +08:00
|
|
|
|
formatDateTime(value) {
|
|
|
|
|
|
if (!value) return '-'
|
|
|
|
|
|
return parseTime(value)
|
|
|
|
|
|
},
|
|
|
|
|
|
formatDate(value) {
|
|
|
|
|
|
if (!value) return '-'
|
|
|
|
|
|
if (typeof value === 'string') return value
|
|
|
|
|
|
return parseTime(value, '{y}-{m}-{d}')
|
|
|
|
|
|
},
|
|
|
|
|
|
calculateAge(birthday) {
|
|
|
|
|
|
if (!birthday) return '-'
|
|
|
|
|
|
const birth = new Date(birthday)
|
|
|
|
|
|
const today = new Date()
|
|
|
|
|
|
let age = today.getFullYear() - birth.getFullYear()
|
|
|
|
|
|
const monthDiff = today.getMonth() - birth.getMonth()
|
|
|
|
|
|
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
|
|
|
|
|
|
age--
|
|
|
|
|
|
}
|
|
|
|
|
|
return age + '岁'
|
|
|
|
|
|
},
|
|
|
|
|
|
handleReportSelection(selection) {
|
|
|
|
|
|
this.selectedReports = selection
|
|
|
|
|
|
},
|
|
|
|
|
|
async generateComprehensiveReport() {
|
|
|
|
|
|
if (this.selectedReports.length === 0) {
|
|
|
|
|
|
this.$message.warning('请至少选择一个报告')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-19 09:03:26 +08:00
|
|
|
|
// 先保存选中的报告副本,防止在异步操作中被修改
|
|
|
|
|
|
const selectedReportsCopy = [...this.selectedReports]
|
|
|
|
|
|
console.log('generateComprehensiveReport - 选中报告数:', selectedReportsCopy.length, selectedReportsCopy)
|
|
|
|
|
|
|
2025-11-26 14:23:53 +08:00
|
|
|
|
this.generating = true
|
|
|
|
|
|
this.reportDialogVisible = true
|
|
|
|
|
|
this.comprehensiveReport = ''
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-12-19 09:03:26 +08:00
|
|
|
|
// 1. 获取选中量表的报告内容(传入副本)
|
|
|
|
|
|
const scaleReports = await this.fetchSelectedReports(selectedReportsCopy)
|
2025-11-26 14:23:53 +08:00
|
|
|
|
|
|
|
|
|
|
// 2. 构建用户信息摘要
|
|
|
|
|
|
const userInfoSummary = this.buildUserInfoSummary()
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 构建AI提示词
|
|
|
|
|
|
const prompt = this.buildAIPrompt(userInfoSummary, scaleReports)
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 调用AI生成报告
|
|
|
|
|
|
const aiReport = await this.callOLLAMA(prompt)
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 格式化并展示报告
|
|
|
|
|
|
this.comprehensiveReport = this.formatReport(aiReport, userInfoSummary, scaleReports)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('生成报告失败:', error)
|
|
|
|
|
|
this.$message.error('生成报告失败:' + (error.message || '未知错误'))
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.generating = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-12-19 09:03:26 +08:00
|
|
|
|
async fetchSelectedReports(selectedReportsList) {
|
|
|
|
|
|
// 使用传入的列表,而不是 this.selectedReports
|
|
|
|
|
|
const reportsToProcess = selectedReportsList || this.selectedReports || []
|
|
|
|
|
|
console.log('fetchSelectedReports - 待处理报告数:', reportsToProcess.length, reportsToProcess)
|
|
|
|
|
|
|
2025-11-26 14:23:53 +08:00
|
|
|
|
const reports = []
|
2025-12-19 09:03:26 +08:00
|
|
|
|
for (const row of reportsToProcess) {
|
|
|
|
|
|
console.log('处理报告行:', row)
|
|
|
|
|
|
// 即使没有reportId,也尝试添加基本信息
|
|
|
|
|
|
const sourceType = row.sourceType === 'questionnaire' ? 'questionnaire' : 'assessment'
|
|
|
|
|
|
let reportContent = ''
|
|
|
|
|
|
let summary = row.summary || ''
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有reportId,尝试获取详细内容
|
|
|
|
|
|
if (row.reportId) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await getReport(row.reportId, sourceType)
|
|
|
|
|
|
if (response && response.data) {
|
|
|
|
|
|
reportContent = response.data.reportContent || ''
|
|
|
|
|
|
summary = response.data.summary || row.summary || ''
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.warn(`获取${sourceType === 'questionnaire' ? '问卷' : '量表'} ${row.scaleName} 的报告失败:`, error)
|
2025-11-26 14:23:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-19 09:03:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 无论是否获取到详细内容,都添加到报告列表
|
|
|
|
|
|
reports.push({
|
|
|
|
|
|
scaleName: row.scaleName || row.reportTitle || '未知量表',
|
|
|
|
|
|
submitTime: row.submitTime,
|
|
|
|
|
|
totalScore: row.totalScore,
|
|
|
|
|
|
summary: summary,
|
|
|
|
|
|
content: reportContent,
|
|
|
|
|
|
sourceType
|
|
|
|
|
|
})
|
2025-11-26 14:23:53 +08:00
|
|
|
|
}
|
2025-12-19 09:03:26 +08:00
|
|
|
|
console.log('fetchSelectedReports - 返回报告数:', reports.length, reports)
|
2025-11-26 14:23:53 +08:00
|
|
|
|
return reports
|
|
|
|
|
|
},
|
|
|
|
|
|
buildUserInfoSummary() {
|
|
|
|
|
|
if (!this.userProfile) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
userName: this.userSummary?.userName || this.userSummary?.nickName || '未知',
|
2025-12-19 09:03:26 +08:00
|
|
|
|
infoNumber: '-',
|
|
|
|
|
|
gender: '-',
|
2025-11-26 14:23:53 +08:00
|
|
|
|
age: '-',
|
2025-12-19 09:03:26 +08:00
|
|
|
|
birthday: '-',
|
|
|
|
|
|
nation: '-',
|
|
|
|
|
|
educationLevel: '-',
|
2025-11-26 14:23:53 +08:00
|
|
|
|
crimeName: '-',
|
|
|
|
|
|
sentenceTerm: '-',
|
2025-12-19 09:03:26 +08:00
|
|
|
|
sentenceStartDate: '-',
|
2025-11-26 14:23:53 +08:00
|
|
|
|
sentenceEndDate: '-',
|
2025-12-19 09:03:26 +08:00
|
|
|
|
entryDate: '-',
|
|
|
|
|
|
prisonArea: '-',
|
|
|
|
|
|
status: '-'
|
2025-11-26 14:23:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-19 09:03:26 +08:00
|
|
|
|
// 状态映射
|
|
|
|
|
|
const statusMap = { '0': '在押', '1': '释放', '2': '外出', '3': '假释' }
|
|
|
|
|
|
const genderMap = { '0': '男', '1': '女', '2': '未知' }
|
2025-11-26 14:23:53 +08:00
|
|
|
|
return {
|
|
|
|
|
|
userName: this.userProfile.userName || this.userSummary?.userName || '未知',
|
2025-12-19 09:03:26 +08:00
|
|
|
|
infoNumber: this.userProfile.infoNumber || '-',
|
|
|
|
|
|
gender: genderMap[this.userProfile.gender] || this.userProfile.gender || '-',
|
2025-11-26 14:23:53 +08:00
|
|
|
|
age: this.calculateAge(this.userProfile.birthday),
|
2025-12-19 09:03:26 +08:00
|
|
|
|
birthday: this.formatDate(this.userProfile.birthday) || '-',
|
|
|
|
|
|
nation: this.userProfile.nation || '-',
|
|
|
|
|
|
educationLevel: this.userProfile.educationLevel || '-',
|
2025-11-26 14:23:53 +08:00
|
|
|
|
crimeName: this.userProfile.crimeName || '-',
|
|
|
|
|
|
sentenceTerm: this.userProfile.sentenceTerm || '-',
|
2025-12-19 09:03:26 +08:00
|
|
|
|
sentenceStartDate: this.formatDate(this.userProfile.sentenceStartDate) || '-',
|
|
|
|
|
|
sentenceEndDate: this.formatDate(this.userProfile.sentenceEndDate) || '-',
|
|
|
|
|
|
entryDate: this.formatDate(this.userProfile.entryDate) || '-',
|
|
|
|
|
|
prisonArea: this.userProfile.prisonArea || '-',
|
|
|
|
|
|
status: statusMap[this.userProfile.status] || this.userProfile.status || '-'
|
2025-11-26 14:23:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
buildAIPrompt(userInfo, scaleReports) {
|
2025-12-19 09:03:26 +08:00
|
|
|
|
// 构建量表清单
|
|
|
|
|
|
const scaleListText = scaleReports
|
|
|
|
|
|
.map((report, index) => {
|
|
|
|
|
|
const typeLabel = report.sourceType === 'questionnaire' ? '【问卷】' : '【量表】'
|
|
|
|
|
|
return `${index + 1}. ${typeLabel}${report.scaleName}(测评时间:${this.formatDateTime(report.submitTime)},得分:${report.totalScore})`
|
|
|
|
|
|
})
|
|
|
|
|
|
.join('\n')
|
|
|
|
|
|
|
2025-11-26 14:23:53 +08:00
|
|
|
|
const SYSTEM_PROMPT = [
|
2025-12-19 09:03:26 +08:00
|
|
|
|
'你是一名资深的监狱心理矫治专家和心理测评分析师,拥有丰富的罪犯心理评估和矫治工作经验。',
|
|
|
|
|
|
'请根据提供的服刑人员个人信息和多个心理量表/问卷测评结果,生成一份专业、详细、深入的综合心理评估报告。',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'【重要提示】本次分析涉及以下量表/问卷,请在报告中对每一个进行深入分析:',
|
|
|
|
|
|
scaleListText,
|
|
|
|
|
|
'',
|
|
|
|
|
|
'【报告撰写要求】',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'一、报告字数要求:',
|
|
|
|
|
|
' - 整份报告总字数不少于3000字',
|
|
|
|
|
|
' - 每个量表的分析不少于400字',
|
|
|
|
|
|
' - 综合评估结论不少于800字',
|
|
|
|
|
|
' - 管控措施建议不少于600字',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'二、个人信息深度分析要求:',
|
|
|
|
|
|
' - 必须结合被测试者的所有个人信息字段(姓名、年龄、性别、民族、文化程度、罪名、刑期、入监时间、监区、当前状态等)进行深入分析',
|
|
|
|
|
|
' - 分析年龄与心理特征的关系(如青年期冲动性、中年期稳定性等)',
|
|
|
|
|
|
' - 分析文化程度对认知能力和心理调适能力的影响',
|
|
|
|
|
|
' - 分析罪名类型反映的人格特征和行为模式(如暴力犯罪、经济犯罪、毒品犯罪等不同类型的心理特点)',
|
|
|
|
|
|
' - 分析刑期长短对心理状态的影响(短刑期焦虑、长刑期适应等)',
|
|
|
|
|
|
' - 分析服刑阶段(入监初期、服刑中期、临释期)的心理特点',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'三、量表因子深度分析要求:',
|
|
|
|
|
|
' - 对每个量表的各个因子进行逐一分析,说明因子得分的心理学含义',
|
|
|
|
|
|
' - 结合该量表的心理学理论背景进行专业解读',
|
|
|
|
|
|
' - 说明该量表的常模参照标准和得分等级划分',
|
|
|
|
|
|
' - 分析各因子之间的相互关系和整体心理画像',
|
|
|
|
|
|
' - 将量表结果与被测者的个人背景信息进行关联分析',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'四、心理学知识结合要求:',
|
|
|
|
|
|
' - 引用相关心理学理论(如人格理论、认知行为理论、社会学习理论等)',
|
|
|
|
|
|
' - 结合犯罪心理学知识分析犯罪行为的心理成因',
|
|
|
|
|
|
' - 运用发展心理学知识分析年龄阶段特点',
|
|
|
|
|
|
' - 应用临床心理学知识评估心理健康状况',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'五、管控措施要求(必须结合监狱工作实际):',
|
|
|
|
|
|
' - 提出具体的日常管理建议(如监舍安排、劳动岗位、活动参与等)',
|
|
|
|
|
|
' - 制定针对性的心理矫治方案(个别谈话、团体辅导、心理咨询等)',
|
|
|
|
|
|
' - 提出重点关注事项和风险防控措施',
|
|
|
|
|
|
' - 建议适合的教育改造项目和技能培训',
|
|
|
|
|
|
' - 提出与家属沟通和社会支持方面的建议',
|
|
|
|
|
|
' - 针对不同风险等级提出分级管控策略',
|
|
|
|
|
|
' - 建议定期心理复评的时间和重点关注指标',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'六、报告格式要求:',
|
|
|
|
|
|
' - 使用HTML格式输出',
|
|
|
|
|
|
' - 使用<h3>标签作为章节标题',
|
|
|
|
|
|
' - 使用<h4>标签作为小节标题',
|
|
|
|
|
|
' - 使用<p>标签作为段落',
|
|
|
|
|
|
' - 使用<ul><li>标签列出要点',
|
|
|
|
|
|
' - 使用<strong>标签强调重要内容',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'七、报告结构要求:',
|
|
|
|
|
|
' 1. 综合概述(约300字):列出所有分析的量表名称,概述评估目的和方法',
|
|
|
|
|
|
' 2. 个人背景分析(约400字):深入分析被测者的个人信息与心理特征的关系',
|
|
|
|
|
|
' 3. 各量表详细分析(每个量表约400字):',
|
|
|
|
|
|
' - 量表简介和测评目的',
|
|
|
|
|
|
' - 各因子得分分析',
|
|
|
|
|
|
' - 心理学理论解读',
|
|
|
|
|
|
' - 与个人背景的关联分析',
|
|
|
|
|
|
' 4. 综合评估结论(约800字):',
|
|
|
|
|
|
' - 整体心理状态评估',
|
|
|
|
|
|
' - 主要心理问题识别',
|
|
|
|
|
|
' - 心理风险等级判定',
|
|
|
|
|
|
' - 人格特征总结',
|
|
|
|
|
|
' 5. 管控措施与矫治建议(约600字):',
|
|
|
|
|
|
' - 日常管理建议',
|
|
|
|
|
|
' - 心理矫治方案',
|
|
|
|
|
|
' - 风险防控措施',
|
|
|
|
|
|
' - 教育改造建议',
|
|
|
|
|
|
' - 后续跟踪计划',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'八、其他要求:',
|
|
|
|
|
|
' - 语言专业、客观、严谨',
|
|
|
|
|
|
' - 仅输出分析结果,不要包含思考过程、<think>标签或</think>标签',
|
|
|
|
|
|
' - 不要使用markdown格式,只使用HTML标签'
|
2025-11-26 14:23:53 +08:00
|
|
|
|
].join('\n')
|
|
|
|
|
|
|
|
|
|
|
|
const userInfoText = `
|
2025-12-19 09:03:26 +08:00
|
|
|
|
【被测试者完整个人信息】
|
2025-11-26 14:23:53 +08:00
|
|
|
|
- 姓名:${userInfo.userName}
|
2025-12-19 09:03:26 +08:00
|
|
|
|
- 信息编号:${userInfo.infoNumber}
|
|
|
|
|
|
- 性别:${userInfo.gender}
|
2025-11-26 14:23:53 +08:00
|
|
|
|
- 年龄:${userInfo.age}
|
2025-12-19 09:03:26 +08:00
|
|
|
|
- 出生日期:${userInfo.birthday}
|
|
|
|
|
|
- 民族:${userInfo.nation}
|
|
|
|
|
|
- 文化程度:${userInfo.educationLevel}
|
2025-11-26 14:23:53 +08:00
|
|
|
|
- 罪名:${userInfo.crimeName}
|
|
|
|
|
|
- 刑期:${userInfo.sentenceTerm}
|
2025-12-19 09:03:26 +08:00
|
|
|
|
- 刑期起日:${userInfo.sentenceStartDate}
|
2025-11-26 14:23:53 +08:00
|
|
|
|
- 刑期止日:${userInfo.sentenceEndDate}
|
2025-12-19 09:03:26 +08:00
|
|
|
|
- 入监时间:${userInfo.entryDate}
|
|
|
|
|
|
- 所在监区:${userInfo.prisonArea}
|
|
|
|
|
|
- 当前状态:${userInfo.status}
|
2025-11-26 14:23:53 +08:00
|
|
|
|
`.trim()
|
|
|
|
|
|
|
|
|
|
|
|
const scaleReportsText = scaleReports
|
|
|
|
|
|
.map((report, index) => {
|
2025-11-27 18:12:23 +08:00
|
|
|
|
const typeLabel = report.sourceType === 'questionnaire' ? '问卷' : '量表'
|
2025-12-19 09:03:26 +08:00
|
|
|
|
// 提取完整内容以便AI更好地分析
|
2025-11-26 14:23:53 +08:00
|
|
|
|
const contentText = report.content
|
|
|
|
|
|
.replace(/<[^>]*>/g, '')
|
|
|
|
|
|
.replace(/ /g, ' ')
|
2025-12-19 09:03:26 +08:00
|
|
|
|
.replace(/\s+/g, ' ')
|
|
|
|
|
|
.substring(0, 2000) // 增加内容长度以获取更多因子信息
|
2025-11-26 14:23:53 +08:00
|
|
|
|
return `
|
2025-12-19 09:03:26 +08:00
|
|
|
|
【${typeLabel}${index + 1}】${report.scaleName}
|
|
|
|
|
|
- 测评时间:${this.formatDateTime(report.submitTime)}
|
|
|
|
|
|
- 总分:${report.totalScore}
|
|
|
|
|
|
- 报告摘要:${report.summary || '无'}
|
|
|
|
|
|
- 详细内容(包含各因子得分):${contentText}...
|
2025-11-26 14:23:53 +08:00
|
|
|
|
`.trim()
|
|
|
|
|
|
})
|
|
|
|
|
|
.join('\n\n')
|
|
|
|
|
|
|
2025-12-19 09:03:26 +08:00
|
|
|
|
return `${SYSTEM_PROMPT}\n\n${userInfoText}\n\n【各量表/问卷详细报告及因子分析】\n${scaleReportsText}`
|
2025-11-26 14:23:53 +08:00
|
|
|
|
},
|
2025-12-21 18:57:14 +08:00
|
|
|
|
// Ollama 本地大模型调用方法
|
2025-11-26 14:23:53 +08:00
|
|
|
|
async callOLLAMA(prompt) {
|
|
|
|
|
|
try {
|
2025-12-20 18:05:11 +08:00
|
|
|
|
// 1. 先调用RAG服务获取知识库上下文
|
|
|
|
|
|
let knowledgeContext = '';
|
|
|
|
|
|
let ragSources = [];
|
2025-12-21 18:57:14 +08:00
|
|
|
|
const RAG_API_URL = window.location.protocol === 'https:'
|
|
|
|
|
|
? '/rag-api/api/rag-analyze'
|
|
|
|
|
|
: `http://${window.location.hostname}:5000/api/rag-analyze`;
|
2025-12-20 18:05:11 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 从prompt中提取关键信息用于RAG检索
|
|
|
|
|
|
const ragResponse = await axios.post(RAG_API_URL, {
|
|
|
|
|
|
reportContent: prompt,
|
|
|
|
|
|
reportTitle: '综合心理评估'
|
|
|
|
|
|
}, { timeout: 10000 });
|
|
|
|
|
|
|
|
|
|
|
|
if (ragResponse.data && ragResponse.data.success) {
|
|
|
|
|
|
knowledgeContext = ragResponse.data.data.knowledgeContext || '';
|
|
|
|
|
|
ragSources = ragResponse.data.data.sources || [];
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 在控制台打印完整的知识库引用信息 ==========
|
|
|
|
|
|
console.log('========================================');
|
|
|
|
|
|
console.log('📚 RAG知识库检索结果');
|
|
|
|
|
|
console.log('========================================');
|
|
|
|
|
|
console.log('检索到的文档数量:', ragSources.length);
|
|
|
|
|
|
console.log('');
|
|
|
|
|
|
console.log('📋 引用的知识库文件列表:');
|
|
|
|
|
|
ragSources.forEach((source, index) => {
|
|
|
|
|
|
console.log(` ${index + 1}. 文件名: ${source.filename || '未知'}`);
|
|
|
|
|
|
console.log(` 相似度: ${source.similarity ? (source.similarity * 100).toFixed(2) + '%' : '未知'}`);
|
|
|
|
|
|
console.log(` 内容预览: ${source.content ? source.content.substring(0, 100) + '...' : '无内容'}`);
|
|
|
|
|
|
console.log('');
|
|
|
|
|
|
});
|
|
|
|
|
|
console.log('========================================');
|
|
|
|
|
|
console.log('📝 知识库上下文长度:', knowledgeContext.length, '字符');
|
|
|
|
|
|
console.log('========================================');
|
|
|
|
|
|
// ========== 打印结束 ==========
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (ragErr) {
|
|
|
|
|
|
console.warn('RAG服务调用失败,将不使用知识库增强:', ragErr.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 如果有知识库上下文,添加到prompt中
|
|
|
|
|
|
let enhancedPrompt = prompt;
|
|
|
|
|
|
if (knowledgeContext) {
|
|
|
|
|
|
enhancedPrompt = prompt + '\n\n【专业知识库参考资料】\n' + knowledgeContext + '\n\n请结合以上专业心理学资料进行分析,使报告更加专业和有深度。';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-21 18:57:14 +08:00
|
|
|
|
// 3. 调用AI API(根据API类型选择不同的请求格式)
|
|
|
|
|
|
let response = '';
|
|
|
|
|
|
|
|
|
|
|
|
if (this.API_URL.includes('11434')) {
|
|
|
|
|
|
// Ollama 本地模型格式
|
|
|
|
|
|
const { data } = await axios.post(this.API_URL, {
|
|
|
|
|
|
model: this.MODEL,
|
|
|
|
|
|
messages: [
|
|
|
|
|
|
{ role: 'user', content: enhancedPrompt }
|
|
|
|
|
|
],
|
|
|
|
|
|
stream: false,
|
|
|
|
|
|
options: {
|
|
|
|
|
|
temperature: 0.3,
|
|
|
|
|
|
num_predict: 8000
|
|
|
|
|
|
}
|
|
|
|
|
|
}, {
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
timeout: 600000 // Ollama本地模型需要更长时间,设置10分钟
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
response = data?.message?.content ?? '';
|
|
|
|
|
|
console.log('Ollama响应:', response);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// OpenAI兼容格式(Kimi等)
|
|
|
|
|
|
const { data } = await axios.post(this.API_URL, {
|
|
|
|
|
|
model: this.MODEL,
|
|
|
|
|
|
messages: [
|
|
|
|
|
|
{ role: 'user', content: enhancedPrompt }
|
|
|
|
|
|
],
|
|
|
|
|
|
temperature: 0.3,
|
|
|
|
|
|
max_tokens: 8000,
|
|
|
|
|
|
stream: false
|
|
|
|
|
|
}, {
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Authorization': `Bearer ${this.API_KEY}`,
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
timeout: 180000
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
response = data?.choices?.[0]?.message?.content ?? '';
|
|
|
|
|
|
console.log('OpenAI格式响应:', response);
|
|
|
|
|
|
}
|
2025-12-20 18:05:11 +08:00
|
|
|
|
|
|
|
|
|
|
// 4. 如果有知识库来源,添加参考资料说明
|
|
|
|
|
|
if (ragSources && ragSources.length > 0) {
|
|
|
|
|
|
this.ragSourcesForReport = ragSources;
|
|
|
|
|
|
}
|
2025-11-26 14:23:53 +08:00
|
|
|
|
|
|
|
|
|
|
// 清理响应内容
|
|
|
|
|
|
response = response
|
|
|
|
|
|
.replace(/<think>[\s\S]*?<\/think>/gi, '')
|
|
|
|
|
|
.replace(/<think>[\s\S]*?<\/redacted_reasoning>/gi, '')
|
|
|
|
|
|
.replace(/```html\s*/gi, '')
|
|
|
|
|
|
.replace(/```\s*/g, '')
|
|
|
|
|
|
.replace(/```[a-z]*\s*/gi, '')
|
|
|
|
|
|
.trim()
|
|
|
|
|
|
|
2025-12-02 15:12:55 +08:00
|
|
|
|
console.log('清理后响应:', response)
|
|
|
|
|
|
|
2025-11-26 14:23:53 +08:00
|
|
|
|
if (!response) {
|
2025-12-02 15:12:55 +08:00
|
|
|
|
throw new Error('AI分析返回结果为空')
|
2025-11-26 14:23:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
} catch (error) {
|
2025-12-02 15:12:55 +08:00
|
|
|
|
console.error('AI分析失败:', error)
|
|
|
|
|
|
throw new Error('AI分析失败:' + (error.message || '未知错误'))
|
2025-11-26 14:23:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
formatReport(aiReport, userInfo, scaleReports) {
|
2025-12-19 09:03:26 +08:00
|
|
|
|
console.log('formatReport - scaleReports:', scaleReports)
|
|
|
|
|
|
console.log('formatReport - scaleReports length:', scaleReports ? scaleReports.length : 0)
|
|
|
|
|
|
|
|
|
|
|
|
// 先构建量表列表HTML
|
|
|
|
|
|
let scaleListHtml = ''
|
|
|
|
|
|
if (scaleReports && scaleReports.length > 0) {
|
|
|
|
|
|
const scaleItems = scaleReports.map((r, i) => {
|
|
|
|
|
|
const typeLabel = r.sourceType === 'questionnaire' ? '【问卷】' : '【量表】'
|
|
|
|
|
|
const timeStr = this.formatDateTime(r.submitTime)
|
|
|
|
|
|
return `<li>${i + 1}. ${typeLabel}${r.scaleName}(测评时间:${timeStr},总分:${r.totalScore})</li>`
|
|
|
|
|
|
})
|
|
|
|
|
|
scaleListHtml = scaleItems.join('')
|
|
|
|
|
|
} else {
|
|
|
|
|
|
scaleListHtml = '<li>暂无量表数据</li>'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:23:53 +08:00
|
|
|
|
// 构建完整的报告HTML
|
|
|
|
|
|
const reportHtml = `
|
|
|
|
|
|
<div class="comprehensive-report">
|
2025-12-19 09:03:26 +08:00
|
|
|
|
<h1 style="text-align: center; margin-bottom: 30px;">综合心理评估报告</h1>
|
2025-12-02 15:12:55 +08:00
|
|
|
|
|
2025-11-26 14:23:53 +08:00
|
|
|
|
<div class="user-info-section" style="margin-bottom: 30px; padding: 15px; background-color: #f5f7fa; border-radius: 4px;">
|
2025-12-19 09:03:26 +08:00
|
|
|
|
<h2 style="margin-top: 0;">被测试者基本信息</h2>
|
2025-11-26 14:23:53 +08:00
|
|
|
|
<table style="width: 100%; border-collapse: collapse;">
|
|
|
|
|
|
<tr>
|
2025-12-19 09:03:26 +08:00
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd; width: 120px; background-color: #fafafa;"><strong>姓名</strong></td>
|
2025-11-26 14:23:53 +08:00
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${userInfo.userName}</td>
|
2025-12-19 09:03:26 +08:00
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd; width: 120px; background-color: #fafafa;"><strong>信息编号</strong></td>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${userInfo.infoNumber}</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd; background-color: #fafafa;"><strong>性别</strong></td>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${userInfo.gender}</td>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd; background-color: #fafafa;"><strong>年龄</strong></td>
|
2025-11-26 14:23:53 +08:00
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${userInfo.age}</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
2025-12-19 09:03:26 +08:00
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd; background-color: #fafafa;"><strong>民族</strong></td>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${userInfo.nation}</td>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd; background-color: #fafafa;"><strong>文化程度</strong></td>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${userInfo.educationLevel}</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd; background-color: #fafafa;"><strong>罪名</strong></td>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd;" colspan="3">${userInfo.crimeName}</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd; background-color: #fafafa;"><strong>刑期</strong></td>
|
2025-11-26 14:23:53 +08:00
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${userInfo.sentenceTerm}</td>
|
2025-12-19 09:03:26 +08:00
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd; background-color: #fafafa;"><strong>当前状态</strong></td>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${userInfo.status}</td>
|
2025-11-26 14:23:53 +08:00
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
2025-12-19 09:03:26 +08:00
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd; background-color: #fafafa;"><strong>刑期起日</strong></td>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${userInfo.sentenceStartDate}</td>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd; background-color: #fafafa;"><strong>刑期止日</strong></td>
|
2025-11-26 14:23:53 +08:00
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${userInfo.sentenceEndDate}</td>
|
2025-12-19 09:03:26 +08:00
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd; background-color: #fafafa;"><strong>入监时间</strong></td>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${userInfo.entryDate}</td>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd; background-color: #fafafa;"><strong>所在监区</strong></td>
|
|
|
|
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${userInfo.prisonArea}</td>
|
2025-11-26 14:23:53 +08:00
|
|
|
|
</tr>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="scale-summary" style="margin-bottom: 30px;">
|
2025-12-19 09:03:26 +08:00
|
|
|
|
<h2>参与测评的量表/问卷(共${scaleReports ? scaleReports.length : 0}个)</h2>
|
|
|
|
|
|
<ul style="line-height: 2; padding-left: 20px;">
|
|
|
|
|
|
${scaleListHtml}
|
2025-11-26 14:23:53 +08:00
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="ai-analysis">
|
|
|
|
|
|
<h2 style="color: #67C23A; border-bottom: 2px solid #67C23A; padding-bottom: 10px;">AI综合评估分析</h2>
|
|
|
|
|
|
${this.formatAIResult(aiReport)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; text-align: right; color: #909399; font-size: 12px;">
|
2025-11-27 18:12:23 +08:00
|
|
|
|
<div>报告生成时间:${parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')}</div>
|
2025-12-02 15:12:55 +08:00
|
|
|
|
<div style="margin-top: 8px;">被评估人:<span style="color: #303133; font-weight: bold; text-decoration: underline;">______________________</span></div>
|
2025-11-26 14:23:53 +08:00
|
|
|
|
</div>
|
2025-12-19 09:03:26 +08:00
|
|
|
|
|
|
|
|
|
|
<div style="margin-top: 30px; padding: 15px; background-color: #FDF6EC; border: 1px solid #E6A23C; border-radius: 4px; text-align: center;">
|
|
|
|
|
|
<p style="margin: 0; color: #E6A23C; font-size: 14px; font-weight: bold;">
|
|
|
|
|
|
⚠️ 此结果仅供参考,不可作为临床诊断的唯一标准
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
2025-12-20 18:05:11 +08:00
|
|
|
|
|
|
|
|
|
|
${this.ragSourcesForReport && this.ragSourcesForReport.length > 0 ? `
|
|
|
|
|
|
<div class="rag-sources" style="margin-top: 20px; padding: 15px; background: #f5f7fa; border-radius: 8px;">
|
|
|
|
|
|
<h4 style="margin: 0 0 10px 0; color: #409EFF;"><i class="el-icon-document"></i> 参考知识库资料</h4>
|
|
|
|
|
|
<ul style="margin: 0; padding-left: 20px; color: #666;">
|
|
|
|
|
|
${this.ragSourcesForReport.map(source => `<li style="margin-bottom: 5px;"><strong>${source.filename}</strong></li>`).join('')}
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
` : ''}
|
2025-11-26 14:23:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
`
|
|
|
|
|
|
return reportHtml
|
|
|
|
|
|
},
|
|
|
|
|
|
formatAIResult(text) {
|
|
|
|
|
|
let html = text.trim()
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经是HTML格式,清理后返回
|
|
|
|
|
|
if (html.includes('<h3>') || html.includes('<p>') || html.includes('<div>')) {
|
|
|
|
|
|
return html
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理标题
|
|
|
|
|
|
html = html.replace(/^(\d+[\.、]?\s*[^\n]+)$/gm, '<h3>$1</h3>')
|
|
|
|
|
|
html = html.replace(/^([^\n]*(?:结论|分析|建议|总结|概述)[^\n]*)$/gm, '<h3>$1</h3>')
|
|
|
|
|
|
|
|
|
|
|
|
// 将段落分隔符转换为<p>标签
|
|
|
|
|
|
html = html.split('\n\n').map((para) => {
|
|
|
|
|
|
para = para.trim()
|
|
|
|
|
|
if (!para) return ''
|
|
|
|
|
|
if (para.startsWith('<h3>')) return para
|
|
|
|
|
|
return '<p>' + para.replace(/\n/g, '<br>') + '</p>'
|
|
|
|
|
|
}).join('')
|
|
|
|
|
|
|
|
|
|
|
|
return html
|
|
|
|
|
|
},
|
2025-11-27 18:12:23 +08:00
|
|
|
|
buildReportHtml() {
|
|
|
|
|
|
return `
|
2025-11-26 14:23:53 +08:00
|
|
|
|
<html>
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8" />
|
|
|
|
|
|
<title>综合评估报告</title>
|
|
|
|
|
|
<style>
|
|
|
|
|
|
body { font-family: 'Microsoft Yahei', sans-serif; padding: 32px; color: #303133; }
|
|
|
|
|
|
h1, h2, h3 { margin: 16px 0; }
|
|
|
|
|
|
h1 { text-align: center; font-size: 24px; }
|
|
|
|
|
|
h2 { font-size: 20px; color: #1f2d3d; }
|
|
|
|
|
|
h3 { font-size: 18px; color: #606266; }
|
|
|
|
|
|
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
|
|
|
|
|
table td, table th { border: 1px solid #ddd; padding: 8px; }
|
|
|
|
|
|
ul { line-height: 1.8; }
|
|
|
|
|
|
p { line-height: 1.8; margin: 10px 0; }
|
|
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
${this.comprehensiveReport}
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|
|
|
|
|
|
`
|
2025-11-27 18:12:23 +08:00
|
|
|
|
},
|
|
|
|
|
|
exportReport(format) {
|
|
|
|
|
|
if (!this.comprehensiveReport) {
|
|
|
|
|
|
this.$message.warning('报告内容为空')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const reportHtml = this.buildReportHtml()
|
2025-11-26 14:23:53 +08:00
|
|
|
|
|
|
|
|
|
|
if (format === 'word') {
|
|
|
|
|
|
const blob = new Blob(['\ufeff', reportHtml], { type: 'application/msword' })
|
|
|
|
|
|
const filename = `综合评估报告_${this.userProfile?.userName || '用户'}_${Date.now()}.doc`
|
|
|
|
|
|
const link = document.createElement('a')
|
|
|
|
|
|
link.href = window.URL.createObjectURL(blob)
|
|
|
|
|
|
link.download = filename
|
|
|
|
|
|
document.body.appendChild(link)
|
|
|
|
|
|
link.click()
|
|
|
|
|
|
document.body.removeChild(link)
|
|
|
|
|
|
window.URL.revokeObjectURL(link.href)
|
|
|
|
|
|
this.$message.success('导出成功')
|
|
|
|
|
|
} else {
|
2025-11-27 18:12:23 +08:00
|
|
|
|
this.printReport()
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
printReport() {
|
|
|
|
|
|
if (!this.comprehensiveReport) {
|
|
|
|
|
|
this.$message.warning('报告内容为空')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const reportHtml = this.buildReportHtml()
|
|
|
|
|
|
const printWindow = window.open('', '_blank')
|
|
|
|
|
|
if (!printWindow) {
|
|
|
|
|
|
this.$message.error('无法打开打印窗口,请检查浏览器是否阻止了弹窗')
|
|
|
|
|
|
return
|
2025-11-26 14:23:53 +08:00
|
|
|
|
}
|
2025-11-27 18:12:23 +08:00
|
|
|
|
printWindow.document.write(reportHtml)
|
|
|
|
|
|
printWindow.document.close()
|
|
|
|
|
|
printWindow.focus()
|
|
|
|
|
|
printWindow.print()
|
2025-11-26 14:23:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.comprehensive-assessment {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-selector {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-info {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.scale-list {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-option {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-option .name {
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-option .dept {
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.report-preview {
|
|
|
|
|
|
min-height: 400px;
|
|
|
|
|
|
max-height: 600px;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.report-content {
|
|
|
|
|
|
line-height: 1.8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.report-content h3 {
|
|
|
|
|
|
color: #409EFF;
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.report-content p {
|
|
|
|
|
|
margin: 10px 0;
|
|
|
|
|
|
text-indent: 2em;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-report {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 100px 0;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|