比对模式的检测报告生成和下载
This commit is contained in:
@@ -133,6 +133,10 @@ class RequestHttp {
|
|||||||
await router.replace(LOGIN_URL)
|
await router.replace(LOGIN_URL)
|
||||||
return Promise.reject(data)
|
return Promise.reject(data)
|
||||||
}
|
}
|
||||||
|
// 对于blob类型的响应,返回完整的response对象以保留响应头
|
||||||
|
if (response.config.responseType === 'blob') {
|
||||||
|
return response
|
||||||
|
}
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
async (error: AxiosError) => {
|
async (error: AxiosError) => {
|
||||||
@@ -171,6 +175,10 @@ class RequestHttp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
download(url: string, params?: object, _object = {}): Promise<BlobPart> {
|
download(url: string, params?: object, _object = {}): Promise<BlobPart> {
|
||||||
|
return this.service.post(url, params, { ..._object, responseType: 'blob' }).then(res => res.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadWithHeaders(url: string, params?: object, _object = {}): Promise<AxiosResponse<Blob>> {
|
||||||
return this.service.post(url, params, { ..._object, responseType: 'blob' })
|
return this.service.post(url, params, { ..._object, responseType: 'blob' })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +197,7 @@ class RequestHttp {
|
|||||||
..._object,
|
..._object,
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
})
|
}).then(res => res.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加SSE连接方法
|
// 添加SSE连接方法
|
||||||
|
|||||||
@@ -85,6 +85,11 @@ export const downloadDevData = (params: Device.ReqDevReportParams) => {
|
|||||||
return http.download(`/report/downloadReport`, params)
|
return http.download(`/report/downloadReport`, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 装置检测报告下载(带响应头)
|
||||||
|
export const downloadDevDataWithHeaders = (params: Device.ReqDevReportParams) => {
|
||||||
|
return http.downloadWithHeaders(`/report/downloadReport`, params)
|
||||||
|
}
|
||||||
|
|
||||||
export const staticsAnalyse = (params: { id: string[] }) => {
|
export const staticsAnalyse = (params: { id: string[] }) => {
|
||||||
return http.download('/adPlan/analyse', params)
|
return http.download('/adPlan/analyse', params)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ElNotification } from "element-plus";
|
import { ElNotification } from "element-plus";
|
||||||
|
import type { AxiosResponse } from "axios";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 接收数据流生成 blob,创建链接,下载文件
|
* @description 接收数据流生成 blob,创建链接,下载文件
|
||||||
@@ -8,6 +9,55 @@ import { ElNotification } from "element-plus";
|
|||||||
* @param {Boolean} isNotify 是否有导出消息提示 (默认为 true)
|
* @param {Boolean} isNotify 是否有导出消息提示 (默认为 true)
|
||||||
* @param {String} fileType 导出的文件格式 (默认为.xlsx)
|
* @param {String} fileType 导出的文件格式 (默认为.xlsx)
|
||||||
* */
|
* */
|
||||||
|
/**
|
||||||
|
* 从 Content-Disposition 头解析文件名
|
||||||
|
*/
|
||||||
|
const getFileNameFromContentDisposition = (contentDisposition: string | undefined | null): string | null => {
|
||||||
|
if (!contentDisposition) return null;
|
||||||
|
|
||||||
|
// 优先匹配 filename*=UTF-8'' 格式(RFC 5987)
|
||||||
|
const filenameStarRegex = /filename\*\s*=\s*UTF-8''([^;]+)/i;
|
||||||
|
const starMatches = filenameStarRegex.exec(contentDisposition);
|
||||||
|
|
||||||
|
if (starMatches && starMatches[1]) {
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(starMatches[1]);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('解码 filename* 失败:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其次匹配 filename="文件名" 或 filename=文件名 格式
|
||||||
|
const filenameRegex = /filename\s*=\s*([^;]+)/i;
|
||||||
|
const matches = filenameRegex.exec(contentDisposition);
|
||||||
|
|
||||||
|
if (matches && matches[1]) {
|
||||||
|
let filename = matches[1].trim();
|
||||||
|
|
||||||
|
// 去除引号
|
||||||
|
filename = filename.replace(/^["']|["']$/g, "");
|
||||||
|
|
||||||
|
// 尝试解码 URL 编码的文件名
|
||||||
|
try {
|
||||||
|
// 检查是否包含 URL 编码字符
|
||||||
|
if (filename.includes('%') || filename.includes('UTF-8')) {
|
||||||
|
// 处理可能的 UTF-8 前缀
|
||||||
|
if (filename.startsWith("UTF-8''") || filename.startsWith("utf-8''")) {
|
||||||
|
filename = filename.substring(7);
|
||||||
|
}
|
||||||
|
filename = decodeURIComponent(filename);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 如果解码失败,返回原始文件名
|
||||||
|
console.warn('解码文件名失败,使用原始值:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export const useDownload = async (
|
export const useDownload = async (
|
||||||
api: (param: any) => Promise<any>,
|
api: (param: any) => Promise<any>,
|
||||||
tempName: string,
|
tempName: string,
|
||||||
@@ -39,6 +89,73 @@ export const useDownload = async (
|
|||||||
document.body.removeChild(exportFile);
|
document.body.removeChild(exportFile);
|
||||||
window.URL.revokeObjectURL(blobUrl);
|
window.URL.revokeObjectURL(blobUrl);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 支持服务器文件名的下载方法,会从 Content-Disposition 头获取文件名
|
||||||
|
* @param {Function} api 导出表格的api方法 (必传)
|
||||||
|
* @param {String} fallbackName 备用文件名,当服务器未提供时使用 (可选)
|
||||||
|
* @param {Object} params 导出的参数 (默认{})
|
||||||
|
* @param {Boolean} isNotify 是否有导出消息提示 (默认为 true)
|
||||||
|
* @param {String} fallbackFileType 备用文件格式,当服务器未提供时使用 (默认为.xlsx)
|
||||||
|
*/
|
||||||
|
export const useDownloadWithServerFileName = async (
|
||||||
|
api: (param: any) => Promise<AxiosResponse<Blob> | any>,
|
||||||
|
fallbackName: string = "",
|
||||||
|
params: any = {},
|
||||||
|
isNotify: boolean = true,
|
||||||
|
fallbackFileType: string = ".xlsx"
|
||||||
|
) => {
|
||||||
|
if (isNotify) {
|
||||||
|
ElNotification({
|
||||||
|
title: "温馨提示",
|
||||||
|
message: "如果数据庞大会导致下载缓慢哦,请您耐心等待!",
|
||||||
|
type: "info",
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await api(params);
|
||||||
|
|
||||||
|
// 检查响应是否包含 data 属性(AxiosResponse)还是直接是 Blob
|
||||||
|
const blob = res.data ? new Blob([res.data]) : new Blob([res]);
|
||||||
|
|
||||||
|
// 尝试从响应头获取文件名(如果存在)
|
||||||
|
let serverFileName: string | null = null;
|
||||||
|
if (res.headers) {
|
||||||
|
const headers = res.headers || {};
|
||||||
|
const contentDisposition = headers['content-disposition'] || headers['Content-Disposition'];
|
||||||
|
serverFileName = getFileNameFromContentDisposition(contentDisposition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确定最终使用的文件名
|
||||||
|
let finalFileName: string;
|
||||||
|
if (serverFileName) {
|
||||||
|
finalFileName = serverFileName;
|
||||||
|
} else if (fallbackName) {
|
||||||
|
finalFileName = `${fallbackName}${fallbackFileType}`;
|
||||||
|
} else {
|
||||||
|
finalFileName = `download${fallbackFileType}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容 edge 不支持 createObjectURL 方法
|
||||||
|
if ("msSaveOrOpenBlob" in navigator) {
|
||||||
|
return window.navigator.msSaveOrOpenBlob(blob, finalFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blobUrl = window.URL.createObjectURL(blob);
|
||||||
|
const exportFile = document.createElement("a");
|
||||||
|
exportFile.style.display = "none";
|
||||||
|
exportFile.download = finalFileName;
|
||||||
|
exportFile.href = blobUrl;
|
||||||
|
document.body.appendChild(exportFile);
|
||||||
|
exportFile.click();
|
||||||
|
// 去除下载对 url 的影响
|
||||||
|
document.body.removeChild(exportFile);
|
||||||
|
window.URL.revokeObjectURL(blobUrl);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('文件下载失败:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
</el-tabs>
|
</el-tabs>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button type="primary" size="small" @click="handleChooseClick">重新选择</el-button>
|
<el-button type="primary" size="small" @click="handleChooseClick">重新选择</el-button>
|
||||||
<el-button type="primary">确认生成</el-button>
|
<el-button type="primary" @click="handleConfirmGenerate">确认生成</el-button>
|
||||||
</template>
|
</template>
|
||||||
<!-- 选择检测数据源弹框-->
|
<!-- 选择检测数据源弹框-->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
@@ -157,11 +157,20 @@
|
|||||||
<script setup lang="ts" name="reportPopup">
|
<script setup lang="ts" name="reportPopup">
|
||||||
import { getMonitorDataSourceResult, getMonitorResult, updateMonitorResult } from '@/api/result/result'
|
import { getMonitorDataSourceResult, getMonitorResult, updateMonitorResult } from '@/api/result/result'
|
||||||
import { type MonitorResult } from '@/api/result/interface'
|
import { type MonitorResult } from '@/api/result/interface'
|
||||||
|
import { generateDevReport } from '@/api/plan/plan'
|
||||||
|
import { useCheckStore } from '@/stores/modules/check'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const dialogSourceVisible = ref(false)
|
const dialogSourceVisible = ref(false)
|
||||||
const devData = ref<any>()
|
const devData = ref<any>()
|
||||||
const activeName = ref<number>(0)
|
const activeName = ref<number>(0)
|
||||||
|
const checkStore = useCheckStore()
|
||||||
|
|
||||||
|
// 定义 emit 事件
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'reportGenerated'): void
|
||||||
|
}>()
|
||||||
const resultData = ref<MonitorResult[]>([])
|
const resultData = ref<MonitorResult[]>([])
|
||||||
const resultSourceData = ref<any>({})
|
const resultSourceData = ref<any>({})
|
||||||
const whichTimeData = ref<any>([])
|
const whichTimeData = ref<any>([])
|
||||||
@@ -228,6 +237,26 @@ const handleSureChoose = () => {
|
|||||||
dialogSourceVisible.value = false
|
dialogSourceVisible.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理确认生成报告
|
||||||
|
const handleConfirmGenerate = async () => {
|
||||||
|
try {
|
||||||
|
await generateDevReport({
|
||||||
|
planId: checkStore.plan.id,
|
||||||
|
devIdList: [devData.value.id],
|
||||||
|
scriptId: checkStore.plan.scriptId,
|
||||||
|
planCode: checkStore.plan.code + '',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 999
|
||||||
|
})
|
||||||
|
ElMessage.success({ message: `报告生成成功!` })
|
||||||
|
dialogVisible.value = false
|
||||||
|
emit('reportGenerated') // 触发事件通知父组件
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('报告生成失败')
|
||||||
|
console.error('报告生成错误:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
defineExpose({
|
defineExpose({
|
||||||
open
|
open
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -195,7 +195,7 @@
|
|||||||
:append-to-body="true"
|
:append-to-body="true"
|
||||||
/>
|
/>
|
||||||
<!-- 报告生成弹框 -->
|
<!-- 报告生成弹框 -->
|
||||||
<ReportResultPopup ref="reportPopup"></ReportResultPopup>
|
<ReportResultPopup ref="reportPopup" @reportGenerated="handleReportGenerated"></ReportResultPopup>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -214,13 +214,13 @@ import { type Device } from '@/api/device/interface/device'
|
|||||||
import { type ColumnProps, type ProTableInstance } from '@/components/ProTable/interface'
|
import { type ColumnProps, type ProTableInstance } from '@/components/ProTable/interface'
|
||||||
import { type Plan } from '@/api/plan/interface'
|
import { type Plan } from '@/api/plan/interface'
|
||||||
import { type StandardDevice } from '@/api/device/interface/standardDevice'
|
import { type StandardDevice } from '@/api/device/interface/standardDevice'
|
||||||
import { downloadDevData, generateDevReport, getBoundPqDevList } from '@/api/plan/plan'
|
import { downloadDevData, downloadDevDataWithHeaders, generateDevReport, getBoundPqDevList } from '@/api/plan/plan'
|
||||||
import { getPqDev } from '@/api/device/device'
|
import { getPqDev } from '@/api/device/device'
|
||||||
import { useAppSceneStore, useModeStore } from '@/stores/modules/mode' // 引入模式 store
|
import { useAppSceneStore, useModeStore } from '@/stores/modules/mode' // 引入模式 store
|
||||||
import { useCheckStore } from '@/stores/modules/check'
|
import { useCheckStore } from '@/stores/modules/check'
|
||||||
import { CheckData } from '@/api/check/interface'
|
import { CheckData } from '@/api/check/interface'
|
||||||
import { useAuthStore } from '@/stores/modules/auth'
|
import { useAuthStore } from '@/stores/modules/auth'
|
||||||
import { useDownload } from '@/hooks/useDownload'
|
import { useDownload, useDownloadWithServerFileName } from '@/hooks/useDownload'
|
||||||
import { documentedPqDev } from '@/api/device/report'
|
import { documentedPqDev } from '@/api/device/report'
|
||||||
import { ResultEnum } from '@/enums/httpEnum'
|
import { ResultEnum } from '@/enums/httpEnum'
|
||||||
import { getPqMonList } from '@/api/device/monitor/index.ts'
|
import { getPqMonList } from '@/api/device/monitor/index.ts'
|
||||||
@@ -1033,6 +1033,7 @@ const openDrawer = async (title: string, row: any) => {
|
|||||||
// 单个设备报告生成
|
// 单个设备报告生成
|
||||||
if (title === '报告生成') {
|
if (title === '报告生成') {
|
||||||
if (modeStore.currentMode == '比对式'){
|
if (modeStore.currentMode == '比对式'){
|
||||||
|
console.log(row)
|
||||||
reportPopup.value?.open(row)
|
reportPopup.value?.open(row)
|
||||||
}else{
|
}else{
|
||||||
await generateDevReport({
|
await generateDevReport({
|
||||||
@@ -1049,9 +1050,9 @@ const openDrawer = async (title: string, row: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (title === '报告下载') {
|
if (title === '报告下载') {
|
||||||
await useDownload(
|
await useDownloadWithServerFileName(
|
||||||
downloadDevData,
|
downloadDevDataWithHeaders,
|
||||||
row.createId,
|
row.createId, // 备用文件名
|
||||||
{
|
{
|
||||||
planId: checkStore.plan.id,
|
planId: checkStore.plan.id,
|
||||||
devId: row.id
|
devId: row.id
|
||||||
@@ -1121,6 +1122,11 @@ const handleQuitClicked = () => {
|
|||||||
emit('batchGenerateClicked') // 触发事件
|
emit('batchGenerateClicked') // 触发事件
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理报告生成完成事件
|
||||||
|
const handleReportGenerated = () => {
|
||||||
|
emit('batchGenerateClicked') // 触发事件通知父组件刷新数据
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({ changeActiveTabs })
|
defineExpose({ changeActiveTabs })
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user