Files
admin-sjzx/src/views/pqs/runManage/assessment/components/assess/index.vue
2025-12-12 09:26:56 +08:00

366 lines
12 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>
<div :style="height">
<splitpanes style="height: 100%" class="default-theme" id="navigation-splitpanes">
<pane :size="size">
<AssessTree
:default-expand-all="false"
@node-click="handleNodeClick"
@init="handleNodeClick"
ref="assessTreeRef"
:highlight-current="true"
:current-node-key="currentTreeKey"
:expand-on-click-node="false"
></AssessTree>
</pane>
<pane style="background: #fff">
<div class="actionButtons mb10">
<el-button icon="el-icon-Download" type="primary" @click="exportExcelTemplate" :loading="loading">
模版下载
</el-button>
<el-button type="primary" icon="el-icon-Upload" @click="handleImportClick">导入数据背景</el-button>
<el-button type="primary" icon="el-icon-Memo" class="ml10" @click="assess2">评估</el-button>
<el-button type="primary" icon="el-icon-Download" @click="exportReport">导出报告</el-button>
</div>
<div :style="collapseHeight" style="overflow-y: auto">
<el-collapse v-model="collapseName" class="pl10 pr10">
<el-collapse-item title="基本信息" :name="1">
<div class="loading-container">
<div v-if="infoLoading" class="loading-wrapper">
<el-icon class="is-loading"><Loading /></el-icon>
<span class="loading-text">加载中...</span>
</div>
<information
v-show="!infoLoading"
:node-id="currentNodeId"
@data-loaded="() => (infoLoading = false)"
/>
</div>
</el-collapse-item>
<el-collapse-item title="评估结果信息" :name="2">
<div class="loading-container">
<div v-if="outcomeLoading" class="loading-wrapper">
<el-icon class="is-loading"><Loading /></el-icon>
<span class="loading-text">加载中...</span>
</div>
<Outcome
v-show="!outcomeLoading"
:node-id="currentNodeId"
@data-status="handleDataStatus"
@data-loaded="() => (outcomeLoading = false)"
/>
</div>
</el-collapse-item>
</el-collapse>
</div>
</pane>
</splitpanes>
</div>
<AssessTemplate ref="assessTemplate" @data-success="assess"></AssessTemplate>
</template>
<script setup lang="ts">
import { ElMessage, ElLoading } from 'element-plus'
import { onMounted, ref, provide, nextTick } from 'vue'
import 'splitpanes/dist/splitpanes.css'
import { Splitpanes, Pane } from 'splitpanes'
import TableStore from '@/utils/tableStore'
import AssessTree from '@/components/tree/pqs/assessTree.vue'
import TableHeader from '@/components/table/header/index.vue'
import { mainHeight } from '@/utils/layout'
import information from './information.vue'
import Outcome from './outcome.vue'
import { exportResult, downloadAssessTemplate, assessResult, userGetInfo } from '@/api/advance-boot/assess'
import { Loading } from '@element-plus/icons-vue'
import AssessTemplate from './assessTemplate.vue'
import { fa, tr } from 'element-plus/es/locale'
defineOptions({
// name: 'harmonic-boot/report/word'
})
const collapseName = ref([1, 2])
const height = mainHeight(80)
const collapseHeight = mainHeight(125)
const size = ref(23)
const dotList: any = ref({})
const infoLoading = ref(false)
const outcomeLoading = ref(false)
const loading = ref(false)
const assessTemplate = ref()
// 添加当前节点 ID 的响应式变量
const currentNodeId = ref<string | number | null>(null)
const currentNodeName = ref('')
const tableStore = new TableStore({
url: '',
method: 'POST',
column: [],
beforeSearchFun: () => {},
loadCallback: () => {}
})
provide('tableStore', tableStore)
//有无评估结果
const result = ref(true)
const handleDataStatus = (hasData: boolean) => {
if (!hasData) {
result.value = false
} else {
result.value = true
}
}
//模板下载
const exportExcelTemplate = async () => {
loading.value = true
downloadAssessTemplate().then((res: any) => {
let blob = new Blob([res], {
type: 'application/vnd.ms-excel'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '评估结果模板'
document.body.appendChild(link)
link.click()
link.remove()
})
await setTimeout(() => {
loading.value = false
}, 0)
}
//导出
const exportReport = () => {
if (!result.value) {
ElMessage.warning('请先评估!')
return
}
const loadingMessage = ElMessage({
message: '正在导出中',
icon: h(Loading),
duration: 0 // 不自动关闭
})
exportResult({}, currentNodeId.value ?? '')
.then((res: any) => {
let blob = new Blob([res], {
// 注意使用 res.data 而不是 res
type: 'application/vnd.ms-excel'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = currentNodeName.value + '-评估结果.xls' // 建议添加文件扩展名
document.body.appendChild(link)
link.click()
link.remove()
window.URL.revokeObjectURL(url)
// 关闭加载提示并显示成功消息
loadingMessage.close()
ElMessage.success('导出成功!')
})
.catch(error => {
// 处理导出失败情况
loadingMessage.close()
ElMessage.error('导出失败,请重试!')
})
}
//导入
const handleImportClick = () => {
if (!currentNodeId.value) {
ElMessage.warning('请选择评估节点!')
return
}
assessTemplate.value.open('导入背景数据', currentNodeId.value.toString())
}
// 导入弹窗关闭后通知父组件更新
const assess = async () => {
// 展开评估结果
collapseName.value = [2]
// 更新评估状态
result.value = true
// ✅ 强制更新 nodeId触发子组件的 watch
// 可以通过修改 currentNodeId 的值,或者重新赋值来触发 watch
const tempId = currentNodeId.value
currentNodeId.value = null // 先置空
setTimeout(() => {
currentNodeId.value = tempId // 再恢复,触发 watch
}, 0)
}
//评估
const assess2 = async () => {
// 显示导出中提示
const loading = ElMessage({
message: '评估结果中',
icon: h(Loading),
duration: 0 // 不自动关闭
})
userGetInfo({ assessId: currentNodeId.value })
.then(res => {
// 获取昨天的日期
const yesterday = new Date()
yesterday.setDate(yesterday.getDate() - 1)
// 格式化为 yyyy-MM-dd
const yyyy = yesterday.getFullYear()
const mm = String(yesterday.getMonth() + 1).padStart(2, '0')
const dd = String(yesterday.getDate()).padStart(2, '0')
const yesterdayStr = `${yyyy}-${mm}-${dd}`
const assessData = {
assessId: currentNodeId.value,
endTime: `${yesterdayStr} 23:59:59`, // 使用动态的昨天日期
id: res.data.lineId,
startTime: `${yesterdayStr} 00:00:00` // 使用动态的昨天日期
}
assessResult(assessData)
.then(res => {
// 关闭加载提示并显示成功消息
loading.close()
ElMessage.success('评估成功!')
// 展开评估结果
collapseName.value = [2]
// 更新评估状态
result.value = true
const tempId = currentNodeId.value
currentNodeId.value = null // 先置空
setTimeout(() => {
currentNodeId.value = tempId // 再恢复,触发 watch
}, 0)
})
.catch(error => {
// 关闭加载提示并显示失败消息
loading.close()
ElMessage.error('评估失败')
})
})
.catch(error => {
// 处理 userGetInfo 的错误
loading.close()
ElMessage.error('获取用户信息失败')
})
}
onMounted(() => {
const dom = document.getElementById('navigation-splitpanes')
setTimeout(() => {
if (dom) {
size.value = Math.round((180 / dom.offsetHeight) * 120)
}
}, 100)
})
// 添加树组件的引用
const assessTreeRef = ref<any>(null)
// 控制树组件当前高亮的节点key
const currentTreeKey = ref<string | number | null>(null)
// 保存上一次真正有效的节点key子节点
const lastValidNodeKey = ref<string | number | null>(null)
const handleNodeClick = (data: any, node: any) => {
// 阻止点击父节点的逻辑处理
if (
!data ||
(node && !node.isLeaf) ||
(data.children && data.children.length > 0) ||
(node && node.childNodes && node.childNodes.length > 0)
) {
// 点击父节点时,保持树的高亮状态为上一次有效的子节点
currentTreeKey.value = lastValidNodeKey.value
return
}
// 如果点击的是同一个节点,不需要重新加载,但可以提供刷新功能
if (currentNodeId.value === data?.id) {
// 可选:如果你希望重复点击能刷新数据,可以保留下面的代码
// 否则可以直接 return 不做任何操作
infoLoading.value = true
outcomeLoading.value = true
// 强制触发子组件重新加载,通过先置空再恢复
const tempId = currentNodeId.value
currentNodeId.value = null
nextTick(() => {
currentNodeId.value = tempId
})
return
}
// 点击的是不同的子节点,更新高亮状态
lastValidNodeKey.value = data?.id || null
currentTreeKey.value = lastValidNodeKey.value
dotList.value = data
currentNodeId.value = data?.id || null
currentNodeName.value = data?.name || ''
// ✅ 开启两个 loading
infoLoading.value = true
outcomeLoading.value = true
}
</script>
<style lang="scss" scoped>
.splitpanes.default-theme .splitpanes__pane {
background: #eaeef1;
}
:deep(.el-collapse-item__header) {
// font-family: AlimamaDongFangDaKai;
font-size: 20px;
&::before {
content: '▍'; /* 添加星号 */
font-size: 16px;
color: var(--el-color-primary);
}
}
.actionButtons {
display: flex;
justify-content: end;
}
.loading-container {
position: relative;
min-height: 100px; /* 防止空白时高度塌陷 */
}
.loading-wrapper {
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.loading-text {
margin-left: 8px;
color: #606266;
}
.is-loading {
animation: rotating 2s linear infinite;
}
@keyframes rotating {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.ml10 {
margin-left: 10px;
}
</style>