feat(aireport): 新增多模板批任务功能
- 在动态路由中添加 reportTaskMulti 和 testReportMulti 的路径映射 - 新增 TestReportMulti API 接口定义,包括批任务记录、列表参数、模板目录等接口类型 - 实现批任务多模板相关 API 方法,包含列表查询、压缩包验证、创建、详情获取等功能 - 创建 testReportMulti 页面组件,实现批任务管理界面和交互逻辑 - 开发批任务创建对话框组件,支持压缩包上传和子报告配置功能
This commit is contained in:
40
frontend/src/api/aireport/testReportMulti/index.ts
Normal file
40
frontend/src/api/aireport/testReportMulti/index.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import http from '@/api'
|
||||
import type { AxiosResponse } from 'axios'
|
||||
import type { ReportTaskMulti } from './interface'
|
||||
|
||||
const REPORT_TASK_MULTI_BASE_URL = '/api/test-report-multi'
|
||||
|
||||
export const listReportTaskMultisApi = (params: ReportTaskMulti.ReportTaskMultiListParams) => {
|
||||
return http.post(`${REPORT_TASK_MULTI_BASE_URL}/list`, params)
|
||||
}
|
||||
|
||||
export const validateReportTaskMultiZipApi = (formData: FormData) => {
|
||||
return http.post<ReportTaskMulti.ReportTaskMultiValidateResult>(`${REPORT_TASK_MULTI_BASE_URL}/zip/validate`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
}
|
||||
|
||||
export const createReportTaskMultiApi = (params: ReportTaskMulti.ReportTaskMultiCreateParam) => {
|
||||
return http.post<boolean>(`${REPORT_TASK_MULTI_BASE_URL}/create`, params)
|
||||
}
|
||||
|
||||
export const getReportTaskMultiDetailApi = (id: string) => {
|
||||
return http.get<ReportTaskMulti.ReportTaskMultiRecord>(`${REPORT_TASK_MULTI_BASE_URL}/${id}`)
|
||||
}
|
||||
|
||||
export const listReportTaskMultiChildReportsApi = (id: string) => {
|
||||
return http.get(`${REPORT_TASK_MULTI_BASE_URL}/${id}/child-report/list`)
|
||||
}
|
||||
|
||||
export const downloadReportTaskMultiApi = (id: string): Promise<AxiosResponse<Blob>> =>
|
||||
http.get(`${REPORT_TASK_MULTI_BASE_URL}/${id}/download`, undefined, {
|
||||
responseType: 'blob'
|
||||
}) as unknown as Promise<AxiosResponse<Blob>>
|
||||
|
||||
export const deleteReportTaskMultisApi = (ids: string[]) => {
|
||||
return http.post<boolean>(`${REPORT_TASK_MULTI_BASE_URL}/delete`, ids)
|
||||
}
|
||||
|
||||
export const generateReportTaskMultiApi = (id: string) => {
|
||||
return http.post<boolean>(`${REPORT_TASK_MULTI_BASE_URL}/${id}/generate`)
|
||||
}
|
||||
72
frontend/src/api/aireport/testReportMulti/interface/index.ts
Normal file
72
frontend/src/api/aireport/testReportMulti/interface/index.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
export namespace ReportTaskMulti {
|
||||
export interface ReportTaskMultiRecord {
|
||||
id?: string
|
||||
no?: string | null
|
||||
zipFileName?: string | null
|
||||
summaryFileName?: string | null
|
||||
dirCount?: number | null
|
||||
childReportCount?: number | null
|
||||
pointCount?: number | null
|
||||
importState?: number | null
|
||||
generateState?: number | null
|
||||
status?: number | null
|
||||
createBy?: string | null
|
||||
createByName?: string | null
|
||||
createTime?: string | null
|
||||
updateBy?: string | null
|
||||
updateByName?: string | null
|
||||
updateTime?: string | null
|
||||
}
|
||||
|
||||
export interface ReportTaskMultiListParams {
|
||||
keyword?: string
|
||||
searchBeginTime?: string
|
||||
searchEndTime?: string
|
||||
timeRange?: string[]
|
||||
generateState?: number
|
||||
pageNum?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export interface ReportTaskMultiTemplateDirRecord {
|
||||
dirName?: string | null
|
||||
dirPath?: string | null
|
||||
pointCount?: number | null
|
||||
groupCount?: number | null
|
||||
excelAttachmentCount?: number | null
|
||||
wordAttachmentCount?: number | null
|
||||
}
|
||||
|
||||
export interface ReportTaskMultiValidateResult {
|
||||
success?: boolean | null
|
||||
sessionId?: string | null
|
||||
zipFileName?: string | null
|
||||
summaryFileName?: string | null
|
||||
dirCount?: number | null
|
||||
childReportCount?: number | null
|
||||
pointCount?: number | null
|
||||
excelAttachmentCount?: number | null
|
||||
wordAttachmentCount?: number | null
|
||||
orphanAttachmentPaths?: string[] | null
|
||||
failReasons?: string[] | null
|
||||
templateDirs?: ReportTaskMultiTemplateDirRecord[] | null
|
||||
}
|
||||
|
||||
export interface ReportTaskMultiChildConfig {
|
||||
templateDirName: string
|
||||
templateDirPath: string
|
||||
no: string
|
||||
clientUnitId: string
|
||||
reportModelId: string
|
||||
createUnit: string
|
||||
contractNumber: string
|
||||
standard: string[]
|
||||
phonenumber?: string
|
||||
}
|
||||
|
||||
export interface ReportTaskMultiCreateParam {
|
||||
no: string
|
||||
sessionId: string
|
||||
childReports: ReportTaskMultiChildConfig[]
|
||||
}
|
||||
}
|
||||
@@ -44,8 +44,12 @@ const COMPONENT_PATH_ALIASES: Record<string, string> = {
|
||||
// 报告菜单后端使用 aiReport,前端目录沿用既有 aireport 命名。
|
||||
'/aiReport/reportTask': '/aireport/testReport',
|
||||
'/aiReport/reportTask/index': '/aireport/testReport/index',
|
||||
'/aiReport/reportTaskMulti': '/aireport/testReportMulti',
|
||||
'/aiReport/reportTaskMulti/index': '/aireport/testReportMulti/index',
|
||||
'/aiReport/testReport': '/aireport/testReport',
|
||||
'/aiReport/testReport/index': '/aireport/testReport/index',
|
||||
'/aiReport/testReportMulti': '/aireport/testReportMulti',
|
||||
'/aiReport/testReportMulti/index': '/aireport/testReportMulti/index',
|
||||
'/aiReport/reportModel': '/aireport/reportModel',
|
||||
'/aiReport/reportModel/index': '/aireport/reportModel/index',
|
||||
'/aiReport/clientUnit': '/aireport/clientUnit',
|
||||
|
||||
@@ -0,0 +1,438 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visibleProxy"
|
||||
title="新增多模板批任务"
|
||||
width="1240px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<div class="create-dialog-body">
|
||||
<el-form :model="createForm" label-width="110px" class="create-base-form">
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :sm="12">
|
||||
<el-form-item label="批任务编号" required>
|
||||
<el-input v-model="createForm.no" maxlength="64" clearable placeholder="请输入批任务编号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12">
|
||||
<el-form-item label="压缩包文件" required>
|
||||
<el-upload
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
:file-list="zipFileList"
|
||||
accept=".zip"
|
||||
@change="handleZipChange"
|
||||
@remove="handleZipRemove"
|
||||
@exceed="handleZipExceed"
|
||||
>
|
||||
<el-button>选择 zip 压缩包</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<div class="validate-toolbar">
|
||||
<el-button type="primary" :loading="zipValidating" @click="handleValidateZip">校验压缩包</el-button>
|
||||
<div v-if="validateResult" class="validate-summary">
|
||||
<span>模板目录:{{ validateResult.dirCount || 0 }}</span>
|
||||
<span>子报告:{{ validateResult.childReportCount || 0 }}</span>
|
||||
<span>监测点:{{ validateResult.pointCount || 0 }}</span>
|
||||
<span>Excel:{{ validateResult.excelAttachmentCount || 0 }}</span>
|
||||
<span>Word:{{ validateResult.wordAttachmentCount || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
v-if="validateResult?.success"
|
||||
type="success"
|
||||
show-icon
|
||||
:closable="false"
|
||||
title="压缩包校验通过,可继续填写各模板目录对应的子报告信息。"
|
||||
/>
|
||||
<el-alert
|
||||
v-else-if="validateResult"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
title="压缩包校验未通过,请按下方原因修正后重新上传。"
|
||||
/>
|
||||
|
||||
<el-card v-if="validateResult?.failReasons?.length" shadow="never" class="result-card">
|
||||
<template #header>校验问题</template>
|
||||
<ul class="result-list">
|
||||
<li v-for="item in validateResult.failReasons" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
</el-card>
|
||||
|
||||
<el-card v-if="validateResult?.orphanAttachmentPaths?.length" shadow="never" class="result-card">
|
||||
<template #header>未引用附件</template>
|
||||
<ul class="result-list">
|
||||
<li v-for="item in validateResult.orphanAttachmentPaths" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
</el-card>
|
||||
|
||||
<el-card v-if="createForm.childReports.length" shadow="never" class="result-card">
|
||||
<template #header>模板目录与子报告配置</template>
|
||||
<div class="dir-config-list">
|
||||
<div v-for="item in createForm.childReports" :key="item.templateDirPath" class="dir-config-item">
|
||||
<div class="dir-config-head">
|
||||
<div>
|
||||
<div class="dir-title">{{ item.templateDirName }}</div>
|
||||
<div class="dir-path">{{ item.templateDirPath }}</div>
|
||||
</div>
|
||||
<div class="dir-metrics">
|
||||
<span>监测点 {{ item.pointCount }}</span>
|
||||
<span>分组 {{ item.groupCount }}</span>
|
||||
<span>Excel {{ item.excelAttachmentCount }}</span>
|
||||
<span>Word {{ item.wordAttachmentCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-form label-width="96px">
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="报告编号" required>
|
||||
<el-input v-model="item.no" maxlength="32" clearable placeholder="请输入报告编号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="委托单位" required>
|
||||
<el-select v-model="item.clientUnitId" filterable clearable placeholder="请选择委托单位">
|
||||
<el-option
|
||||
v-for="option in clientUnitOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="报告模板" required>
|
||||
<el-select v-model="item.reportModelId" filterable clearable placeholder="请选择报告模板">
|
||||
<el-option
|
||||
v-for="option in reportModelOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="检测公司" required>
|
||||
<el-select v-model="item.createUnit" filterable clearable placeholder="请选择检测公司">
|
||||
<el-option
|
||||
v-for="option in companyOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="合同编号" required>
|
||||
<el-input v-model="item.contractNumber" maxlength="32" clearable placeholder="请输入合同编号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="联系电话">
|
||||
<el-input v-model="item.phonenumber" maxlength="32" clearable placeholder="请输入联系电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24">
|
||||
<el-form-item label="检测依据" required>
|
||||
<el-select
|
||||
v-model="item.standard"
|
||||
multiple
|
||||
filterable
|
||||
clearable
|
||||
collapse-tags
|
||||
placeholder="请选择检测依据"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in standardOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button :disabled="createSaving" @click="visibleProxy = false">取消</el-button>
|
||||
<el-button type="primary" :loading="createSaving" @click="handleCreate">创建批任务</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, type UploadFile, type UploadFiles, type UploadUserFile } from 'element-plus'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import type { ReportTaskMulti } from '@/api/aireport/testReportMulti/interface'
|
||||
|
||||
defineOptions({
|
||||
name: 'TestReportMultiCreateDialog'
|
||||
})
|
||||
|
||||
type SelectOption = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
type ChildConfigFormItem = ReportTaskMulti.ReportTaskMultiChildConfig &
|
||||
ReportTaskMulti.ReportTaskMultiTemplateDirRecord & {
|
||||
pointCount: number
|
||||
groupCount: number
|
||||
excelAttachmentCount: number
|
||||
wordAttachmentCount: number
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
zipValidating: boolean
|
||||
createSaving: boolean
|
||||
validateResult: ReportTaskMulti.ReportTaskMultiValidateResult | null
|
||||
clientUnitOptions: SelectOption[]
|
||||
reportModelOptions: SelectOption[]
|
||||
companyOptions: SelectOption[]
|
||||
standardOptions: SelectOption[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:visible', value: boolean): void
|
||||
(event: 'validate-zip', file: File): void
|
||||
(event: 'create', payload: ReportTaskMulti.ReportTaskMultiCreateParam): void
|
||||
(event: 'reset'): void
|
||||
}>()
|
||||
|
||||
const selectedZipFile = ref<File | null>(null)
|
||||
const zipFileList = ref<UploadUserFile[]>([])
|
||||
const createForm = reactive<{
|
||||
no: string
|
||||
sessionId: string
|
||||
childReports: ChildConfigFormItem[]
|
||||
}>({
|
||||
no: '',
|
||||
sessionId: '',
|
||||
childReports: []
|
||||
})
|
||||
|
||||
const visibleProxy = computed({
|
||||
get: () => props.visible,
|
||||
set: value => emit('update:visible', value)
|
||||
})
|
||||
|
||||
const clearValidatedResult = (notify = true) => {
|
||||
createForm.sessionId = ''
|
||||
createForm.childReports = []
|
||||
if (notify) {
|
||||
emit('reset')
|
||||
}
|
||||
}
|
||||
|
||||
const resetLocalState = (notify = true) => {
|
||||
createForm.no = ''
|
||||
selectedZipFile.value = null
|
||||
zipFileList.value = []
|
||||
clearValidatedResult(notify)
|
||||
}
|
||||
|
||||
const buildChildConfigForms = (templateDirs: ReportTaskMulti.ReportTaskMultiTemplateDirRecord[] = []) =>
|
||||
templateDirs.map(item => ({
|
||||
templateDirName: String(item.dirName || ''),
|
||||
templateDirPath: String(item.dirPath || ''),
|
||||
no: '',
|
||||
clientUnitId: '',
|
||||
reportModelId: '',
|
||||
createUnit: '',
|
||||
contractNumber: '',
|
||||
standard: [],
|
||||
phonenumber: '',
|
||||
pointCount: Number(item.pointCount || 0),
|
||||
groupCount: Number(item.groupCount || 0),
|
||||
excelAttachmentCount: Number(item.excelAttachmentCount || 0),
|
||||
wordAttachmentCount: Number(item.wordAttachmentCount || 0)
|
||||
}))
|
||||
|
||||
watch(
|
||||
() => props.validateResult,
|
||||
value => {
|
||||
createForm.sessionId = String(value?.sessionId || '')
|
||||
createForm.childReports = buildChildConfigForms(value?.templateDirs || [])
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const handleZipChange = (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
|
||||
selectedZipFile.value = uploadFile.raw || null
|
||||
zipFileList.value = uploadFiles.slice(-1).map(item => ({
|
||||
name: item.name,
|
||||
url: item.url,
|
||||
status: item.status,
|
||||
uid: item.uid
|
||||
}))
|
||||
clearValidatedResult()
|
||||
}
|
||||
|
||||
const handleZipRemove = () => {
|
||||
selectedZipFile.value = null
|
||||
zipFileList.value = []
|
||||
clearValidatedResult()
|
||||
}
|
||||
|
||||
const handleZipExceed = (files: File[]) => {
|
||||
const file = files[0]
|
||||
if (!file) return
|
||||
selectedZipFile.value = file
|
||||
zipFileList.value = [{ name: file.name, uid: Date.now(), status: 'ready' }]
|
||||
clearValidatedResult()
|
||||
}
|
||||
|
||||
const handleValidateZip = () => {
|
||||
if (!selectedZipFile.value) {
|
||||
ElMessage.warning('请先选择 zip 压缩包')
|
||||
return
|
||||
}
|
||||
emit('validate-zip', selectedZipFile.value)
|
||||
}
|
||||
|
||||
const validateCreateForm = () => {
|
||||
if (!createForm.no.trim()) return '请输入批任务编号'
|
||||
if (!createForm.sessionId) return '请先校验通过压缩包'
|
||||
if (!createForm.childReports.length) return '当前没有可创建的子报告配置'
|
||||
|
||||
for (const item of createForm.childReports) {
|
||||
if (!item.no.trim()) return `模板目录 ${item.templateDirName} 缺少报告编号`
|
||||
if (!item.clientUnitId) return `模板目录 ${item.templateDirName} 缺少委托单位`
|
||||
if (!item.reportModelId) return `模板目录 ${item.templateDirName} 缺少报告模板`
|
||||
if (!item.createUnit) return `模板目录 ${item.templateDirName} 缺少检测公司`
|
||||
if (!item.contractNumber.trim()) return `模板目录 ${item.templateDirName} 缺少合同编号`
|
||||
if (!item.standard.length) return `模板目录 ${item.templateDirName} 缺少检测依据`
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
const handleCreate = () => {
|
||||
const errorMessage = validateCreateForm()
|
||||
if (errorMessage) {
|
||||
ElMessage.warning(errorMessage)
|
||||
return
|
||||
}
|
||||
|
||||
emit('create', {
|
||||
no: createForm.no.trim(),
|
||||
sessionId: createForm.sessionId,
|
||||
childReports: createForm.childReports.map(item => ({
|
||||
templateDirName: item.templateDirName,
|
||||
templateDirPath: item.templateDirPath,
|
||||
no: item.no.trim(),
|
||||
clientUnitId: item.clientUnitId,
|
||||
reportModelId: item.reportModelId,
|
||||
createUnit: item.createUnit,
|
||||
contractNumber: item.contractNumber.trim(),
|
||||
standard: item.standard,
|
||||
phonenumber: item.phonenumber?.trim() || undefined
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
const handleClosed = () => {
|
||||
resetLocalState()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.create-dialog-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.create-base-form,
|
||||
.result-card {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.validate-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.validate-summary {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
color: var(--el-text-color-regular);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.result-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.dir-config-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.dir-config-item {
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 8px;
|
||||
padding: 16px 16px 0;
|
||||
background: var(--el-fill-color-blank);
|
||||
}
|
||||
|
||||
.dir-config-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dir-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.dir-path {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.dir-metrics {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.validate-toolbar,
|
||||
.dir-config-head {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<ProTable
|
||||
ref="proTable"
|
||||
:columns="columns"
|
||||
:request-api="props.requestApi"
|
||||
:request-error="props.requestError"
|
||||
:search-col="{ xs: 1, sm: 2, md: 2, lg: 4, xl: 4 }"
|
||||
:tool-button="['refresh', 'setting']"
|
||||
row-key="id"
|
||||
>
|
||||
<template #tableHeader="scope">
|
||||
<el-button type="primary" :icon="Plus" @click="emit('create')">新增批任务</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
:icon="Delete"
|
||||
:disabled="!scope.isSelected"
|
||||
@click="emit('batch-delete', scope.selectedListIds)"
|
||||
>
|
||||
批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #operation="{ row }">
|
||||
<el-button link type="primary" :icon="View" @click="emit('detail', row)">详情</el-button>
|
||||
<el-button link type="primary" :icon="Download" @click="emit('download', row)">下载</el-button>
|
||||
<el-button link type="primary" @click="emit('generate', row)">执行生成</el-button>
|
||||
<el-button link type="danger" :icon="Delete" @click="emit('delete', row)">删除</el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { Delete, Download, Plus, View } from '@element-plus/icons-vue'
|
||||
import { ElTag } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
import type { ResPage } from '@/api/interface'
|
||||
import ProTable from '@/components/ProTable/index.vue'
|
||||
import type { ColumnProps, ProTableInstance } from '@/components/ProTable/interface'
|
||||
import type { ReportTaskMulti } from '@/api/aireport/testReportMulti/interface'
|
||||
import { resolveReportTaskText } from '../../testReport/utils/testReport'
|
||||
|
||||
defineOptions({
|
||||
name: 'TestReportMultiTable'
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
requestApi: (
|
||||
params: ReportTaskMulti.ReportTaskMultiListParams
|
||||
) => Promise<{ data: ResPage<ReportTaskMulti.ReportTaskMultiRecord> }>
|
||||
requestError?: (error: unknown) => void
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'create'): void
|
||||
(event: 'detail', row: ReportTaskMulti.ReportTaskMultiRecord): void
|
||||
(event: 'download', row: ReportTaskMulti.ReportTaskMultiRecord): void
|
||||
(event: 'generate', row: ReportTaskMulti.ReportTaskMultiRecord): void
|
||||
(event: 'delete', row: ReportTaskMulti.ReportTaskMultiRecord): void
|
||||
(event: 'batch-delete', ids: string[]): void
|
||||
}>()
|
||||
|
||||
const MULTI_GENERATE_STATE_MAP: Record<number, { text: string; tagType: 'info' | 'warning' | 'success' | 'danger' }> = {
|
||||
0: { text: '未生成', tagType: 'info' },
|
||||
1: { text: '部分成功', tagType: 'warning' },
|
||||
2: { text: '全部成功', tagType: 'success' },
|
||||
3: { text: '生成中', tagType: 'warning' },
|
||||
4: { text: '生成失败', tagType: 'danger' }
|
||||
}
|
||||
|
||||
const proTable = ref<ProTableInstance>()
|
||||
|
||||
const columns: ColumnProps[] = [
|
||||
{ type: 'selection', fixed: 'left', width: 70 },
|
||||
{ type: 'index', fixed: 'left', width: 70, label: '序号' },
|
||||
{
|
||||
prop: 'keyword',
|
||||
label: '关键字',
|
||||
minWidth: 180,
|
||||
isShow: false,
|
||||
search: {
|
||||
el: 'input',
|
||||
order: 1,
|
||||
props: {
|
||||
placeholder: '请输入批任务编号、压缩包或台账汇总文件名'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'timeRange',
|
||||
label: '创建时间',
|
||||
minWidth: 220,
|
||||
isShow: false,
|
||||
search: {
|
||||
el: 'date-picker',
|
||||
order: 2,
|
||||
key: 'timeRange',
|
||||
props: {
|
||||
type: 'datetimerange',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
startPlaceholder: '开始创建时间',
|
||||
endPlaceholder: '结束创建时间'
|
||||
}
|
||||
}
|
||||
},
|
||||
{ prop: 'no', label: '批任务编号', minWidth: 180, fixed: 'left', render: ({ row }) => resolveReportTaskText(row.no) },
|
||||
{ prop: 'zipFileName', label: '压缩包文件', minWidth: 200, render: ({ row }) => resolveReportTaskText(row.zipFileName) },
|
||||
{ prop: 'summaryFileName', label: '台账汇总文件', minWidth: 200, render: ({ row }) => resolveReportTaskText(row.summaryFileName) },
|
||||
{ prop: 'dirCount', label: '模板目录数', minWidth: 110 },
|
||||
{ prop: 'childReportCount', label: '子报告数', minWidth: 100 },
|
||||
{ prop: 'pointCount', label: '监测点总数', minWidth: 110 },
|
||||
{
|
||||
prop: 'generateState',
|
||||
label: '整体生成状态',
|
||||
minWidth: 130,
|
||||
render: ({ row }) => (
|
||||
<ElTag type={MULTI_GENERATE_STATE_MAP[Number(row.generateState)]?.tagType || 'info'}>
|
||||
{MULTI_GENERATE_STATE_MAP[Number(row.generateState)]?.text || '未知状态'}
|
||||
</ElTag>
|
||||
)
|
||||
},
|
||||
{ prop: 'createTime', label: '创建时间', minWidth: 170, render: ({ row }) => resolveReportTaskText(row.createTime) },
|
||||
{ prop: 'operation', label: '操作', fixed: 'right', width: 280 }
|
||||
]
|
||||
|
||||
const refreshTable = () => {
|
||||
proTable.value?.clearSelection()
|
||||
proTable.value?.getTableList()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
refreshTable
|
||||
})
|
||||
</script>
|
||||
607
frontend/src/views/aireport/testReportMulti/index.vue
Normal file
607
frontend/src/views/aireport/testReportMulti/index.vue
Normal file
@@ -0,0 +1,607 @@
|
||||
<template>
|
||||
<div class="table-box report-task-multi-page">
|
||||
<TestReportMultiTable
|
||||
ref="tableRef"
|
||||
:request-api="getTableList"
|
||||
:request-error="handleTableRequestError"
|
||||
@create="openCreateDialog"
|
||||
@detail="openDetailDialog"
|
||||
@download="handleDownloadMulti"
|
||||
@generate="handleGenerateMulti"
|
||||
@delete="handleDelete"
|
||||
@batch-delete="handleBatchDelete"
|
||||
/>
|
||||
|
||||
<TestReportMultiCreateDialog
|
||||
v-model:visible="createDialogVisible"
|
||||
:zip-validating="zipValidating"
|
||||
:create-saving="createSaving"
|
||||
:validate-result="zipValidateResult"
|
||||
:client-unit-options="clientUnitOptions"
|
||||
:report-model-options="reportModelOptions"
|
||||
:company-options="companyOptions"
|
||||
:standard-options="standardOptions"
|
||||
@validate-zip="handleValidateZip"
|
||||
@create="handleCreateMulti"
|
||||
@reset="resetCreateDialogState"
|
||||
/>
|
||||
|
||||
<el-dialog v-model="detailDialogVisible" title="多模板批任务详情" width="1200px" append-to-body destroy-on-close>
|
||||
<div v-loading="detailLoading" class="detail-dialog-body">
|
||||
<el-descriptions v-if="detailRecord" :column="2" border>
|
||||
<el-descriptions-item label="批任务编号">{{ resolveReportTaskText(detailRecord.no) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="压缩包文件">{{ resolveReportTaskText(detailRecord.zipFileName) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="台账汇总文件">{{ resolveReportTaskText(detailRecord.summaryFileName) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="整体生成状态">
|
||||
<el-tag :type="getMultiGenerateStateTagType(detailRecord.generateState)">
|
||||
{{ getMultiGenerateStateText(detailRecord.generateState) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="模板目录数">{{ detailRecord.dirCount ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="子报告数">{{ detailRecord.childReportCount ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="监测点总数">{{ detailRecord.pointCount ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ resolveReportTaskText(detailRecord.createTime) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="detail-toolbar">
|
||||
<div class="section-title">子报告列表</div>
|
||||
<el-button
|
||||
:loading="downloadingMultiId === detailRecord?.id"
|
||||
:disabled="!detailRecord?.id"
|
||||
@click="handleDownloadMulti(detailRecord)"
|
||||
>
|
||||
下载批任务
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="multiGenerating"
|
||||
:disabled="!detailRecord?.id"
|
||||
@click="handleGenerateMulti(detailRecord)"
|
||||
>
|
||||
执行整批生成
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="detailChildReports" border class="child-report-table">
|
||||
<el-table-column prop="templateDirName" label="模板目录" min-width="150" />
|
||||
<el-table-column prop="no" label="报告编号" min-width="160" />
|
||||
<el-table-column prop="reportModelName" label="报告模板" min-width="140" />
|
||||
<el-table-column prop="state" label="报告状态" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getReportTaskStateTagType(row.state)">{{ getReportTaskStateText(row.state) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="reportGenerateState" label="生成状态" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getReportTaskGenerateStateTagType(row.reportGenerateState)">
|
||||
{{ getReportTaskGenerateStateText(row.reportGenerateState) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="pointCount" label="监测点数" min-width="100" />
|
||||
<el-table-column prop="groupCount" label="分组数" min-width="100" />
|
||||
<el-table-column label="操作" min-width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openChildDetailDialog(row)">详情</el-button>
|
||||
<el-button link type="primary" @click="openChildGroupDialog(row)">分组</el-button>
|
||||
<el-button link type="primary" @click="handleGenerateChildReport(row)">生成报告</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<ReportTaskDetailDialog
|
||||
v-model:visible="childDetailDialogVisible"
|
||||
:loading="childDetailLoading"
|
||||
:detail="childDetailRecord"
|
||||
:group-reports="childDetailGroupReports"
|
||||
:company-label-map="companyLabelMap"
|
||||
:standard-label-map="standardLabelMap"
|
||||
:downloading-group-report-id="downloadingGroupReportId"
|
||||
@download-group-report="handleDownloadGroupReport"
|
||||
/>
|
||||
|
||||
<ReportTaskGroupDialog
|
||||
v-model:visible="childGroupDialogVisible"
|
||||
:loading="childGroupLoading"
|
||||
:saving="childGroupSaving"
|
||||
:record="currentChildGroupRecord"
|
||||
:point-list="childGroupPointList"
|
||||
@submit="handleSaveChildGroups"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import type { Dict, ResPage, ResultData } from '@/api/interface'
|
||||
import { getDictList } from '@/api/user/login'
|
||||
import { DICT_CODES } from '@/constants/dictCodes'
|
||||
import { listClientUnitsApi } from '@/api/aireport/clientUnit'
|
||||
import { listReportModelsApi } from '@/api/aireport/reportModel'
|
||||
import {
|
||||
deleteReportTaskMultisApi,
|
||||
downloadReportTaskMultiApi,
|
||||
generateReportTaskMultiApi,
|
||||
getReportTaskMultiDetailApi,
|
||||
listReportTaskMultiChildReportsApi,
|
||||
listReportTaskMultisApi,
|
||||
validateReportTaskMultiZipApi,
|
||||
createReportTaskMultiApi
|
||||
} from '@/api/aireport/testReportMulti'
|
||||
import type { ReportTaskMulti } from '@/api/aireport/testReportMulti/interface'
|
||||
import {
|
||||
downloadReportTaskGroupReportApi,
|
||||
generateReportTaskGroupReportsApi,
|
||||
getReportTaskDetailApi,
|
||||
listReportTaskGroupReportsApi,
|
||||
listReportTaskPointsApi,
|
||||
saveReportTaskGroupsApi
|
||||
} from '@/api/aireport/testReport'
|
||||
import type { ReportTask } from '@/api/aireport/testReport/interface'
|
||||
import { useDownloadWithServerFileName } from '@/hooks/useDownload'
|
||||
import { useDictStore } from '@/stores/modules/dict'
|
||||
import TestReportMultiCreateDialog from './components/TestReportMultiCreateDialog.vue'
|
||||
import TestReportMultiTable from './components/TestReportMultiTable.vue'
|
||||
import ReportTaskDetailDialog from '../testReport/components/ReportTaskDetailDialog.vue'
|
||||
import ReportTaskGroupDialog from '../testReport/components/ReportTaskGroupDialog.vue'
|
||||
import {
|
||||
buildDictSelectOptions,
|
||||
buildOptionLabelMap,
|
||||
getReportTaskErrorMessage,
|
||||
getReportTaskGenerateStateTagType,
|
||||
getReportTaskGenerateStateText,
|
||||
getReportTaskStateTagType,
|
||||
getReportTaskStateText,
|
||||
normalizeReportTaskGroupReportList,
|
||||
normalizeReportTaskPage,
|
||||
normalizeReportTaskPointList,
|
||||
resolveReportTaskText,
|
||||
unwrapReportTaskPayload
|
||||
} from '../testReport/utils/testReport'
|
||||
|
||||
defineOptions({
|
||||
name: 'ReportTaskMultiPage'
|
||||
})
|
||||
|
||||
const MULTI_GENERATE_STATE_MAP: Record<number, { text: string; tagType: 'info' | 'warning' | 'success' | 'danger' }> = {
|
||||
0: { text: '未生成', tagType: 'info' },
|
||||
1: { text: '部分成功', tagType: 'warning' },
|
||||
2: { text: '全部成功', tagType: 'success' },
|
||||
3: { text: '生成中', tagType: 'warning' },
|
||||
4: { text: '生成失败', tagType: 'danger' }
|
||||
}
|
||||
|
||||
const dictStore = useDictStore()
|
||||
const tableRef = ref<{ refreshTable: () => void } | null>(null)
|
||||
const createDialogVisible = ref(false)
|
||||
const detailDialogVisible = ref(false)
|
||||
const childDetailDialogVisible = ref(false)
|
||||
const childGroupDialogVisible = ref(false)
|
||||
const zipValidating = ref(false)
|
||||
const createSaving = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const childDetailLoading = ref(false)
|
||||
const childGroupLoading = ref(false)
|
||||
const childGroupSaving = ref(false)
|
||||
const multiGenerating = ref(false)
|
||||
const zipValidateResult = ref<ReportTaskMulti.ReportTaskMultiValidateResult | null>(null)
|
||||
const detailRecord = ref<ReportTaskMulti.ReportTaskMultiRecord | null>(null)
|
||||
const detailChildReports = ref<ReportTask.ReportTaskRecord[]>([])
|
||||
const childDetailRecord = ref<ReportTask.ReportTaskRecord | null>(null)
|
||||
const childDetailGroupReports = ref<ReportTask.ReportTaskGroupReportRecord[]>([])
|
||||
const downloadingMultiId = ref('')
|
||||
const downloadingGroupReportId = ref('')
|
||||
const currentChildGroupRecord = ref<ReportTask.ReportTaskRecord | null>(null)
|
||||
const childGroupPointList = ref<ReportTask.ReportTaskPointRecord[]>([])
|
||||
const clientUnitOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
const reportModelOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
|
||||
const companyOptions = computed(() => buildDictSelectOptions(dictStore.getDictData(DICT_CODES.TEST_REPORT_COMPANY)))
|
||||
const standardOptions = computed(() => buildDictSelectOptions(dictStore.getDictData(DICT_CODES.TEST_REPORT_STANDARD)))
|
||||
const companyLabelMap = computed(() => buildOptionLabelMap(companyOptions.value))
|
||||
const standardLabelMap = computed(() => buildOptionLabelMap(standardOptions.value))
|
||||
const requiredDictCodes = [DICT_CODES.TEST_REPORT_COMPANY, DICT_CODES.TEST_REPORT_STANDARD]
|
||||
|
||||
const normalizeMultiListParams = (params: ReportTaskMulti.ReportTaskMultiListParams = {}) => {
|
||||
const timeRange = Array.isArray(params.timeRange) ? params.timeRange : []
|
||||
const [searchBeginTime = '', searchEndTime = ''] = timeRange
|
||||
return {
|
||||
keyword: String(params.keyword || '').trim() || undefined,
|
||||
searchBeginTime: searchBeginTime || params.searchBeginTime || undefined,
|
||||
searchEndTime: searchEndTime || params.searchEndTime || undefined,
|
||||
generateState: params.generateState,
|
||||
pageNum: params.pageNum || 1,
|
||||
pageSize: params.pageSize || 10
|
||||
}
|
||||
}
|
||||
|
||||
const getMultiGenerateStateText = (value?: number | null) => MULTI_GENERATE_STATE_MAP[Number(value)]?.text || '未知状态'
|
||||
const getMultiGenerateStateTagType = (value?: number | null) => MULTI_GENERATE_STATE_MAP[Number(value)]?.tagType || 'info'
|
||||
|
||||
const ensureDictOptionsReady = async () => {
|
||||
const missingCodes = requiredDictCodes.filter(code => !dictStore.getDictData(code).length)
|
||||
if (!missingCodes.length) return
|
||||
const response = await getDictList()
|
||||
const dictData = (response.data as unknown as Dict[]) || []
|
||||
const nextDictMap = new Map((dictStore.dictData || []).map(item => [item.code, item]))
|
||||
dictData.forEach(item => {
|
||||
if (item?.code) nextDictMap.set(item.code, item)
|
||||
})
|
||||
dictStore.setDictData(Array.from(nextDictMap.values()))
|
||||
}
|
||||
|
||||
const loadClientUnitOptions = async () => {
|
||||
const response = await listClientUnitsApi({ pageNum: 1, pageSize: 200 })
|
||||
const pageData = normalizeReportTaskPage<{ id?: string; name?: string }>(response as unknown as ResPage<{ id?: string; name?: string }>)
|
||||
clientUnitOptions.value = (pageData.records || [])
|
||||
.filter(item => item.id && item.name)
|
||||
.map(item => ({ label: item.name!, value: item.id! }))
|
||||
}
|
||||
|
||||
const loadReportModelOptions = async () => {
|
||||
const response = await listReportModelsApi({ pageNum: 1, pageSize: 200 })
|
||||
const pageData = normalizeReportTaskPage<{ id?: string; name?: string }>(response as unknown as ResPage<{ id?: string; name?: string }>)
|
||||
reportModelOptions.value = (pageData.records || [])
|
||||
.filter(item => item.id && item.name)
|
||||
.map(item => ({ label: item.name!, value: item.id! }))
|
||||
}
|
||||
|
||||
const ensureCreateOptionsReady = async () => {
|
||||
await Promise.all([ensureDictOptionsReady(), loadClientUnitOptions(), loadReportModelOptions()])
|
||||
}
|
||||
|
||||
const getTableList = async (params: ReportTaskMulti.ReportTaskMultiListParams = {}) => {
|
||||
const response = await listReportTaskMultisApi(normalizeMultiListParams(params))
|
||||
const pageData = normalizeReportTaskPage<ReportTaskMulti.ReportTaskMultiRecord>(
|
||||
response as unknown as ResPage<ReportTaskMulti.ReportTaskMultiRecord>
|
||||
)
|
||||
return {
|
||||
data: pageData
|
||||
}
|
||||
}
|
||||
|
||||
const refreshTable = () => {
|
||||
tableRef.value?.refreshTable()
|
||||
}
|
||||
|
||||
const handleTableRequestError = (error: unknown) => {
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '多模板批任务列表查询失败'))
|
||||
}
|
||||
|
||||
const resetCreateDialogState = () => {
|
||||
zipValidateResult.value = null
|
||||
}
|
||||
|
||||
const openCreateDialog = async () => {
|
||||
try {
|
||||
await ensureCreateOptionsReady()
|
||||
resetCreateDialogState()
|
||||
createDialogVisible.value = true
|
||||
} catch (error) {
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '批任务关联选项加载失败'))
|
||||
}
|
||||
}
|
||||
|
||||
const handleValidateZip = async (file: File) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
zipValidating.value = true
|
||||
try {
|
||||
const response = await validateReportTaskMultiZipApi(formData)
|
||||
const result = unwrapReportTaskPayload<ReportTaskMulti.ReportTaskMultiValidateResult>(
|
||||
response as ResultData<ReportTaskMulti.ReportTaskMultiValidateResult>
|
||||
)
|
||||
zipValidateResult.value = result
|
||||
ElMessage.success(result?.success ? '压缩包校验通过' : '压缩包校验未通过')
|
||||
} catch (error) {
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '压缩包校验失败'))
|
||||
} finally {
|
||||
zipValidating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateMulti = async (payload: ReportTaskMulti.ReportTaskMultiCreateParam) => {
|
||||
createSaving.value = true
|
||||
try {
|
||||
await createReportTaskMultiApi(payload)
|
||||
ElMessage.success('多模板批任务创建成功')
|
||||
createDialogVisible.value = false
|
||||
refreshTable()
|
||||
} catch (error) {
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '多模板批任务创建失败'))
|
||||
} finally {
|
||||
createSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadDetailData = async (id: string) => {
|
||||
const [detailResponse, childReportsResponse] = await Promise.all([
|
||||
getReportTaskMultiDetailApi(id),
|
||||
listReportTaskMultiChildReportsApi(id)
|
||||
])
|
||||
detailRecord.value = unwrapReportTaskPayload<ReportTaskMulti.ReportTaskMultiRecord>(
|
||||
detailResponse as ResultData<ReportTaskMulti.ReportTaskMultiRecord>
|
||||
)
|
||||
detailChildReports.value = (unwrapReportTaskPayload<ReportTask.ReportTaskRecord[]>(
|
||||
childReportsResponse as ResultData<ReportTask.ReportTaskRecord[]>
|
||||
) || []) as ReportTask.ReportTaskRecord[]
|
||||
}
|
||||
|
||||
const openDetailDialog = async (row: ReportTaskMulti.ReportTaskMultiRecord) => {
|
||||
const id = String(row.id || '').trim()
|
||||
if (!id) {
|
||||
ElMessage.warning('当前批任务缺少 ID,无法查看详情')
|
||||
return
|
||||
}
|
||||
detailDialogVisible.value = true
|
||||
detailLoading.value = true
|
||||
try {
|
||||
await loadDetailData(id)
|
||||
} catch (error) {
|
||||
detailDialogVisible.value = false
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '多模板批任务详情查询失败'))
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const refreshDetailIfMatched = async (id: string) => {
|
||||
if (!detailDialogVisible.value) return
|
||||
if (String(detailRecord.value?.id || '').trim() !== id) return
|
||||
await loadDetailData(id)
|
||||
}
|
||||
|
||||
const handleDownloadMulti = async (row: ReportTaskMulti.ReportTaskMultiRecord | null) => {
|
||||
const id = String(row?.id || '').trim()
|
||||
if (!id) {
|
||||
ElMessage.warning('当前批任务缺少 ID,无法下载')
|
||||
return
|
||||
}
|
||||
downloadingMultiId.value = id
|
||||
try {
|
||||
await useDownloadWithServerFileName(
|
||||
() => downloadReportTaskMultiApi(id),
|
||||
row?.no || '批任务结果包',
|
||||
undefined,
|
||||
false,
|
||||
'.zip'
|
||||
)
|
||||
} catch (error) {
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '批任务下载失败'))
|
||||
} finally {
|
||||
downloadingMultiId.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
const handleGenerateMulti = async (row: ReportTaskMulti.ReportTaskMultiRecord | null) => {
|
||||
const id = String(row?.id || '').trim()
|
||||
if (!id) {
|
||||
ElMessage.warning('当前批任务缺少 ID,无法执行生成')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm('将依次执行当前批任务下全部子报告的按分组生成,是否继续?', '执行整批生成', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
multiGenerating.value = true
|
||||
try {
|
||||
await generateReportTaskMultiApi(id)
|
||||
ElMessage.success('整批生成执行完成')
|
||||
refreshTable()
|
||||
await refreshDetailIfMatched(id)
|
||||
} catch (error) {
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '整批生成失败'))
|
||||
await refreshDetailIfMatched(id)
|
||||
refreshTable()
|
||||
} finally {
|
||||
multiGenerating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const openChildDetailDialog = async (row: ReportTask.ReportTaskRecord) => {
|
||||
const reportId = String(row.id || '').trim()
|
||||
if (!reportId) {
|
||||
ElMessage.warning('当前子报告缺少 ID,无法查看详情')
|
||||
return
|
||||
}
|
||||
childDetailDialogVisible.value = true
|
||||
childDetailLoading.value = true
|
||||
try {
|
||||
const [detailResponse, groupReportsResponse] = await Promise.all([
|
||||
getReportTaskDetailApi(reportId),
|
||||
listReportTaskGroupReportsApi(reportId)
|
||||
])
|
||||
childDetailRecord.value = unwrapReportTaskPayload<ReportTask.ReportTaskRecord>(
|
||||
detailResponse as ResultData<ReportTask.ReportTaskRecord>
|
||||
)
|
||||
childDetailGroupReports.value = normalizeReportTaskGroupReportList(groupReportsResponse)
|
||||
} catch (error) {
|
||||
childDetailDialogVisible.value = false
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '子报告详情查询失败'))
|
||||
} finally {
|
||||
childDetailLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const openChildGroupDialog = async (row: ReportTask.ReportTaskRecord) => {
|
||||
const reportId = String(row.id || '').trim()
|
||||
if (!reportId) {
|
||||
ElMessage.warning('当前子报告缺少 ID,无法维护分组')
|
||||
return
|
||||
}
|
||||
currentChildGroupRecord.value = row
|
||||
childGroupPointList.value = []
|
||||
childGroupDialogVisible.value = true
|
||||
childGroupLoading.value = true
|
||||
try {
|
||||
const response = await listReportTaskPointsApi(reportId)
|
||||
childGroupPointList.value = normalizeReportTaskPointList(response)
|
||||
} catch (error) {
|
||||
childGroupDialogVisible.value = false
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '子报告监测点查询失败'))
|
||||
} finally {
|
||||
childGroupLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveChildGroups = async (params: ReportTask.ReportTaskGroupSaveRequest) => {
|
||||
const reportId = String(currentChildGroupRecord.value?.id || '').trim()
|
||||
if (!reportId) {
|
||||
ElMessage.warning('当前子报告缺少 ID,无法保存分组')
|
||||
return
|
||||
}
|
||||
childGroupSaving.value = true
|
||||
try {
|
||||
await saveReportTaskGroupsApi(reportId, params)
|
||||
ElMessage.success('子报告分组保存成功')
|
||||
childGroupDialogVisible.value = false
|
||||
refreshTable()
|
||||
await refreshDetailIfMatched(String(detailRecord.value?.id || ''))
|
||||
} catch (error) {
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '子报告分组保存失败'))
|
||||
} finally {
|
||||
childGroupSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleGenerateChildReport = async (row: ReportTask.ReportTaskRecord) => {
|
||||
const reportId = String(row.id || '').trim()
|
||||
if (!reportId) {
|
||||
ElMessage.warning('当前子报告缺少 ID,无法生成报告')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm('将按当前子报告的分组生成全部报告,是否继续?', '生成子报告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await generateReportTaskGroupReportsApi(reportId)
|
||||
ElMessage.success('子报告生成完成')
|
||||
refreshTable()
|
||||
await refreshDetailIfMatched(String(detailRecord.value?.id || ''))
|
||||
if (childDetailDialogVisible.value && String(childDetailRecord.value?.id || '') === reportId) {
|
||||
await openChildDetailDialog(row)
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '子报告生成失败'))
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownloadGroupReport = async (row: ReportTask.ReportTaskGroupReportRecord) => {
|
||||
const groupReportId = String(row.id || '').trim()
|
||||
if (!groupReportId) {
|
||||
ElMessage.warning('当前分组报告缺少 ID,无法下载')
|
||||
return
|
||||
}
|
||||
downloadingGroupReportId.value = groupReportId
|
||||
try {
|
||||
await useDownloadWithServerFileName(
|
||||
() => downloadReportTaskGroupReportApi(groupReportId),
|
||||
row.reportFileName || row.reportName || `分组报告_${row.groupNo || ''}`,
|
||||
undefined,
|
||||
false,
|
||||
'.docx'
|
||||
)
|
||||
} catch (error) {
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '分组报告下载失败'))
|
||||
} finally {
|
||||
downloadingGroupReportId.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
const deleteRows = async (ids: string[], successMessage: string) => {
|
||||
if (!ids.length) {
|
||||
ElMessage.warning('请选择要删除的批任务')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm('删除后该批任务及其下全部子报告都会被删除,是否继续?', '删除批任务', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await deleteReportTaskMultisApi(ids)
|
||||
ElMessage.success(successMessage)
|
||||
refreshTable()
|
||||
} catch (error) {
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '批任务删除失败'))
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (row: ReportTaskMulti.ReportTaskMultiRecord) => {
|
||||
const id = String(row.id || '').trim()
|
||||
if (!id) {
|
||||
ElMessage.warning('当前批任务缺少 ID,无法删除')
|
||||
return
|
||||
}
|
||||
await deleteRows([id], '批任务删除成功')
|
||||
}
|
||||
|
||||
const handleBatchDelete = async (ids: string[]) => {
|
||||
await deleteRows(ids, '批任务批量删除成功')
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await ensureDictOptionsReady()
|
||||
} catch (error) {
|
||||
ElMessage.error(getReportTaskErrorMessage(error, '字典加载失败'))
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.report-task-multi-page {
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-dialog-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.child-report-table {
|
||||
width: 100%;
|
||||
}
|
||||
.detail-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.detail-toolbar {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,9 @@
|
||||
"include": [
|
||||
"env.d.ts",
|
||||
"src/**/*",
|
||||
"src/**/*.vue"
|
||||
"src/**/*.vue",
|
||||
"src/views/aireport/testReportMulti/**/*.vue",
|
||||
"src/views/aireport/testReportMulti/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/__tests__/*"
|
||||
|
||||
Reference in New Issue
Block a user