2026-01-28 10:30:20 +08:00

288 lines
9.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<Dialog style="display: none;" :title="'评估报告'" v-model="dialogVisible" width="900px">
<div v-loading="loading" class="report-edit-container" ref="previewRef">
<template v-if="selectedReport">
<div class="basic-info-title">{{ selectedReport.templateName }}</div>
<!-- 基本信息区 -->
<div class="basic-info-section">
<div class="basic-info-item">服刑人员{{ selectedReport.prisonerName }} ({{ selectedReport.prisonerNo }})</div>
<div class="basic-info-item">监区{{ selectedReport.areaName || '-' }}</div>
<div class="basic-info-item">评估日期{{ formatDateTime(selectedReport.evaluationDate, 'YYYY-MM-DD') }}</div>
<div class="basic-info-item">风险等级{{ getDictLabel(DICT_TYPE.PRISON_RISK_LEVEL, selectedReport.riskLevel) }}</div>
<div class="basic-info-item">状态{{ getDictLabel(DICT_TYPE.PRISON_REPORT_STATUS, selectedReport.status) }}</div>
</div>
<div v-for="item in dimensionAnalysisPanelRef" :key="item.id" class="dimension-item">
<div class="dimension-item-title">{{ item.name }}</div>
<div style="white-space: pre-line; line-height: 1.5;">{{ item.aiAnalysis?.replace(/## 综合分析建议\n\n/g, '') }}</div>
</div>
</template>
<template v-else-if="!loading">
<el-empty description="报告不存在或已被删除" />
</template>
</div>
<template #footer>
<el-button @click="handleClose">取消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
import { formatDateTime } from '@/utils/formatTime'
import download from '@/utils/download'
import { ReportApi, ReportVO, DimensionDataApi, DimensionDataVO, DimensionApi, DimensionVO } from '@/api/prison/evaluation-report'
import { PrisonerApi } from '@/api/prison/prisoner'
import { asBlob } from 'html-docx-js-typescript'
import { saveAs } from 'file-saver'
defineOptions({ name: 'CreateReportOutput' })
const { t } = useI18n()
const message = useMessage()
const router = useRouter()
// 抽屉状态
const dialogVisible = ref(false)
const drawerTitle = ref('编辑评估报告')
const loading = ref(false)
const saving = ref(false)
const aiGenerating = ref(false)
const aiGeneratingDimension = ref<number | undefined>(undefined)
// 数据状态
const reportId = ref<number>()
const selectedReport = ref<ReportVO | null>(null)
const dimensionDataList = ref<DimensionDataVO[]>([])
const dimensions = ref<any[]>([])
const dimensionAnalysisPanelRef = ref<DimensionDataVO[]>([])
const previewRef = ref<HTMLElement | null>(null)
/** 打开抽屉 */
const open = async (id: number, prisonerId?: number) => {
reportId.value = id
dialogVisible.value = true
await loadReportDetail(id)
try {
exportToWord()
} catch {
} finally {
handleClose()
}
}
/** 加载报告详情 */
const loadReportDetail = async (id: number) => {
loading.value = true
try {
selectedReport.value = await ReportApi.getReport(id)
dimensionDataList.value = await DimensionDataApi.getDimensionDataListByReportId(id)
drawerTitle.value = selectedReport.value?.title || `${selectedReport.value?.prisonerName} - 评估报告`
// 监区信息兜底(报告未返回监区时,从罪犯档案补齐)
if (selectedReport.value?.prisonerId && !selectedReport.value.areaName) {
const prisoner = await PrisonerApi.get(selectedReport.value.prisonerId)
if (prisoner?.prisonAreaName) {
selectedReport.value.areaName = prisoner.prisonAreaName
selectedReport.value.areaId = prisoner.prisonAreaId
}
}
// 加载维度配置
if (selectedReport.value?.templateId) {
try {
const dimensionList = await DimensionApi.getDimensionsByTemplateId(selectedReport.value.templateId)
if (dimensionList && dimensionList.length > 0) {
console.log(dimensionList);
dimensions.value = dimensionList
} else {
// 使用默认维度配置
dimensions.value = getDefaultDimensions(selectedReport.value.templateId)
}
} catch {
dimensions.value = getDefaultDimensions(selectedReport.value.templateId)
}
}
if (selectedReport.value?.id && dimensions.value.length > 0) {
const list = await DimensionDataApi.getDimensionDataListByReportId(selectedReport.value.id)
console.log(list, dimensions.value);
dimensionAnalysisPanelRef.value = dimensions.value.map(item => {
return {
...item,
aiAnalysis: list.find(analys => analys.dimensionId === item.id)?.aiAnalysis
}
})
}
} catch (error) {
message.error(error?.msg || '加载报告失败')
selectedReport.value = null
} finally {
loading.value = false
}
}
/** 获取默认维度配置 */
const getDefaultDimensions = (templateId: number): DimensionVO[] => {
return [
{ id: 1, templateId, name: '基本信息', dimensionType: 1, aiEnabled: 0, status: 0, dataSources: ['prisoner'] },
{ id: 2, templateId, name: '犯罪情况分析', dimensionType: 1, aiEnabled: 1, status: 0, dataSources: ['prisoner', 'risk'] },
{ id: 3, templateId, name: '服刑表现评估', dimensionType: 1, aiEnabled: 1, status: 0, dataSources: ['score', 'violation', 'reward'] },
{ id: 4, templateId, name: '消费行为分析', dimensionType: 1, aiEnabled: 1, status: 0, dataSources: ['consumption'] },
{ id: 5, templateId, name: '综合评估结论', dimensionType: 1, aiEnabled: 1, status: 0, dataSources: ['prisoner', 'psychology'] }
]
}
/** 关闭 */
const handleClose = () => {
dialogVisible.value = false
selectedReport.value = null
dimensionDataList.value = []
}
/** 风险等级变化 */
const handleRiskLevelChange = () => {
// 风险等级变化会自动保存到 selectedReport
}
/** 导出为Word文档 - 直接使用预览容器的HTML */
const exportToWord = async () => {
try {
loading.value = true
if (!previewRef.value) {
message.error('预览内容未加载完成')
return
}
// 获取预览容器的HTML内容
let previewHTML = previewRef.value.innerHTML
// 将文本内容中的 \n 转换为 <br> 标签,使 Word 能够正确换行
// 使用临时 DOM 解析 HTML处理文本节点中的换行符
const tempDiv = document.createElement('div')
tempDiv.innerHTML = previewHTML
const processTextNodes = (node: Node) => {
if (node.nodeType === Node.TEXT_NODE) {
// 将文本节点中的 \n 替换为 <br>
const text = node.textContent || ''
if (text.includes('\n')) {
const span = document.createElement('span')
span.innerHTML = text.replace(/\n/g, '<br>')
node.parentNode?.replaceChild(span, node)
}
} else if (node.nodeType === Node.ELEMENT_NODE) {
// 递归处理子节点
Array.from(node.childNodes).forEach(processTextNodes)
}
}
processTextNodes(tempDiv)
previewHTML = tempDiv.innerHTML
// 构建完整的HTML文档使用与预览页面一致的样式
const fullHTML = `
<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:w='urn:schemas-microsoft-com:office:word'>
<head>
<meta charset="utf-8">
<style>
body {
font-family: 'Microsoft YaHei', '微软雅黑', SimSun, Arial, sans-serif;
font-size: 14px;
line-height: 1.6;
margin: 20px;
padding: 20px;
}
.basic-info-title{
font-family: '黑体', 'Microsoft YaHei', SimSun, Arial, sans-serif;
font-size: 21px;
font-weight: 700;
color: black;
text-align: center;
margin-bottom: 20px;
}
.basic-info-section {
padding: 15px 20px;
color: black;
font-size: 12px;
}
.basic-info-item{
margin-right: 30px;
}
.dimension-item {
padding: 0 40px;
font-size: 12px;
color: black;
}
.dimension-item-title {
font-size: 21px;
padding: 15px 0;
font-weight: 500;
color: black;
}
</style>
</head>
<body>
${previewHTML}
</body>
</html>
`
// 转换为Blob (asBlob返回Promise)
const converted = await asBlob(fullHTML)
// 下载文件
const fileName = `${'评估报告'}_${new Date().toLocaleDateString('zh-CN')}.docx`
saveAs(converted as Blob, fileName)
message.success('Word文档导出成功')
} catch (error) {
console.error('导出Word失败:', error)
message.error('导出Word失败请重试')
} finally {
loading.value = false
}
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
.report-edit-container {
height: 100%;
overflow-y: auto;
background-color: #f5f7fa;
}
.drawer-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.basic-info-section {
padding: 15px 20px;
color: black;
font-size: 14px;
}
.basic-info-item{
margin-right: 30px;
}
.dimension-item {
padding: 0 40px;
font-size: 14px;
color: black;
}
.dimension-item-title {
font-size: 15px;
padding: 15px 0;
font-weight: 500;
color: black;
}
</style>