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,前端目录沿用既有 aireport 命名。
|
||||||
'/aiReport/reportTask': '/aireport/testReport',
|
'/aiReport/reportTask': '/aireport/testReport',
|
||||||
'/aiReport/reportTask/index': '/aireport/testReport/index',
|
'/aiReport/reportTask/index': '/aireport/testReport/index',
|
||||||
|
'/aiReport/reportTaskMulti': '/aireport/testReportMulti',
|
||||||
|
'/aiReport/reportTaskMulti/index': '/aireport/testReportMulti/index',
|
||||||
'/aiReport/testReport': '/aireport/testReport',
|
'/aiReport/testReport': '/aireport/testReport',
|
||||||
'/aiReport/testReport/index': '/aireport/testReport/index',
|
'/aiReport/testReport/index': '/aireport/testReport/index',
|
||||||
|
'/aiReport/testReportMulti': '/aireport/testReportMulti',
|
||||||
|
'/aiReport/testReportMulti/index': '/aireport/testReportMulti/index',
|
||||||
'/aiReport/reportModel': '/aireport/reportModel',
|
'/aiReport/reportModel': '/aireport/reportModel',
|
||||||
'/aiReport/reportModel/index': '/aireport/reportModel/index',
|
'/aiReport/reportModel/index': '/aireport/reportModel/index',
|
||||||
'/aiReport/clientUnit': '/aireport/clientUnit',
|
'/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": [
|
"include": [
|
||||||
"env.d.ts",
|
"env.d.ts",
|
||||||
"src/**/*",
|
"src/**/*",
|
||||||
"src/**/*.vue"
|
"src/**/*.vue",
|
||||||
|
"src/views/aireport/testReportMulti/**/*.vue",
|
||||||
|
"src/views/aireport/testReportMulti/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"src/**/__tests__/*"
|
"src/**/__tests__/*"
|
||||||
|
|||||||
Reference in New Issue
Block a user