2025-03-31 14:33:05 +08:00
|
|
|
|
<template>
|
2025-08-26 15:39:20 +08:00
|
|
|
|
<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"
|
2025-09-04 09:08:45 +08:00
|
|
|
|
:highlight-current = 'true'
|
2025-08-26 15:39:20 +08:00
|
|
|
|
:current-node-key="currentTreeKey"
|
2025-09-04 09:08:45 +08:00
|
|
|
|
:expand-on-click-node="false"
|
2025-08-26 15:39:20 +08:00
|
|
|
|
></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">
|
2025-08-26 11:07:11 +08:00
|
|
|
|
<el-icon class="is-loading"><Loading /></el-icon>
|
|
|
|
|
|
<span class="loading-text">加载中...</span>
|
|
|
|
|
|
</div>
|
2025-08-27 09:22:55 +08:00
|
|
|
|
<information
|
|
|
|
|
|
v-show="!infoLoading"
|
|
|
|
|
|
:node-id="currentNodeId"
|
|
|
|
|
|
@data-loaded="() => infoLoading = false"
|
|
|
|
|
|
/>
|
2025-08-26 15:39:20 +08:00
|
|
|
|
</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>
|
2025-08-27 09:22:55 +08:00
|
|
|
|
<Outcome
|
|
|
|
|
|
v-show="!outcomeLoading"
|
|
|
|
|
|
:node-id="currentNodeId"
|
|
|
|
|
|
@data-status="handleDataStatus"
|
|
|
|
|
|
@data-loaded="() => outcomeLoading = false"
|
|
|
|
|
|
|
|
|
|
|
|
/>
|
2025-08-26 15:39:20 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</el-collapse-item>
|
|
|
|
|
|
</el-collapse>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</pane>
|
|
|
|
|
|
</splitpanes>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<AssessTemplate ref="assessTemplate" @data-success = "assess"></AssessTemplate>
|
|
|
|
|
|
|
2025-03-31 14:33:05 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-08-26 11:07:11 +08:00
|
|
|
|
import { ElMessage, ElLoading } from 'element-plus'
|
|
|
|
|
|
import { onMounted, ref, provide ,nextTick } from 'vue'
|
2025-03-31 14:33:05 +08:00
|
|
|
|
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'
|
2025-08-27 09:22:55 +08:00
|
|
|
|
import {exportResult,downloadAssessTemplate,assessResult,userGetInfo} from '@/api/advance-boot/assess'
|
2025-08-26 11:07:11 +08:00
|
|
|
|
import { Loading } from '@element-plus/icons-vue'
|
2025-08-26 15:39:20 +08:00
|
|
|
|
import AssessTemplate from './assessTemplate.vue'
|
2025-09-04 09:08:45 +08:00
|
|
|
|
import { fa, tr } from 'element-plus/es/locale'
|
2025-08-26 15:39:20 +08:00
|
|
|
|
|
2025-03-31 14:33:05 +08:00
|
|
|
|
|
|
|
|
|
|
defineOptions({
|
2025-08-26 15:39:20 +08:00
|
|
|
|
// name: 'harmonic-boot/report/word'
|
2025-03-31 14:33:05 +08:00
|
|
|
|
})
|
|
|
|
|
|
const collapseName = ref([1, 2])
|
|
|
|
|
|
const height = mainHeight(80)
|
|
|
|
|
|
const collapseHeight = mainHeight(125)
|
|
|
|
|
|
const size = ref(0)
|
|
|
|
|
|
const dotList: any = ref({})
|
2025-08-26 11:07:11 +08:00
|
|
|
|
const infoLoading = ref(false)
|
|
|
|
|
|
const outcomeLoading = ref(false)
|
2025-08-26 15:39:20 +08:00
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const assessTemplate = ref()
|
2025-08-26 11:07:11 +08:00
|
|
|
|
// 添加当前节点 ID 的响应式变量
|
|
|
|
|
|
const currentNodeId = ref<string | number | null>(null)
|
2025-08-26 15:39:20 +08:00
|
|
|
|
const currentNodeName = ref('')
|
2025-03-31 14:33:05 +08:00
|
|
|
|
|
2025-08-27 09:22:55 +08:00
|
|
|
|
|
2025-03-31 14:33:05 +08:00
|
|
|
|
const tableStore = new TableStore({
|
2025-08-26 15:39:20 +08:00
|
|
|
|
url: '',
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
column: [],
|
|
|
|
|
|
beforeSearchFun: () => {},
|
|
|
|
|
|
loadCallback: () => {}
|
2025-03-31 14:33:05 +08:00
|
|
|
|
})
|
|
|
|
|
|
provide('tableStore', tableStore)
|
2025-08-26 11:07:11 +08:00
|
|
|
|
|
|
|
|
|
|
//有无评估结果
|
|
|
|
|
|
const result = ref(true)
|
|
|
|
|
|
const handleDataStatus = (hasData: boolean) => {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
if (!hasData) {
|
|
|
|
|
|
result.value = false
|
|
|
|
|
|
} else {
|
|
|
|
|
|
result.value = true
|
|
|
|
|
|
}
|
2025-08-26 11:07:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-26 15:39:20 +08:00
|
|
|
|
//模板下载
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
2025-08-26 11:07:11 +08:00
|
|
|
|
|
2025-08-27 09:22:55 +08:00
|
|
|
|
//导出
|
2025-08-26 11:07:11 +08:00
|
|
|
|
const exportReport = () => {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
if (!result.value){
|
|
|
|
|
|
ElMessage.warning('请先评估!')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-03 19:30:29 +08:00
|
|
|
|
const loadingMessage = ElMessage({
|
|
|
|
|
|
message: '正在导出中',
|
|
|
|
|
|
icon: h(Loading),
|
|
|
|
|
|
duration: 0 // 不自动关闭
|
2025-08-26 15:39:20 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2025-09-04 09:08:45 +08:00
|
|
|
|
exportResult({}, currentNodeId.value ?? '').then((res: any) => {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭加载提示并显示成功消息
|
2025-09-03 19:30:29 +08:00
|
|
|
|
loadingMessage.close()
|
2025-08-26 15:39:20 +08:00
|
|
|
|
ElMessage.success('导出成功!')
|
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
|
// 处理导出失败情况
|
2025-09-03 19:30:29 +08:00
|
|
|
|
loadingMessage.close()
|
2025-08-26 15:39:20 +08:00
|
|
|
|
ElMessage.error('导出失败,请重试!')
|
|
|
|
|
|
})
|
2025-08-26 11:07:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-27 09:22:55 +08:00
|
|
|
|
|
|
|
|
|
|
//导入
|
2025-08-26 11:07:11 +08:00
|
|
|
|
const handleImportClick = () => {
|
2025-08-27 09:22:55 +08:00
|
|
|
|
if (!currentNodeId.value) {
|
|
|
|
|
|
ElMessage.warning('请选择评估节点!')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
assessTemplate.value.open('导入背景数据', currentNodeId.value.toString())
|
2025-03-31 14:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-26 15:39:20 +08:00
|
|
|
|
|
2025-08-27 09:22:55 +08:00
|
|
|
|
// 导入弹窗关闭后通知父组件更新
|
2025-08-26 11:07:11 +08:00
|
|
|
|
const assess = async () => {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
// 展开评估结果
|
|
|
|
|
|
collapseName.value = [2]
|
|
|
|
|
|
// 更新评估状态
|
|
|
|
|
|
result.value = true
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 强制更新 nodeId,触发子组件的 watch
|
|
|
|
|
|
// 可以通过修改 currentNodeId 的值,或者重新赋值来触发 watch
|
|
|
|
|
|
const tempId = currentNodeId.value
|
|
|
|
|
|
currentNodeId.value = null // 先置空
|
2025-08-27 09:22:55 +08:00
|
|
|
|
|
2025-08-26 15:39:20 +08:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
currentNodeId.value = tempId // 再恢复,触发 watch
|
|
|
|
|
|
}, 0)
|
2025-08-26 11:07:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-26 15:39:20 +08:00
|
|
|
|
//评估
|
|
|
|
|
|
const assess2 = async () => {
|
2025-08-27 09:22:55 +08:00
|
|
|
|
// 显示导出中提示
|
2025-09-03 19:30:29 +08:00
|
|
|
|
const loading = ElMessage({
|
|
|
|
|
|
message: '评估结果中',
|
|
|
|
|
|
icon: h(Loading),
|
|
|
|
|
|
duration: 0 // 不自动关闭
|
|
|
|
|
|
})
|
2025-08-26 15:39:20 +08:00
|
|
|
|
|
2025-08-27 09:22:55 +08:00
|
|
|
|
userGetInfo({ assessId: currentNodeId.value }).then(res => {
|
|
|
|
|
|
// 获取昨天的日期
|
|
|
|
|
|
const yesterday = new Date();
|
|
|
|
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
2025-08-26 15:39:20 +08:00
|
|
|
|
|
2025-08-27 09:22:55 +08:00
|
|
|
|
// 格式化为 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}`;
|
2025-08-26 15:39:20 +08:00
|
|
|
|
|
2025-08-27 09:22:55 +08:00
|
|
|
|
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('获取用户信息失败');
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
2025-08-26 11:07:11 +08:00
|
|
|
|
|
2025-03-31 14:33:05 +08:00
|
|
|
|
onMounted(() => {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
const dom = document.getElementById('navigation-splitpanes')
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (dom) {
|
2025-12-09 20:04:55 +08:00
|
|
|
|
size.value = Math.round((180 / dom.offsetHeight) * 120)
|
2025-08-26 15:39:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
}, 100)
|
2025-03-31 14:33:05 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2025-08-26 11:07:11 +08:00
|
|
|
|
// 添加树组件的引用
|
|
|
|
|
|
const assessTreeRef = ref<any>(null)
|
|
|
|
|
|
// 控制树组件当前高亮的节点key
|
|
|
|
|
|
const currentTreeKey = ref<string | number | null>(null)
|
|
|
|
|
|
// 保存上一次真正有效的节点key(子节点)
|
|
|
|
|
|
const lastValidNodeKey = ref<string | number | null>(null)
|
|
|
|
|
|
|
2025-03-31 14:33:05 +08:00
|
|
|
|
const handleNodeClick = (data: any, node: any) => {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
// 阻止点击父节点的逻辑处理
|
|
|
|
|
|
if (!data ||
|
|
|
|
|
|
(node && !node.isLeaf) ||
|
|
|
|
|
|
(data.children && data.children.length > 0) ||
|
|
|
|
|
|
(node && node.childNodes && node.childNodes.length > 0)) {
|
2025-08-26 11:07:11 +08:00
|
|
|
|
|
2025-08-26 15:39:20 +08:00
|
|
|
|
// 点击父节点时,保持树的高亮状态为上一次有效的子节点
|
2025-08-26 11:07:11 +08:00
|
|
|
|
currentTreeKey.value = lastValidNodeKey.value
|
2025-08-26 15:39:20 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-08-26 11:07:11 +08:00
|
|
|
|
|
2025-09-03 18:26:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果点击的是同一个节点,不需要重新加载,但可以提供刷新功能
|
|
|
|
|
|
if (currentNodeId.value === data?.id) {
|
|
|
|
|
|
// 可选:如果你希望重复点击能刷新数据,可以保留下面的代码
|
|
|
|
|
|
// 否则可以直接 return 不做任何操作
|
|
|
|
|
|
infoLoading.value = true
|
|
|
|
|
|
outcomeLoading.value = true
|
|
|
|
|
|
// 强制触发子组件重新加载,通过先置空再恢复
|
|
|
|
|
|
const tempId = currentNodeId.value
|
|
|
|
|
|
currentNodeId.value = null
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
currentNodeId.value = tempId
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 点击的是不同的子节点,更新高亮状态
|
2025-08-26 15:39:20 +08:00
|
|
|
|
lastValidNodeKey.value = data?.id || null
|
|
|
|
|
|
currentTreeKey.value = lastValidNodeKey.value
|
|
|
|
|
|
dotList.value = data
|
|
|
|
|
|
currentNodeId.value = data?.id || null
|
|
|
|
|
|
currentNodeName.value = data?.name || ''
|
2025-08-26 11:07:11 +08:00
|
|
|
|
|
2025-08-26 15:39:20 +08:00
|
|
|
|
// ✅ 开启两个 loading
|
|
|
|
|
|
infoLoading.value = true
|
|
|
|
|
|
outcomeLoading.value = true
|
2025-03-31 14:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.splitpanes.default-theme .splitpanes__pane {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
background: #eaeef1;
|
2025-03-31 14:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-collapse-item__header) {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
// font-family: AlimamaDongFangDaKai;
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
&::before {
|
|
|
|
|
|
content: '▍'; /* 添加星号 */
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: var(--el-color-primary);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
2025-03-31 14:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.actionButtons {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: end;
|
2025-03-31 14:33:05 +08:00
|
|
|
|
}
|
2025-08-26 11:07:11 +08:00
|
|
|
|
|
|
|
|
|
|
.loading-container {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
position: relative;
|
|
|
|
|
|
min-height: 100px; /* 防止空白时高度塌陷 */
|
2025-08-26 11:07:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-wrapper {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
padding: 20px;
|
2025-08-26 11:07:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-text {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
margin-left: 8px;
|
|
|
|
|
|
color: #606266;
|
2025-08-26 11:07:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.is-loading {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
animation: rotating 2s linear infinite;
|
2025-08-26 11:07:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes rotating {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
0% {
|
|
|
|
|
|
transform: rotate(0deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
|
|
|
transform: rotate(360deg);
|
|
|
|
|
|
}
|
2025-08-26 11:07:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ml10 {
|
2025-08-26 15:39:20 +08:00
|
|
|
|
margin-left: 10px;
|
2025-08-26 11:07:11 +08:00
|
|
|
|
}
|
2025-03-31 14:33:05 +08:00
|
|
|
|
</style>
|