diff --git a/frontend/src/api/aireport/testReport/index.ts b/frontend/src/api/aireport/testReport/index.ts index 64da730..eddcecd 100644 --- a/frontend/src/api/aireport/testReport/index.ts +++ b/frontend/src/api/aireport/testReport/index.ts @@ -37,11 +37,32 @@ export const downloadReportTaskLedgerTemplateApi = (): Promise> export const validateReportTaskLedgerApi = (id: string, formData: FormData) => { - return http.post(`${REPORT_TASK_BASE_URL}/${id}/ledger/validate`, formData, { + return http.post(`${REPORT_TASK_BASE_URL}/${id}/ledger/validate`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }) } +export const importReportTaskLedgerApi = (id: string, params: ReportTask.ReportTaskLedgerImportRequest) => { + return http.post(`${REPORT_TASK_BASE_URL}/${id}/ledger/import`, params) +} + +export const listReportTaskPointsApi = (id: string) => { + return http.get(`${REPORT_TASK_BASE_URL}/${id}/point/list`) +} + +export const saveReportTaskGroupsApi = (id: string, params: ReportTask.ReportTaskGroupSaveRequest) => { + return http.post(`${REPORT_TASK_BASE_URL}/${id}/group/save`, params) +} + export const listReportTaskGroupReportsApi = (id: string) => { return http.get(`${REPORT_TASK_BASE_URL}/${id}/group-report/list`) } + +export const generateReportTaskGroupReportsApi = (id: string) => { + return http.post(`${REPORT_TASK_BASE_URL}/${id}/generate`) +} + +export const downloadReportTaskGroupReportApi = (groupReportId: string): Promise> => + http.get(`${REPORT_TASK_BASE_URL}/group-report/${groupReportId}/download`, undefined, { + responseType: 'blob' + }) as unknown as Promise> diff --git a/frontend/src/api/aireport/testReport/interface/index.ts b/frontend/src/api/aireport/testReport/interface/index.ts index 3ce2e4f..7edb995 100644 --- a/frontend/src/api/aireport/testReport/interface/index.ts +++ b/frontend/src/api/aireport/testReport/interface/index.ts @@ -1,9 +1,12 @@ export namespace ReportTask { + export type ReportTaskState = '01' | '02' | '03' | '04' | '05' + export type ReportTaskGenerateState = 0 | 1 | 2 export type ReportTaskGroupReportGenerateState = 0 | 1 | 2 | 3 export type ReportTaskLedgerImportStage = | '选择文件' | '校验文件' | '文件上传' + | '文件导入' | '基础资料维护' | '完成' @@ -41,6 +44,7 @@ export namespace ReportTask { searchBeginTime?: string searchEndTime?: string timeRange?: string[] + state?: ReportTaskState | string pageNum?: number pageSize?: number } @@ -65,12 +69,59 @@ export namespace ReportTask { checkResult?: string } + export interface ReportTaskLedgerImportRequest { + batchId: string + } + export interface ReportTaskLedgerImportStageRecord { stage?: ReportTaskLedgerImportStage | string | null success?: boolean | null message?: string | null } + export interface ReportTaskLedgerValidateAttachmentRecord { + attachmentName?: string | null + attachmentType?: string | null + referenced?: boolean | null + uploaded?: boolean | null + missing?: boolean | null + reuploadAllowed?: boolean | null + message?: string | null + } + + export interface ReportTaskLedgerValidatePointRecord { + rowNo?: number | null + pointKey?: string | null + substationName?: string | null + pointName?: string | null + complete?: boolean | null + missingAttachmentCount?: number | null + testDeviceNoValid?: boolean | null + testDeviceNoMessage?: string | null + clientUnitNameValid?: boolean | null + clientUnitNameMessage?: string | null + attachments?: ReportTaskLedgerValidateAttachmentRecord[] | null + } + + export interface ReportTaskLedgerValidateResult { + sessionId?: string | null + batchId?: string | null + success?: boolean | null + summaryFileName?: string | null + summaryFilePresent?: boolean | null + tempStoragePath?: string | null + uploadedFileCount?: number | null + pointCount?: number | null + missingAttachmentCount?: number | null + canContinueUpload?: boolean | null + requiredSheets?: string[] | null + existingSheets?: string[] | null + missingSheets?: string[] | null + orphanAttachmentNames?: string[] | null + failReasons?: string[] | null + points?: ReportTaskLedgerValidatePointRecord[] | null + } + export interface ReportTaskLedgerImportSnapshotGroupRecord { groupNo?: number | null count?: number | null @@ -100,7 +151,9 @@ export namespace ReportTask { currentStage?: ReportTaskLedgerImportStage | string | null success?: boolean | null summaryFileName?: string | null + tempStoragePath?: string | null totalFileCount?: number + uploadedFileCount?: number pointCount?: number excelAttachmentCount?: number wordAttachmentCount?: number @@ -153,9 +206,13 @@ export namespace ReportTask { export interface ReportTaskLedgerPreparedState { draftId: string batchId: string + validateSessionId: string + validateResult: ReportTaskLedgerValidateResult | null latestImportResult: ReportTaskLedgerImportResult | null snapshot: ReportTaskLedgerImportSnapshot | null hasImported: boolean + validatedBatchId: string pendingReimport: boolean + pendingRevalidate: boolean } } diff --git a/frontend/src/views/aireport/testReport/components/ReportTaskDetailDialog.vue b/frontend/src/views/aireport/testReport/components/ReportTaskDetailDialog.vue index 6fb91fa..8094b87 100644 --- a/frontend/src/views/aireport/testReport/components/ReportTaskDetailDialog.vue +++ b/frontend/src/views/aireport/testReport/components/ReportTaskDetailDialog.vue @@ -44,6 +44,19 @@ + + + @@ -75,10 +88,12 @@ const props = defineProps<{ groupReports: ReportTask.ReportTaskGroupReportRecord[] companyLabelMap: Record standardLabelMap: Record + downloadingGroupReportId?: string | null }>() const emit = defineEmits<{ (event: 'update:visible', value: boolean): void + (event: 'download-group-report', value: ReportTask.ReportTaskGroupReportRecord): void }>() const visibleProxy = computed({ @@ -89,6 +104,7 @@ const visibleProxy = computed({ const companyLabelMap = computed(() => props.companyLabelMap) const standardLabelMap = computed(() => props.standardLabelMap) const groupReports = computed(() => props.groupReports || []) +const downloadingGroupReportId = computed(() => props.downloadingGroupReportId || '') diff --git a/frontend/src/views/aireport/testReport/index.vue b/frontend/src/views/aireport/testReport/index.vue index ed1e62f..9d90b40 100644 --- a/frontend/src/views/aireport/testReport/index.vue +++ b/frontend/src/views/aireport/testReport/index.vue @@ -24,6 +24,17 @@ @@ -71,10 +93,14 @@ import type { ColumnProps, ProTableInstance } from '@/components/ProTable/interf import { createReportTaskApi, deleteReportTasksApi, + downloadReportTaskGroupReportApi, exportReportTasksApi, + generateReportTaskGroupReportsApi, getReportTaskDetailApi, listReportTaskGroupReportsApi, + listReportTaskPointsApi, listReportTasksApi, + saveReportTaskGroupsApi, submitReportTaskAuditApi, updateReportTaskApi } from '@/api/aireport/testReport' @@ -88,6 +114,7 @@ import { useDictStore } from '@/stores/modules/dict' import ReportTaskAuditDialog from './components/ReportTaskAuditDialog.vue' import ReportTaskDetailDialog from './components/ReportTaskDetailDialog.vue' import ReportTaskFormDialog from './components/ReportTaskFormDialog.vue' +import ReportTaskGroupDialog from './components/ReportTaskGroupDialog.vue' import { buildDictSelectOptions, buildOptionLabelMap, @@ -95,6 +122,7 @@ import { normalizeReportTaskGroupReportList, normalizeReportTaskListParams, normalizeReportTaskPage, + normalizeReportTaskPointList, resolveReportTaskCreateUnitText, resolveReportTaskStandardText, resolveReportTaskText, @@ -110,17 +138,23 @@ const proTable = ref() const formDialogVisible = ref(false) const detailDialogVisible = ref(false) const auditDialogVisible = ref(false) +const groupDialogVisible = ref(false) const formSaving = ref(false) const formSaveSuccessVersion = ref(0) const detailLoading = ref(false) const auditSaving = ref(false) +const groupDialogLoading = ref(false) +const groupDialogSaving = ref(false) const formMode = ref<'create' | 'edit'>('create') const currentRecord = ref(null) const detailRecord = ref(null) const detailGroupReports = ref([]) const currentAuditRecord = ref(null) +const currentGroupRecord = ref(null) +const groupPointList = ref([]) const clientUnitOptions = ref>([]) const reportModelOptions = ref>([]) +const downloadingGroupReportId = ref('') const companyOptions = computed(() => buildDictSelectOptions(dictStore.getDictData(DICT_CODES.TEST_REPORT_COMPANY))) const standardOptions = computed(() => buildDictSelectOptions(dictStore.getDictData(DICT_CODES.TEST_REPORT_STANDARD))) @@ -130,6 +164,7 @@ const requiredDictCodes = [DICT_CODES.TEST_REPORT_COMPANY, DICT_CODES.TEST_REPOR const currentSearchParams = computed(() => normalizeReportTaskListParams((proTable.value?.searchParam || {}) as ReportTask.ReportTaskListParams) ) +const isReportTaskGeneratePending = (row: ReportTask.ReportTaskRecord) => Number(row.reportGenerateState ?? 0) === 0 const columns = reactive[]>([ { type: 'selection', fixed: 'left', width: 70 }, @@ -191,7 +226,7 @@ const columns = reactive[]>([ { prop: 'createTime', label: '创建时间', minWidth: 170, render: ({ row }) => resolveReportTaskText(row.createTime) }, { prop: 'updateByName', label: '更新人', minWidth: 120, isShow: false, render: ({ row }) => resolveReportTaskText(row.updateByName || row.updateBy) }, { prop: 'updateTime', label: '更新时间', minWidth: 170, isShow: false, render: ({ row }) => resolveReportTaskText(row.updateTime) }, - { prop: 'operation', label: '操作', fixed: 'right', width: 280 } + { prop: 'operation', label: '操作', fixed: 'right', width: 360 } ]) const ensureReportTaskDictOptionsReady = async () => { @@ -297,6 +332,27 @@ const openEditDialog = async (row: ReportTask.ReportTaskRecord) => { } } +const loadDetailDialogData = async (id: string) => { + const [detailResponse, groupReportsResponse] = await Promise.all([getReportTaskDetailApi(id), listReportTaskGroupReportsApi(id)]) + + detailRecord.value = unwrapReportTaskPayload(detailResponse) + detailGroupReports.value = normalizeReportTaskGroupReportList(groupReportsResponse) +} + +const refreshDetailDialogDataIfMatched = async (id: string) => { + if (!detailDialogVisible.value) return + + const currentDetailId = String(detailRecord.value?.id || '').trim() + if (currentDetailId !== id) return + + detailLoading.value = true + try { + await loadDetailDialogData(id) + } finally { + detailLoading.value = false + } +} + const openDetailDialog = async (row: ReportTask.ReportTaskRecord) => { if (!row.id) { ElMessage.warning('当前报告任务缺少 ID,无法查看详情') @@ -309,13 +365,7 @@ const openDetailDialog = async (row: ReportTask.ReportTaskRecord) => { detailLoading.value = true try { - const [detailResponse, groupReportsResponse] = await Promise.all([ - getReportTaskDetailApi(row.id), - listReportTaskGroupReportsApi(row.id) - ]) - - detailRecord.value = unwrapReportTaskPayload(detailResponse) - detailGroupReports.value = normalizeReportTaskGroupReportList(groupReportsResponse) + await loadDetailDialogData(row.id) } catch (error) { ElMessage.error(getReportTaskErrorMessage(error, '报告任务详情查询失败')) } finally { @@ -323,6 +373,33 @@ const openDetailDialog = async (row: ReportTask.ReportTaskRecord) => { } } +const openGroupDialog = async (row: ReportTask.ReportTaskRecord) => { + if (!row.id) { + ElMessage.warning('当前报告任务缺少 ID,无法维护分组') + return + } + + if (!isReportTaskGeneratePending(row)) { + ElMessage.warning('当前报告任务已生成过报告,不能再修改分组') + return + } + + currentGroupRecord.value = row + groupPointList.value = [] + groupDialogVisible.value = true + groupDialogLoading.value = true + + try { + const response = await listReportTaskPointsApi(row.id) + groupPointList.value = normalizeReportTaskPointList(response) + } catch (error) { + groupDialogVisible.value = false + ElMessage.error(getReportTaskErrorMessage(error, '监测点列表查询失败')) + } finally { + groupDialogLoading.value = false + } +} + const openAuditDialog = (row: ReportTask.ReportTaskRecord) => { if (!row.id) { ElMessage.warning('当前报告任务缺少 ID,无法提交审核') @@ -368,6 +445,86 @@ const handleSubmitAudit = async (params: ReportTask.ReportTaskAuditRequest) => { } } +const handleSaveGroups = async (params: ReportTask.ReportTaskGroupSaveRequest) => { + const reportId = String(currentGroupRecord.value?.id || '').trim() + if (!reportId) { + ElMessage.warning('当前报告任务缺少 ID,无法保存分组') + return + } + + groupDialogSaving.value = true + try { + await saveReportTaskGroupsApi(reportId, params) + ElMessage.success('监测点分组保存成功') + groupDialogVisible.value = false + refreshReportTasks() + await refreshDetailDialogDataIfMatched(reportId) + } catch (error) { + ElMessage.error(getReportTaskErrorMessage(error, '监测点分组保存失败')) + } finally { + groupDialogSaving.value = false + } +} + +const handleGenerateGroupReports = async (row: ReportTask.ReportTaskRecord) => { + if (!row.id) { + ElMessage.warning('当前报告任务缺少 ID,无法生成报告') + return + } + + if (!isReportTaskGeneratePending(row)) { + ElMessage.warning('当前报告任务不是待生成状态,不能重复生成') + return + } + + if (!row.groupCount) { + ElMessage.warning('请先完成监测点分组后再生成报告') + return + } + + try { + await ElMessageBox.confirm('将按当前分组生成所有报告,是否继续?', '生成报告', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }) + } catch { + return + } + + try { + await generateReportTaskGroupReportsApi(row.id) + ElMessage.success('报告生成任务已执行完成') + refreshReportTasks() + await refreshDetailDialogDataIfMatched(row.id) + } 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 deleteReportTasks = async (ids: string[], successMessage: string) => { if (!ids.length) { ElMessage.warning('请选择要删除的报告任务') diff --git a/frontend/src/views/aireport/testReport/utils/reportTaskFlow.ts b/frontend/src/views/aireport/testReport/utils/reportTaskFlow.ts index bd12021..2a1f38c 100644 --- a/frontend/src/views/aireport/testReport/utils/reportTaskFlow.ts +++ b/frontend/src/views/aireport/testReport/utils/reportTaskFlow.ts @@ -39,12 +39,14 @@ export const REPORT_TASK_FLOW_STEPS: ReportTaskFlowStepOption[] = [ export const createEmptyLedgerPreparedState = (): ReportTask.ReportTaskLedgerPreparedState => ({ draftId: '', + batchId: '', validateSessionId: '', validateResult: null, latestImportResult: null, snapshot: null, hasImported: false, validatedBatchId: '', + pendingReimport: false, pendingRevalidate: false }) diff --git a/frontend/src/views/aireport/testReport/utils/testReport.ts b/frontend/src/views/aireport/testReport/utils/testReport.ts index 155ee5e..49435ad 100644 --- a/frontend/src/views/aireport/testReport/utils/testReport.ts +++ b/frontend/src/views/aireport/testReport/utils/testReport.ts @@ -145,9 +145,13 @@ export const normalizeReportTaskPointList = ( export const normalizeReportTaskLedgerImportResult = ( response: ResultData | ReportTask.ReportTaskLedgerImportResult | null | undefined + | ResultData + | ReportTask.ReportTaskLedgerValidateResult ): ReportTask.ReportTaskLedgerImportResult => { - const payload = unwrapReportTaskPayload( - response as ResultData + const payload = unwrapReportTaskPayload< + Partial | null | undefined + >( + response as ResultData> ) const stageList = Array.isArray(payload?.stages) ? payload.stages : [] @@ -169,7 +173,7 @@ export const normalizeReportTaskLedgerImportResult = ( clientReuseCount: payload?.clientReuseCount ?? 0, groupCount: payload?.groupCount ?? 0, failReasons: Array.isArray(payload?.failReasons) ? payload.failReasons.filter(Boolean) : [], - stages: stageList.map(stage => ({ + stages: stageList.map((stage: ReportTask.ReportTaskLedgerImportStageRecord) => ({ stage: stage?.stage || undefined, success: stage?.success ?? false, message: stage?.message || ''