288 lines
9.2 KiB
Vue
288 lines
9.2 KiB
Vue
<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>
|