refactor(aireport): 重构报告任务模块数据结构和界面组件

- 扩展 ReportTaskGenerateState 类型定义,增加状态值 3 和 4
- 移除 ReportTaskRecord 中的 clientUnitId、clientUnitName、createUnit、contractNumber、standard、phonenumber 字段
- 新增 multiId、templateDirName、templateDirPath、sortNo 等字段到 ReportTaskRecord
- 更新 ReportTaskAddParam 接口,移除 clientUnitId、createUnit、contractNumber、standard、phonenumber 参数
- 在 PointGroupItem 和 GroupReportRecord 中增加 reportNo、clientUnitId、createUnit、contractNumber、standard、phonenumber 字段
- 移除委托单位和检测公司等字段的显示列和表单验证规则
- 更新基础信息步骤组件,简化表单字段配置
- 调整详情对话框布局,增加分组报告表格中的委托单位、检测公司等列
- 优化分组数据显示逻辑,支持从快照数据获取分组数量
- 更新多报告界面组件,同步调整相关属性传递和数据加载逻辑
This commit is contained in:
dk
2026-07-22 11:36:31 +08:00
parent ed81e58583
commit 5b0088ee7b
12 changed files with 433 additions and 559 deletions

View File

@@ -1,6 +1,6 @@
export namespace ReportTask {
export type ReportTaskState = '01' | '02' | '03' | '04' | '05'
export type ReportTaskGenerateState = 0 | 1 | 2
export type ReportTaskGenerateState = 0 | 1 | 2 | 3 | 4
export type ReportTaskGroupReportGenerateState = 0 | 1 | 2 | 3
export type ReportTaskLedgerImportStage =
| '选择文件'
@@ -12,16 +12,14 @@ export namespace ReportTask {
export interface ReportTaskRecord {
id?: string
multiId?: string | null
no?: string | null
clientUnitId?: string | null
clientUnitName?: string | null
reportModelId?: string | null
reportModelName?: string | null
createUnit?: string | null
contractNumber?: string | null
standard?: string[] | string | null
data?: string | null
phonenumber?: string | null
templateDirName?: string | null
templateDirPath?: string | null
sortNo?: number | null
data?: Record<string, unknown> | unknown[] | string | null
checkId?: string | null
checkerName?: string | null
checkTime?: string | null
@@ -53,14 +51,9 @@ export namespace ReportTask {
export interface ReportTaskAddParam {
id?: string
no: string
clientUnitId: string
reportModelId: string
createUnit: string
contractNumber: string
standard: string[]
data: Record<string, unknown> | unknown[]
batchId?: string
phonenumber?: string
}
export interface ReportTaskSaveRequest extends ReportTaskAddParam {}
@@ -181,11 +174,18 @@ export namespace ReportTask {
protocolCapacity?: string | null
powerSupplyCapacity?: string | null
excelAttachmentName?: string | null
wordAttachmentName?: string | null
}
export interface ReportTaskPointGroupItem {
groupNo: number
pointIds: string[]
reportNo?: string
clientUnitId?: string
createUnit?: string
contractNumber?: string
standard?: string[]
phonenumber?: string
}
export interface ReportTaskGroupSaveRequest {
@@ -195,6 +195,12 @@ export namespace ReportTask {
export interface ReportTaskGroupReportRecord {
id?: string
groupNo?: number | null
reportNo?: string | null
clientUnitId?: string | null
createUnit?: string | null
contractNumber?: string | null
standard?: string[] | string | null
phonenumber?: string | null
reportName?: string | null
reportFileName?: string | null
reportStoragePath?: string | null

View File

@@ -56,12 +56,7 @@ export namespace ReportTaskMulti {
templateDirName: string
templateDirPath: string
no: string
clientUnitId: string
reportModelId: string
createUnit: string
contractNumber: string
standard: string[]
phonenumber?: string
}
export interface ReportTaskMultiCreateParam {

View File

@@ -1,19 +1,12 @@
<template>
<div class="form-panel">
<el-form ref="formRef" :model="formModel" :rules="formRules" label-width="108px" class="report-task-form">
<el-form ref="formRef" :model="localFormModel" :rules="formRules" label-width="108px" class="report-task-form">
<el-row :gutter="16">
<el-col :xs="24" :sm="12">
<el-form-item label="报告编号" prop="no">
<el-input v-model="localFormModel.no" maxlength="32" clearable placeholder="请输入报告编号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
<el-form-item label="委托单位" prop="clientUnitId">
<el-select v-model="localFormModel.clientUnitId" filterable clearable placeholder="请选择委托单位">
<el-option v-for="option in clientUnitSelectOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
<el-form-item label="报告模板" prop="reportModelId">
<el-select v-model="localFormModel.reportModelId" filterable clearable placeholder="请选择报告模板">
@@ -21,61 +14,15 @@
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
<el-form-item label="检测公司" prop="createUnit">
<el-select v-model="localFormModel.createUnit" filterable clearable placeholder="请选择检测公司">
<el-option v-for="option in companySelectOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
<el-form-item label="合同编号" prop="contractNumber">
<el-input v-model="localFormModel.contractNumber" maxlength="32" clearable placeholder="请输入合同编号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
<el-form-item label="测试依据" prop="standardIds">
<el-select v-model="localFormModel.standardIds" multiple filterable clearable collapse-tags placeholder="请选择测试依据">
<el-option v-for="option in standardSelectOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
<el-form-item label="联系电话" prop="phonenumber">
<el-input v-model="localFormModel.phonenumber" maxlength="32" clearable placeholder="请输入联系电话" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="summary-strip">
<div class="summary-item">
<span class="summary-label">导入状态</span>
<el-tag :type="canSubmit ? 'success' : 'warning'">
{{ canSubmit ? '已准备提交' : '待完成正式导入' }}
</el-tag>
</div>
<div class="summary-item">
<span class="summary-label">监测点数</span>
<span class="summary-value">{{ importSummary.pointCount }}</span>
</div>
<div class="summary-item">
<span class="summary-label">分组数</span>
<span class="summary-value">{{ importSummary.groupCount }}</span>
</div>
<div class="summary-item">
<span class="summary-label">汇总文件</span>
<span class="summary-value">{{ importSummary.summaryFileName }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { computed, reactive, ref, watch } from 'vue'
import type { ReportTask } from '@/api/aireport/testReport/interface'
import { checkReportTaskLedgerPrepared } from '../utils/testReport'
import type { ReportTaskFormModel } from '../utils/testReportForm'
defineOptions({
@@ -85,28 +32,19 @@ defineOptions({
const props = defineProps<{
formModel: ReportTaskFormModel
record: ReportTask.ReportTaskRecord | null
clientUnitOptions: Array<{ label: string; value: string }>
reportModelOptions: Array<{ label: string; value: string }>
companyOptions: Array<{ label: string; value: string }>
standardOptions: Array<{ label: string; value: string }>
preparedState: ReportTask.ReportTaskLedgerPreparedState
}>()
const emit = defineEmits<{
(event: 'register-form', value: FormInstance | undefined): void
(event: 'update:form-model', value: ReportTaskFormModel): void
}>()
const formRef = ref<FormInstance>()
const localFormModel = reactive<ReportTaskFormModel>({ ...props.formModel })
const localFormModel = ref<ReportTaskFormModel>({ ...props.formModel })
const formRules: FormRules = {
no: [{ required: true, message: '请输入报告编号', trigger: 'blur' }],
clientUnitId: [{ required: true, message: '请选择委托单位', trigger: 'change' }],
reportModelId: [{ required: true, message: '请选择报告模板', trigger: 'change' }],
createUnit: [{ required: true, message: '请选择检测公司', trigger: 'change' }],
contractNumber: [{ required: true, message: '请输入合同编号', trigger: 'blur' }],
standardIds: [{ required: true, message: '请选择测试依据', trigger: 'change' }]
reportModelId: [{ required: true, message: '请选择报告模板', trigger: 'change' }]
}
const ensureCurrentOption = (
@@ -118,56 +56,29 @@ const ensureCurrentOption = (
return [...options, { label: fallbackLabel || value, value }]
}
const clientUnitSelectOptions = computed(() =>
ensureCurrentOption(props.clientUnitOptions, localFormModel.clientUnitId, props.record?.clientUnitName || '')
)
const reportModelSelectOptions = computed(() =>
ensureCurrentOption(props.reportModelOptions, localFormModel.reportModelId, props.record?.reportModelName || '')
)
const companySelectOptions = computed(() =>
ensureCurrentOption(props.companyOptions, localFormModel.createUnit, props.record?.createUnit || '')
)
const standardSelectOptions = computed(() => {
const optionMap = new Map(props.standardOptions.map(option => [option.value, option]))
const extraOptions = localFormModel.standardIds.filter(item => !optionMap.has(item)).map(item => ({ label: item, value: item }))
return [...props.standardOptions, ...extraOptions]
})
const canSubmit = computed(() => checkReportTaskLedgerPrepared(props.preparedState))
const importSummary = computed(() => {
const importResult = props.preparedState.latestImportResult
const snapshot = props.preparedState.snapshot
return {
pointCount: String(importResult?.pointCount ?? snapshot?.rowCount ?? '-'),
groupCount: String(importResult?.groupCount ?? snapshot?.groupSummary?.length ?? '-'),
summaryFileName: importResult?.summaryFileName || snapshot?.summaryFileName || '-'
}
})
watch(
formRef,
value => {
emit('register-form', value)
},
{ immediate: true }
ensureCurrentOption(props.reportModelOptions, localFormModel.value.reportModelId, props.record?.reportModelName || '')
)
watch(
() => props.formModel,
value => {
Object.assign(localFormModel, value)
localFormModel.value = { ...value }
},
{ immediate: true, deep: true }
)
watch(
() => localFormModel.value,
value => {
emit('update:form-model', { ...value })
},
{ deep: true }
)
watch(
localFormModel,
value => {
emit('update:form-model', { ...value, standardIds: [...value.standardIds] })
},
{ deep: true }
)
defineExpose({
validate: () => formRef.value?.validate()
})
</script>
<style scoped lang="scss">
@@ -178,44 +89,8 @@ watch(
background: var(--el-fill-color-blank);
}
.summary-strip {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
margin-top: 8px;
}
.summary-item {
padding: 12px 14px;
border-radius: 10px;
background: var(--el-fill-color-light);
}
.summary-label {
display: block;
margin-bottom: 6px;
font-size: 12px;
color: var(--el-text-color-secondary);
}
.summary-value {
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
:deep(.report-task-form .el-form-item) {
margin-bottom: 18px;
}
:deep(.el-select),
:deep(.el-input) {
width: 100%;
}
@media (max-width: 900px) {
.summary-strip {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -3,16 +3,15 @@
<el-result
icon="success"
:title="mode === 'create' ? '报告任务新增完成' : '报告任务编辑完成'"
sub-title="文件导入与基础信息保存均已完成本次结果已同步刷新到列表"
sub-title="文件导入与任务基础信息保存均已完成"
/>
<el-descriptions :column="2" border>
<el-descriptions-item label="报告编号">{{ formModel.no || '-' }}</el-descriptions-item>
<el-descriptions-item label="委托单位">{{ resolveCurrentOptionLabel(clientUnitOptions, formModel.clientUnitId) }}</el-descriptions-item>
<el-descriptions-item label="检测公司">{{ resolveCurrentOptionLabel(companyOptions, formModel.createUnit) }}</el-descriptions-item>
<el-descriptions-item label="报告模板">{{ resolveCurrentOptionLabel(reportModelOptions, formModel.reportModelId) }}</el-descriptions-item>
<el-descriptions-item label="监测点数">{{ importSummary.pointCount }}</el-descriptions-item>
<el-descriptions-item label="分组数">{{ importSummary.groupCount }}</el-descriptions-item>
<el-descriptions-item label="汇总文件">{{ importSummary.summaryFileName }}</el-descriptions-item>
<el-descriptions-item label="汇总文件" :span="2">{{ importSummary.summaryFileName }}</el-descriptions-item>
</el-descriptions>
</div>
</template>
@@ -29,8 +28,7 @@ const props = defineProps<{
mode: 'create' | 'edit'
formModel: ReportTaskFormModel
preparedState: ReportTask.ReportTaskLedgerPreparedState
clientUnitOptions: Array<{ label: string; value: string }>
companyOptions: Array<{ label: string; value: string }>
reportModelOptions: Array<{ label: string; value: string }>
}>()
const resolveCurrentOptionLabel = (options: Array<{ label: string; value: string }>, value: string) =>

View File

@@ -1,21 +1,13 @@
<template>
<el-dialog v-model="visibleProxy" title="报告任务详情" width="880px" append-to-body destroy-on-close>
<el-dialog v-model="visibleProxy" title="报告任务详情" width="1200px" append-to-body destroy-on-close>
<div v-loading="loading" class="report-task-detail-dialog">
<el-descriptions class="detail-descriptions" :column="2" border>
<el-descriptions-item label="报告编号">{{ resolveReportTaskText(detail?.no) }}</el-descriptions-item>
<el-descriptions-item label="委托单位">{{ resolveReportTaskText(detail?.clientUnitName) }}</el-descriptions-item>
<el-descriptions-item label="报告模板">{{ resolveReportTaskText(detail?.reportModelName) }}</el-descriptions-item>
<el-descriptions-item label="检测公司">
{{ resolveReportTaskCreateUnitText(detail?.createUnit, companyLabelMap) }}
</el-descriptions-item>
<el-descriptions-item label="合同编号">{{ resolveReportTaskText(detail?.contractNumber) }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ resolveReportTaskText(detail?.phonenumber) }}</el-descriptions-item>
<el-descriptions-item label="测试依据" :span="2">
{{ resolveReportTaskStandardText(detail?.standard, standardLabelMap) }}
</el-descriptions-item>
<el-descriptions-item label="送资数据" :span="2">{{ resolveReportTaskText(detail?.data) }}</el-descriptions-item>
<!-- <el-descriptions-item label="模板目录">{{ resolveReportTaskText(detail?.templateDirName) }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="导入汇总快照数据" :span="2">{{ resolveReportTaskText(detail?.data) }}</el-descriptions-item>-->
<el-descriptions-item label="监测点数量">{{ resolveReportTaskText(detail?.pointCount) }}</el-descriptions-item>
<el-descriptions-item label="分组数量">{{ resolveReportTaskText(detail?.groupCount) }}</el-descriptions-item>
<el-descriptions-item label="分组数量">{{ resolveReportTaskText(displayGroupCount) }}</el-descriptions-item>
<el-descriptions-item label="审核人">{{ resolveReportTaskText(detail?.checkerName) }}</el-descriptions-item>
<el-descriptions-item label="审核时间">{{ resolveReportTaskText(detail?.checkTime) }}</el-descriptions-item>
<el-descriptions-item label="审核意见" :span="2">{{ resolveReportTaskText(detail?.checkResult) }}</el-descriptions-item>
@@ -30,10 +22,28 @@
</el-descriptions>
<div class="group-report-section">
<div class="group-report-title">分组报告生成状态</div>
<div class="group-report-title">分组报告</div>
<el-empty v-if="!groupReports.length" description="暂无分组报告记录" :image-size="88" />
<el-table v-else :data="groupReports" border size="small">
<el-table-column prop="groupNo" label="分组号" width="100" />
<el-table-column prop="groupNo" label="分组号" width="90" />
<el-table-column prop="reportNo" label="报告编号" min-width="160" show-overflow-tooltip>
<template #default="{ row }">{{ resolveReportTaskText(row.reportNo) }}</template>
</el-table-column>
<el-table-column label="委托单位" min-width="180" show-overflow-tooltip>
<template #default="{ row }">{{ resolveReportTaskClientUnitText(row.clientUnitId, clientUnitLabelMap) }}</template>
</el-table-column>
<el-table-column label="检测公司" min-width="160" show-overflow-tooltip>
<template #default="{ row }">{{ resolveReportTaskCreateUnitText(row.createUnit, companyLabelMap) }}</template>
</el-table-column>
<el-table-column prop="contractNumber" label="合同编号" min-width="160" show-overflow-tooltip>
<template #default="{ row }">{{ resolveReportTaskText(row.contractNumber) }}</template>
</el-table-column>
<el-table-column label="检测依据" min-width="220" show-overflow-tooltip>
<template #default="{ row }">{{ resolveReportTaskStandardText(row.standard, standardLabelMap) }}</template>
</el-table-column>
<el-table-column prop="phonenumber" label="联系电话" min-width="140" show-overflow-tooltip>
<template #default="{ row }">{{ resolveReportTaskText(row.phonenumber) }}</template>
</el-table-column>
<el-table-column prop="reportName" label="报告名称" min-width="180" show-overflow-tooltip />
<el-table-column label="生成状态" width="120">
<template #default="{ row }">
@@ -70,6 +80,7 @@
import { computed } from 'vue'
import type { ReportTask } from '@/api/aireport/testReport/interface'
import {
normalizeReportTaskLedgerSnapshot,
resolveReportTaskCreateUnitText,
resolveReportTaskGroupReportGenerateStateTagType,
resolveReportTaskGroupReportGenerateStateText,
@@ -86,6 +97,7 @@ const props = defineProps<{
loading: boolean
detail: ReportTask.ReportTaskRecord | null
groupReports: ReportTask.ReportTaskGroupReportRecord[]
clientUnitLabelMap: Record<string, string>
companyLabelMap: Record<string, string>
standardLabelMap: Record<string, string>
downloadingGroupReportId?: string | null
@@ -101,10 +113,28 @@ const visibleProxy = computed({
set: value => emit('update:visible', value)
})
const clientUnitLabelMap = computed(() => props.clientUnitLabelMap)
const companyLabelMap = computed(() => props.companyLabelMap)
const standardLabelMap = computed(() => props.standardLabelMap)
const groupReports = computed(() => props.groupReports || [])
const downloadingGroupReportId = computed(() => props.downloadingGroupReportId || '')
const detailSnapshot = computed(() => normalizeReportTaskLedgerSnapshot(props.detail?.data as string | null | undefined))
const displayGroupCount = computed(() => {
if (groupReports.value.length > 0) {
return groupReports.value.length
}
const snapshotGroupCount = Number(detailSnapshot.value?.groupSummary?.length ?? 0)
if (Number.isInteger(snapshotGroupCount) && snapshotGroupCount > 0) {
return snapshotGroupCount
}
return props.detail?.groupCount
})
const resolveReportTaskClientUnitText = (value: string | null | undefined, labelMap: Record<string, string>) => {
const text = String(value || '').trim()
if (!text) return '-'
return labelMap[text] || text
}
</script>
<style scoped lang="scss">

View File

@@ -51,29 +51,7 @@
<el-row :gutter="16">
<el-col :xs="24" :sm="12">
<el-form-item label="报告编号" prop="no">
<el-input
v-model="formModel.no"
maxlength="32"
clearable
placeholder="请输入报告编号"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
<el-form-item label="委托单位" prop="clientUnitId">
<el-select
v-model="formModel.clientUnitId"
filterable
clearable
placeholder="请选择委托单位"
>
<el-option
v-for="option in clientUnitSelectOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
<el-input v-model="formModel.no" maxlength="32" clearable placeholder="请输入报告编号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
@@ -93,62 +71,6 @@
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
<el-form-item label="检测公司" prop="createUnit">
<el-select
v-model="formModel.createUnit"
filterable
clearable
placeholder="请选择检测公司"
>
<el-option
v-for="option in companySelectOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
<el-form-item label="合同编号" prop="contractNumber">
<el-input
v-model="formModel.contractNumber"
maxlength="32"
clearable
placeholder="请输入合同编号"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
<el-form-item label="测试依据" prop="standardIds">
<el-select
v-model="formModel.standardIds"
multiple
filterable
clearable
collapse-tags
placeholder="请选择测试依据"
>
<el-option
v-for="option in standardSelectOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
<el-form-item label="联系电话" prop="phonenumber">
<el-input
v-model="formModel.phonenumber"
maxlength="32"
clearable
placeholder="请输入联系电话"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
@@ -180,20 +102,17 @@
<el-result
icon="success"
:title="mode === 'create' ? '报告任务新增完成' : '报告任务编辑完成'"
sub-title="台账校验与基础信息保存均已完成本次结果已同步刷新到列表"
sub-title="台账校验与任务基础信息保存均已完成"
/>
<el-descriptions :column="2" border>
<el-descriptions-item label="报告编号">{{ formModel.no || '-' }}</el-descriptions-item>
<el-descriptions-item label="委托单位">
{{ resolveCurrentOptionLabel(clientUnitSelectOptions, formModel.clientUnitId) }}
</el-descriptions-item>
<el-descriptions-item label="检测公司">
{{ resolveCurrentOptionLabel(companySelectOptions, formModel.createUnit) }}
<el-descriptions-item label="报告模板">
{{ resolveCurrentOptionLabel(reportModelSelectOptions, formModel.reportModelId) }}
</el-descriptions-item>
<el-descriptions-item label="监测点数">{{ importSummary.pointCount }}</el-descriptions-item>
<el-descriptions-item label="分组数">{{ importSummary.groupCount }}</el-descriptions-item>
<el-descriptions-item label="汇总文件">
<el-descriptions-item label="汇总文件" :span="2">
{{ importSummary.summaryFileName }}
</el-descriptions-item>
</el-descriptions>
@@ -203,11 +122,7 @@
<div class="log-panel" :class="{ 'is-collapsed': !logPanelExpanded }">
<button type="button" class="log-panel-header" @click="logPanelExpanded = !logPanelExpanded">
<span
class="log-panel-toggle"
:class="{ 'is-collapsed': !logPanelExpanded }"
aria-hidden="true"
></span>
<span class="log-panel-toggle" :class="{ 'is-collapsed': !logPanelExpanded }" aria-hidden="true"></span>
<div class="log-title">日志信息</div>
</button>
@@ -253,15 +168,7 @@ import { computed, reactive, ref, watch } from 'vue'
import type { ReportTask } from '@/api/aireport/testReport/interface'
import { generateUUID } from '@/utils'
import ReportTaskLedgerImportPanel from './ReportTaskLedgerImportPanel.vue'
import {
checkReportTaskLedgerPrepared,
getReportTaskErrorMessage,
normalizeReportTaskLedgerSnapshot,
normalizeReportTaskSelectedOptionValues,
parseReportTaskStandardIds,
resolveReportTaskOptionValue,
stringifyReportTaskStandardIds
} from '../utils/testReport'
import { checkReportTaskLedgerPrepared, getReportTaskErrorMessage, normalizeReportTaskLedgerSnapshot } from '../utils/testReport'
defineOptions({
name: 'ReportTaskFormDialog'
@@ -276,24 +183,9 @@ const REPORT_TASK_FORM_STEPS: Array<{
title: string
description: string
}> = [
{
index: 1,
value: 'import',
title: '数据导入',
description: '先完成台账与附件校验'
},
{
index: 2,
value: 'form',
title: '基础信息填写',
description: '填写并确认任务基础信息'
},
{
index: 3,
value: 'done',
title: '完成',
description: '查看结果并结束本次流程'
}
{ index: 1, value: 'import', title: '数据导入', description: '先完成台账与附件校验' },
{ index: 2, value: 'form', title: '任务信息', description: '填写任务编号和报告模板' },
{ index: 3, value: 'done', title: '完成', description: '查看结果并结束本次流程' }
]
const props = defineProps<{
@@ -335,22 +227,13 @@ const lastHandledSaveSuccessVersion = ref(0)
const formModel = reactive({
id: '',
no: '',
clientUnitId: '',
reportModelId: '',
createUnit: '',
contractNumber: '',
standardIds: [] as string[],
data: '{}',
phonenumber: ''
data: '{}'
})
const formRules: FormRules = {
no: [{ required: true, message: '请输入报告编号', trigger: 'blur' }],
clientUnitId: [{ required: true, message: '请选择委托单位', trigger: 'change' }],
reportModelId: [{ required: true, message: '请选择报告模板', trigger: 'change' }],
createUnit: [{ required: true, message: '请选择检测公司', trigger: 'change' }],
contractNumber: [{ required: true, message: '请输入合同编号', trigger: 'blur' }],
standardIds: [{ required: true, message: '请选择测试依据', trigger: 'change' }]
reportModelId: [{ required: true, message: '请选择报告模板', trigger: 'change' }]
}
const visibleProxy = computed({
@@ -377,27 +260,12 @@ const ensureCurrentOption = (
fallbackLabel: string
) => {
if (!value || options.some(option => option.value === value)) return options
return [...options, { label: fallbackLabel || value, value }]
}
const clientUnitSelectOptions = computed(() =>
ensureCurrentOption(props.clientUnitOptions, formModel.clientUnitId, props.record?.clientUnitName || '')
)
const reportModelSelectOptions = computed(() =>
ensureCurrentOption(props.reportModelOptions, formModel.reportModelId, props.record?.reportModelName || '')
)
const companySelectOptions = computed(() =>
ensureCurrentOption(props.companyOptions, formModel.createUnit, props.record?.createUnit || '')
)
const standardSelectOptions = computed(() => {
const optionMap = new Map(props.standardOptions.map(option => [option.value, option]))
const extraOptions = formModel.standardIds
.filter(item => !optionMap.has(item))
.map(item => ({ label: item, value: item }))
return [...props.standardOptions, ...extraOptions]
})
const buildLogId = () => window.crypto?.randomUUID?.() || generateUUID()
@@ -435,13 +303,8 @@ const resetForm = () => {
formRef.value?.clearValidate()
formModel.id = ''
formModel.no = ''
formModel.clientUnitId = ''
formModel.reportModelId = ''
formModel.createUnit = ''
formModel.contractNumber = ''
formModel.standardIds = []
formModel.data = '{}'
formModel.phonenumber = ''
activeDraftId.value = ''
currentStep.value = 'import'
logPanelExpanded.value = true
@@ -464,17 +327,15 @@ const buildDraftId = () => {
if (props.mode === 'edit') {
return String(props.record?.id || '').trim()
}
return window.crypto?.randomUUID?.() || generateUUID()
}
const applyInitialStep = (hasPreparedLedger: boolean) => {
if (props.mode === 'edit' && hasPreparedLedger) {
currentStep.value = 'form'
pushLog('info', '编辑场景检测到已有导入结果,默认进入基础信息填写步骤')
pushLog('info', '编辑场景检测到已有导入结果,默认进入任务信息步骤')
return
}
currentStep.value = 'import'
pushLog('info', '已进入数据导入步骤,请先完成台账校验')
}
@@ -482,21 +343,15 @@ const applyInitialStep = (hasPreparedLedger: boolean) => {
const fillForm = async () => {
try {
const record = props.record
const snapshot = normalizeReportTaskLedgerSnapshot(record?.data)
const snapshot = normalizeReportTaskLedgerSnapshot(record?.data as string | null | undefined)
formModel.id = record?.id || ''
formModel.no = record?.no || ''
formModel.clientUnitId = record?.clientUnitId || ''
formModel.reportModelId = record?.reportModelId || ''
// 关键业务节点:编辑态详情可能返回展示名称而不是字典 value这里必须先归一化成真实 option value避免下拉重复补项和提交时报“检测公司不存在”。
formModel.createUnit = resolveReportTaskOptionValue(props.companyOptions, record?.createUnit || '')
formModel.contractNumber = record?.contractNumber || ''
formModel.standardIds = normalizeReportTaskSelectedOptionValues(
props.standardOptions,
parseReportTaskStandardIds(record?.standard)
)
formModel.data = record?.data || '{}'
formModel.phonenumber = record?.phonenumber || ''
formModel.data =
typeof record?.data === 'string' && record.data.trim()
? record.data
: JSON.stringify(record?.data || snapshot || {}, null, 0) || '{}'
activeDraftId.value = buildDraftId()
ledgerPreparedState.value = {
@@ -525,12 +380,11 @@ const fillForm = async () => {
const goToFormStep = () => {
if (!canSubmit.value) {
pushLog('warning', '当前校验尚未完成,不能进入基础信息填写步骤')
pushLog('warning', '当前校验尚未完成,不能进入任务信息步骤')
return
}
currentStep.value = 'form'
pushLog('info', '已切换到基础信息填写步骤')
pushLog('info', '已切换到任务信息步骤')
}
const goToImportStep = () => {
@@ -540,16 +394,11 @@ const goToImportStep = () => {
const handleLedgerPreparedChange = (value: ReportTask.ReportTaskLedgerPreparedState) => {
const wasPrepared = checkReportTaskLedgerPrepared(ledgerPreparedState.value)
const nextState = {
...value,
draftId: activeDraftId.value
}
const nextState = { ...value, draftId: activeDraftId.value }
ledgerPreparedState.value = nextState
if (!wasPrepared && checkReportTaskLedgerPrepared(nextState) && currentStep.value === 'import') {
currentStep.value = 'form'
pushLog('success', '台账校验已完成,已自动进入基础信息填写步骤')
pushLog('success', '台账校验已完成,已自动进入任务信息步骤')
}
}
@@ -591,7 +440,6 @@ watch(
() => props.saveSuccessVersion,
value => {
if (!props.visible || !value || value === lastHandledSaveSuccessVersion.value) return
lastHandledSaveSuccessVersion.value = value
currentStep.value = 'done'
pushLog('success', props.mode === 'create' ? '报告任务新增成功,流程已完成' : '报告任务编辑成功,流程已完成')
@@ -601,34 +449,24 @@ watch(
const submitForm = async () => {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) {
pushLog('warning', '基础信息校验未通过,请检查必填项')
pushLog('warning', '任务信息校验未通过,请检查必填项')
return
}
if (!checkReportTaskLedgerPrepared(ledgerPreparedState.value)) {
const message = '请先完成台账校验后再提交基础信息'
const message = '请先完成台账校验后再提交任务信息'
ElMessage.warning(message)
pushLog('warning', message)
return
}
pushLog(
'info',
props.mode === 'create'
? '开始提交新增任务基础信息并写入台账数据'
: '开始提交编辑后的任务基础信息并更新台账数据'
)
pushLog('info', props.mode === 'create' ? '开始提交新增任务' : '开始提交编辑后的任务信息')
emit('submit', {
id: activeDraftId.value || formModel.id || undefined,
no: formModel.no.trim(),
clientUnitId: formModel.clientUnitId,
reportModelId: formModel.reportModelId,
createUnit: formModel.createUnit,
contractNumber: formModel.contractNumber.trim(),
standard: stringifyReportTaskStandardIds(formModel.standardIds),
data: JSON.parse(formModel.data.trim() || '{}'),
batchId: ledgerPreparedState.value.batchId || undefined,
phonenumber: formModel.phonenumber.trim() || undefined
batchId: ledgerPreparedState.value.batchId || undefined
})
}
</script>
@@ -907,10 +745,6 @@ const submitForm = async () => {
text-overflow: ellipsis;
}
.log-empty {
padding: 18px 0;
}
:deep(.report-task-flow-dialog .el-dialog__body) {
overflow: hidden;
}

View File

@@ -1,9 +1,8 @@
<template>
<el-dialog v-model="visibleProxy" title="监测点分组" width="1120px" append-to-body destroy-on-close>
<el-dialog v-model="visibleProxy" title="监测点分组" width="1380px" append-to-body destroy-on-close>
<div v-loading="loading" class="report-task-group-dialog">
<el-descriptions :column="4" border class="summary-descriptions">
<el-descriptions-item label="报告编号">{{ resolveReportTaskText(record?.no) }}</el-descriptions-item>
<el-descriptions-item label="委托单位">{{ resolveReportTaskText(record?.clientUnitName) }}</el-descriptions-item>
<el-descriptions-item label="监测点数">{{ pointList.length || 0 }}</el-descriptions-item>
<el-descriptions-item label="已分组点位">{{ groupedPointCount }}</el-descriptions-item>
<el-descriptions-item label="分组数量">{{ groupedPayload.groups.length || 0 }}</el-descriptions-item>
@@ -14,7 +13,7 @@
<template v-else>
<el-alert
v-if="!groupingCompleted"
title="请为所有监测点设置大于 0 的分组号后再保存。"
title="台账导入时会读取 Excel 中的分组号,也可在此按需调整。当前存在未分组监测点,请补充分组号后再保存。"
type="warning"
:closable="false"
show-icon
@@ -22,20 +21,8 @@
/>
<div class="toolbar">
<div class="toolbar-title">批量分组</div>
<div class="toolbar-actions">
<el-input-number
v-model="batchGroupNo"
:min="1"
:step="1"
controls-position="right"
placeholder="分组号"
/>
<el-button type="primary" :disabled="!selectedPointIds.length || !batchGroupNo" @click="applyBatchGroupNo">
应用到已选
</el-button>
<el-button :disabled="!selectedPointIds.length" @click="clearSelectedGroups">清空已选</el-button>
</div>
<div class="toolbar-title">监测点分组</div>
<div class="toolbar-tip">分组号默认来自已导入的台账 Excel可在此按需修改</div>
</div>
<el-table
@@ -43,12 +30,9 @@
row-key="id"
border
size="small"
max-height="460"
max-height="420"
class="point-table"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column type="index" label="序号" width="70" />
<el-table-column label="分组号" width="120">
<template #default="{ row }">
<el-input-number
@@ -64,7 +48,118 @@
<el-table-column prop="pointName" label="监测点" min-width="180" show-overflow-tooltip />
<el-table-column prop="monitorTime" label="监测时间" min-width="160" show-overflow-tooltip />
<el-table-column prop="voltageLevel" label="电压等级" min-width="120" show-overflow-tooltip />
<el-table-column prop="excelAttachmentName" label="原始文件" min-width="180" show-overflow-tooltip />
<el-table-column prop="excelAttachmentName" label="Excel原始文件" min-width="220" show-overflow-tooltip />
<el-table-column prop="wordAttachmentName" label="Word原始文件" min-width="220" show-overflow-tooltip>
<template #default="{ row }">
{{ resolveReportTaskText(row.wordAttachmentName) }}
</template>
</el-table-column>
</el-table>
<div class="toolbar secondary-toolbar">
<div class="toolbar-title">分组报告信息</div>
<div class="toolbar-tip">这些字段允许先保存为空执行生成报告时再由后端做完整校验</div>
</div>
<el-empty
v-if="!groupedRows.length"
description="请先完成监测点分组,再维护各分组的报告信息"
:image-size="72"
class="group-empty"
/>
<el-table v-else :data="groupedRows" border size="small" max-height="420" class="group-report-table">
<el-table-column prop="groupNo" label="分组号" width="90" />
<el-table-column prop="pointCount" label="点位数" width="90" />
<el-table-column label="报告编号" min-width="180">
<template #default="{ row }">
<el-input
:model-value="resolveGroupField(row.groupNo, 'reportNo')"
maxlength="32"
clearable
placeholder="请输入报告编号"
@update:model-value="updateGroupField(row.groupNo, 'reportNo', $event)"
/>
</template>
</el-table-column>
<el-table-column label="委托单位" min-width="180">
<template #default="{ row }">
<el-select
:model-value="resolveGroupField(row.groupNo, 'clientUnitId')"
filterable
clearable
placeholder="请选择委托单位"
@update:model-value="updateGroupField(row.groupNo, 'clientUnitId', $event)"
>
<el-option
v-for="option in clientUnitOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="检测公司" min-width="180">
<template #default="{ row }">
<el-select
:model-value="resolveGroupField(row.groupNo, 'createUnit')"
filterable
clearable
placeholder="请选择检测公司"
@update:model-value="updateGroupField(row.groupNo, 'createUnit', $event)"
>
<el-option
v-for="option in companyOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="合同编号" min-width="160">
<template #default="{ row }">
<el-input
:model-value="resolveGroupField(row.groupNo, 'contractNumber')"
maxlength="32"
clearable
placeholder="请输入合同编号"
@update:model-value="updateGroupField(row.groupNo, 'contractNumber', $event)"
/>
</template>
</el-table-column>
<el-table-column label="检测依据" min-width="220">
<template #default="{ row }">
<el-select
:model-value="resolveGroupStandard(row.groupNo)"
multiple
filterable
clearable
collapse-tags
placeholder="请选择检测依据"
@update:model-value="updateGroupStandard(row.groupNo, $event)"
>
<el-option
v-for="option in standardOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="联系电话" min-width="160">
<template #default="{ row }">
<el-input
:model-value="resolveGroupField(row.groupNo, 'phonenumber')"
maxlength="32"
clearable
placeholder="请输入联系电话"
@update:model-value="updateGroupField(row.groupNo, 'phonenumber', $event)"
/>
</template>
</el-table-column>
</el-table>
</template>
</div>
@@ -80,22 +175,31 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import type { ReportTask } from '@/api/aireport/testReport/interface'
import {
checkReportTaskGroupingCompleted,
normalizeReportTaskGroupSavePayload,
resolveReportTaskText
} from '../utils/testReport'
import { normalizeReportTaskGroupSavePayload, parseReportTaskStandardIds, resolveReportTaskText } from '../utils/testReport'
defineOptions({
name: 'ReportTaskGroupDialog'
})
type GroupConfig = {
reportNo: string
clientUnitId: string
createUnit: string
contractNumber: string
standard: string[]
phonenumber: string
}
const props = defineProps<{
visible: boolean
loading: boolean
saving: boolean
record: ReportTask.ReportTaskRecord | null
pointList: ReportTask.ReportTaskPointRecord[]
groupReports: ReportTask.ReportTaskGroupReportRecord[]
clientUnitOptions: Array<{ label: string; value: string }>
companyOptions: Array<{ label: string; value: string }>
standardOptions: Array<{ label: string; value: string }>
}>()
const emit = defineEmits<{
@@ -104,51 +208,109 @@ const emit = defineEmits<{
}>()
const localPointGroupMap = ref<Record<string, number | null>>({})
const selectedPointIds = ref<string[]>([])
const batchGroupNo = ref<number | null>(null)
const localGroupConfigMap = ref<Record<number, GroupConfig>>({})
const visibleProxy = computed({
get: () => props.visible,
set: value => emit('update:visible', value)
})
const createEmptyGroupConfig = (): GroupConfig => ({
reportNo: '',
clientUnitId: '',
createUnit: '',
contractNumber: '',
standard: [],
phonenumber: ''
})
const initializePointGroupMap = () => {
const nextGroupMap: Record<string, number | null> = {}
props.pointList.forEach(point => {
const pointId = String(point.id || '').trim()
if (!pointId) return
const groupNo = Number(point.groupNo)
nextGroupMap[pointId] = Number.isInteger(groupNo) && groupNo > 0 ? groupNo : null
})
localPointGroupMap.value = nextGroupMap
selectedPointIds.value = []
batchGroupNo.value = null
}
const initializeGroupConfigMap = () => {
const nextConfigMap: Record<number, GroupConfig> = {}
;(props.groupReports || []).forEach(groupReport => {
const groupNo = Number(groupReport.groupNo)
if (!Number.isInteger(groupNo) || groupNo <= 0) return
nextConfigMap[groupNo] = {
reportNo: String(groupReport.reportNo || ''),
clientUnitId: String(groupReport.clientUnitId || ''),
createUnit: String(groupReport.createUnit || ''),
contractNumber: String(groupReport.contractNumber || ''),
standard: parseReportTaskStandardIds(groupReport.standard),
phonenumber: String(groupReport.phonenumber || '')
}
})
localGroupConfigMap.value = nextConfigMap
}
const syncGroupedConfigMap = () => {
const activeGroupNos = new Set<number>()
Object.values(localPointGroupMap.value).forEach(groupNo => {
const normalizedGroupNo = Number(groupNo)
if (Number.isInteger(normalizedGroupNo) && normalizedGroupNo > 0) {
activeGroupNos.add(normalizedGroupNo)
}
})
const nextConfigMap: Record<number, GroupConfig> = {}
Array.from(activeGroupNos)
.sort((a, b) => a - b)
.forEach(groupNo => {
nextConfigMap[groupNo] = localGroupConfigMap.value[groupNo] || createEmptyGroupConfig()
})
localGroupConfigMap.value = nextConfigMap
}
watch(
() => [props.visible, props.pointList],
() => [props.visible, props.pointList, props.groupReports],
([visible]) => {
if (visible) {
initializePointGroupMap()
}
if (!visible) return
initializePointGroupMap()
initializeGroupConfigMap()
syncGroupedConfigMap()
},
{ immediate: true, deep: true }
)
const groupedPayload = computed(() => normalizeReportTaskGroupSavePayload(props.pointList, localPointGroupMap.value))
const groupingCompleted = computed(() => checkReportTaskGroupingCompleted(props.pointList, groupedPayload.value))
watch(
() => localPointGroupMap.value,
() => {
syncGroupedConfigMap()
},
{ deep: true }
)
const groupedPayload = computed(() => normalizeReportTaskGroupSavePayload(props.pointList, localPointGroupMap.value, localGroupConfigMap.value))
const groupingCompleted = computed(() => {
if (!props.pointList.length || !groupedPayload.value.groups.length) return false
const pointIds = props.pointList.map(point => String(point.id || '').trim()).filter(Boolean)
const groupedIds = groupedPayload.value.groups.flatMap(group => group.pointIds.map(pointId => String(pointId || '').trim()).filter(Boolean))
if (groupedIds.length !== pointIds.length) return false
return new Set(groupedIds).size === pointIds.length
})
const groupedPointCount = computed(() =>
props.pointList.reduce((count, point) => {
const pointId = String(point.id || '').trim()
if (!pointId) return count
const groupNo = Number(localPointGroupMap.value[pointId])
return Number.isInteger(groupNo) && groupNo > 0 ? count + 1 : count
}, 0)
)
const groupedRows = computed(() =>
groupedPayload.value.groups.map(group => ({
groupNo: group.groupNo,
pointCount: group.pointIds.length
}))
)
const resolvePointGroupNo = (pointId?: string) => {
const normalizedPointId = String(pointId || '').trim()
@@ -159,7 +321,6 @@ const resolvePointGroupNo = (pointId?: string) => {
const updatePointGroupNo = (pointId: string | undefined, value: number | null | undefined) => {
const normalizedPointId = String(pointId || '').trim()
if (!normalizedPointId) return
const groupNo = Number(value)
localPointGroupMap.value = {
...localPointGroupMap.value,
@@ -171,27 +332,42 @@ const handlePointGroupNoChange = (pointId: string | undefined, value: number | n
updatePointGroupNo(pointId, value)
}
const handleSelectionChange = (selection: ReportTask.ReportTaskPointRecord[]) => {
selectedPointIds.value = selection.map(item => String(item.id || '').trim()).filter(Boolean)
const ensureGroupConfig = (groupNo: number) => {
if (!localGroupConfigMap.value[groupNo]) {
localGroupConfigMap.value = {
...localGroupConfigMap.value,
[groupNo]: createEmptyGroupConfig()
}
}
return localGroupConfigMap.value[groupNo]
}
const applyBatchGroupNo = () => {
const groupNo = Number(batchGroupNo.value)
if (!Number.isInteger(groupNo) || groupNo <= 0) return
const resolveGroupField = (groupNo: number, field: keyof Omit<GroupConfig, 'standard'>) =>
ensureGroupConfig(groupNo)[field] || ''
const nextGroupMap = { ...localPointGroupMap.value }
selectedPointIds.value.forEach(pointId => {
nextGroupMap[pointId] = groupNo
})
localPointGroupMap.value = nextGroupMap
const resolveGroupStandard = (groupNo: number) => ensureGroupConfig(groupNo).standard || []
const updateGroupField = (groupNo: number, field: keyof Omit<GroupConfig, 'standard'>, value: unknown) => {
const groupConfig = ensureGroupConfig(groupNo)
localGroupConfigMap.value = {
...localGroupConfigMap.value,
[groupNo]: {
...groupConfig,
[field]: String(value || '')
}
}
}
const clearSelectedGroups = () => {
const nextGroupMap = { ...localPointGroupMap.value }
selectedPointIds.value.forEach(pointId => {
nextGroupMap[pointId] = null
})
localPointGroupMap.value = nextGroupMap
const updateGroupStandard = (groupNo: number, value: unknown) => {
const groupConfig = ensureGroupConfig(groupNo)
const standard = Array.isArray(value) ? value.map(item => String(item || '').trim()).filter(Boolean) : []
localGroupConfigMap.value = {
...localGroupConfigMap.value,
[groupNo]: {
...groupConfig,
standard
}
}
}
const handleSubmit = () => {
@@ -206,7 +382,9 @@ const handleSubmit = () => {
.summary-descriptions,
.section-alert,
.point-table {
.point-table,
.group-report-table,
.group-empty {
margin-top: 16px;
}
@@ -219,16 +397,23 @@ const handleSubmit = () => {
flex-wrap: wrap;
}
.secondary-toolbar {
margin-top: 20px;
}
.toolbar-title {
font-size: 15px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.toolbar-actions {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
.toolbar-tip {
font-size: 12px;
color: var(--el-text-color-secondary);
}
:deep(.el-select),
:deep(.el-input) {
width: 100%;
}
</style>

View File

@@ -59,6 +59,7 @@
:loading="detailLoading"
:detail="detailRecord"
:group-reports="detailGroupReports"
:client-unit-label-map="clientUnitLabelMap"
:company-label-map="companyLabelMap"
:standard-label-map="standardLabelMap"
:downloading-group-report-id="downloadingGroupReportId"
@@ -78,6 +79,10 @@
:saving="groupDialogSaving"
:record="currentGroupRecord"
:point-list="groupPointList"
:group-reports="groupReportList"
:client-unit-options="clientUnitOptions"
:company-options="companyOptions"
:standard-options="standardOptions"
@submit="handleSaveGroups"
/>
</div>
@@ -125,8 +130,6 @@ import {
normalizeReportTaskListParams,
normalizeReportTaskPage,
normalizeReportTaskPointList,
resolveReportTaskCreateUnitText,
resolveReportTaskStandardText,
resolveReportTaskText,
unwrapReportTaskPayload
} from './utils/testReport'
@@ -154,10 +157,12 @@ const detailGroupReports = ref<ReportTask.ReportTaskGroupReportRecord[]>([])
const currentAuditRecord = ref<ReportTask.ReportTaskRecord | null>(null)
const currentGroupRecord = ref<ReportTask.ReportTaskRecord | null>(null)
const groupPointList = ref<ReportTask.ReportTaskPointRecord[]>([])
const groupReportList = ref<ReportTask.ReportTaskGroupReportRecord[]>([])
const clientUnitOptions = ref<Array<{ label: string; value: string }>>([])
const reportModelOptions = ref<Array<{ label: string; value: string }>>([])
const downloadingGroupReportId = ref('')
const clientUnitLabelMap = computed(() => buildOptionLabelMap(clientUnitOptions.value))
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))
@@ -180,7 +185,7 @@ const columns = reactive<ColumnProps<ReportTask.ReportTaskRecord>[]>([
el: 'input',
order: 1,
props: {
placeholder: '请输入报告编号、合同编号、委托单位或模板'
placeholder: '请输入报告编号或模板'
}
}
},
@@ -202,14 +207,7 @@ const columns = reactive<ColumnProps<ReportTask.ReportTaskRecord>[]>([
}
},
{ prop: 'no', label: '报告编号', minWidth: 180, fixed: 'left', render: ({ row }) => resolveReportTaskText(row.no) },
{ prop: 'clientUnitName', label: '委托单位', minWidth: 160, render: ({ row }) => resolveReportTaskText(row.clientUnitName) },
{ prop: 'reportModelName', label: '报告模板', minWidth: 160, render: ({ row }) => resolveReportTaskText(row.reportModelName) },
{
prop: 'createUnit',
label: '检测公司',
minWidth: 160,
render: ({ row }) => resolveReportTaskCreateUnitText(row.createUnit, companyLabelMap.value)
},
{
prop: 'state',
label: '报告状态',
@@ -315,7 +313,6 @@ const openEditDialog = async (row: ReportTask.ReportTaskRecord) => {
formMode.value = 'edit'
try {
// 关键业务节点:编辑表单提交仍依赖原始 clientUnitId、reportModelId、createUnit、standard 等字段,不能只回填列表展示文本。
const response = await getReportTaskDetailApi(row.id)
currentRecord.value = unwrapReportTaskPayload<ReportTask.ReportTaskRecord>(response)
formDialogVisible.value = true
@@ -351,6 +348,7 @@ const openDetailDialog = async (row: ReportTask.ReportTaskRecord) => {
return
}
await ensureFormOptionsReady()
detailRecord.value = null
detailGroupReports.value = []
detailDialogVisible.value = true
@@ -378,15 +376,21 @@ const openGroupDialog = async (row: ReportTask.ReportTaskRecord) => {
currentGroupRecord.value = row
groupPointList.value = []
groupReportList.value = []
groupDialogVisible.value = true
groupDialogLoading.value = true
try {
const response = await listReportTaskPointsApi(row.id)
groupPointList.value = normalizeReportTaskPointList(response)
await ensureFormOptionsReady()
const [pointResponse, groupReportResponse] = await Promise.all([
listReportTaskPointsApi(row.id),
listReportTaskGroupReportsApi(row.id)
])
groupPointList.value = normalizeReportTaskPointList(pointResponse)
groupReportList.value = normalizeReportTaskGroupReportList(groupReportResponse)
} catch (error) {
groupDialogVisible.value = false
ElMessage.error(getReportTaskErrorMessage(error, '监测点列表查询失败'))
ElMessage.error(getReportTaskErrorMessage(error, '监测点分组数据查询失败'))
} finally {
groupDialogLoading.value = false
}
@@ -565,9 +569,9 @@ const handleExport = async () => {
onMounted(async () => {
try {
await ensureReportTaskDictOptionsReady()
await ensureFormOptionsReady()
} catch (error) {
ElMessage.error(getReportTaskErrorMessage(error, '报告任务字典加载失败'))
ElMessage.error(getReportTaskErrorMessage(error, '报告任务关联选项加载失败'))
}
})
</script>

View File

@@ -33,8 +33,10 @@ export const REPORT_TASK_GENERATE_STATE_MAP: Record<
{ text: string; tagType: TagType }
> = {
0: { text: '未生成', tagType: 'info' },
1: { text: '生成中', tagType: 'warning' },
2: { text: '已生成', tagType: 'success' }
1: { text: '部分成功', tagType: 'warning' },
2: { text: '全部成功', tagType: 'success' },
3: { text: '生成中', tagType: 'warning' },
4: { text: '生成失败', tagType: 'danger' }
}
export const REPORT_TASK_GROUP_REPORT_GENERATE_STATUS_TEXT_MAP: Record<number, string> = {
0: '未生成',
@@ -249,7 +251,12 @@ export const normalizeReportTaskGroupReportList = (
response as ResultData<ReportTask.ReportTaskGroupReportRecord[]>
)
return Array.isArray(payload) ? payload : []
return Array.isArray(payload)
? payload.map(item => ({
...item,
standard: Array.isArray(item?.standard) ? item.standard : parseReportTaskStandardIds(item?.standard)
}))
: []
}
export const normalizeReportTaskLedgerSnapshot = (
@@ -391,7 +398,18 @@ export const stringifyReportTaskStandardIds = (value: string[]) =>
export const normalizeReportTaskGroupSavePayload = (
pointList: ReportTask.ReportTaskPointRecord[],
pointGroupMap: Record<string, number | string | null | undefined>
pointGroupMap: Record<string, number | string | null | undefined>,
groupConfigMap: Record<
number,
{
reportNo?: string
clientUnitId?: string
createUnit?: string
contractNumber?: string
standard?: string[]
phonenumber?: string
}
> = {}
): ReportTask.ReportTaskGroupSaveRequest => {
const groupedPointMap = new Map<number, string[]>()
@@ -413,7 +431,15 @@ export const normalizeReportTaskGroupSavePayload = (
.sort(([groupNoA], [groupNoB]) => groupNoA - groupNoB)
.map(([groupNo, pointIds]) => ({
groupNo,
pointIds
pointIds,
reportNo: String(groupConfigMap[groupNo]?.reportNo || '').trim() || undefined,
clientUnitId: String(groupConfigMap[groupNo]?.clientUnitId || '').trim() || undefined,
createUnit: String(groupConfigMap[groupNo]?.createUnit || '').trim() || undefined,
contractNumber: String(groupConfigMap[groupNo]?.contractNumber || '').trim() || undefined,
standard: Array.isArray(groupConfigMap[groupNo]?.standard)
? groupConfigMap[groupNo].standard!.map(item => String(item || '').trim()).filter(Boolean)
: [],
phonenumber: String(groupConfigMap[groupNo]?.phonenumber || '').trim() || undefined
}))
}
}

View File

@@ -1,29 +1,19 @@
import type { ReportTask } from '@/api/aireport/testReport/interface'
import { generateUUID } from '@/utils'
import { normalizeReportTaskLedgerSnapshot, parseReportTaskStandardIds, stringifyReportTaskStandardIds } from './testReport'
import { normalizeReportTaskLedgerSnapshot } from './testReport'
export interface ReportTaskFormModel {
id: string
no: string
clientUnitId: string
reportModelId: string
createUnit: string
contractNumber: string
standardIds: string[]
data: string
phonenumber: string
}
export const createDefaultReportTaskFormModel = (): ReportTaskFormModel => ({
id: '',
no: '',
clientUnitId: '',
reportModelId: '',
createUnit: '',
contractNumber: '',
standardIds: [],
data: '{}',
phonenumber: ''
data: '{}'
})
export const buildReportTaskDraftId = (mode: 'create' | 'edit', record: ReportTask.ReportTaskRecord | null) => {
@@ -40,13 +30,8 @@ export const buildReportTaskFormContext = (mode: 'create' | 'edit', record: Repo
formModel.id = record?.id || ''
formModel.no = record?.no || ''
formModel.clientUnitId = record?.clientUnitId || ''
formModel.reportModelId = record?.reportModelId || ''
formModel.createUnit = record?.createUnit || ''
formModel.contractNumber = record?.contractNumber || ''
formModel.standardIds = parseReportTaskStandardIds(record?.standard)
formModel.data = record?.data || '{}'
formModel.phonenumber = record?.phonenumber || ''
formModel.data = typeof record?.data === 'string' ? record.data : JSON.stringify(record?.data || {})
return {
formModel,
@@ -62,11 +47,6 @@ export const buildReportTaskSavePayload = (
): ReportTask.ReportTaskSaveRequest => ({
id: draftId || formModel.id || undefined,
no: formModel.no.trim(),
clientUnitId: formModel.clientUnitId,
reportModelId: formModel.reportModelId,
createUnit: formModel.createUnit,
contractNumber: formModel.contractNumber.trim(),
standard: stringifyReportTaskStandardIds(formModel.standardIds),
data: JSON.parse(formModel.data.trim() || '{}'),
phonenumber: formModel.phonenumber.trim() || undefined
data: JSON.parse(formModel.data.trim() || '{}')
})

View File

@@ -96,18 +96,6 @@
<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="请选择报告模板">
@@ -120,47 +108,6 @@
</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>
@@ -202,10 +149,7 @@ const props = defineProps<{
zipValidating: boolean
createSaving: boolean
validateResult: ReportTaskMulti.ReportTaskMultiValidateResult | null
clientUnitOptions: SelectOption[]
reportModelOptions: SelectOption[]
companyOptions: SelectOption[]
standardOptions: SelectOption[]
}>()
const emit = defineEmits<{
@@ -252,12 +196,7 @@ const buildChildConfigForms = (templateDirs: ReportTaskMulti.ReportTaskMultiTemp
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),
@@ -313,11 +252,7 @@ const validateCreateForm = () => {
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 ''
@@ -337,12 +272,7 @@ const handleCreate = () => {
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
reportModelId: item.reportModelId
}))
})
}

View File

@@ -17,10 +17,7 @@
: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"
@@ -96,6 +93,7 @@
:loading="childDetailLoading"
:detail="childDetailRecord"
:group-reports="childDetailGroupReports"
:client-unit-label-map="clientUnitLabelMap"
:company-label-map="companyLabelMap"
:standard-label-map="standardLabelMap"
:downloading-group-report-id="downloadingGroupReportId"
@@ -108,6 +106,10 @@
:saving="childGroupSaving"
:record="currentChildGroupRecord"
:point-list="childGroupPointList"
:group-reports="childGroupReports"
:client-unit-options="clientUnitOptions"
:company-options="companyOptions"
:standard-options="standardOptions"
@submit="handleSaveChildGroups"
/>
</div>
@@ -192,6 +194,7 @@ 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 childGroupReports = ref<ReportTask.ReportTaskGroupReportRecord[]>([])
const downloadingMultiId = ref('')
const downloadingGroupReportId = ref('')
const currentChildGroupRecord = ref<ReportTask.ReportTaskRecord | null>(null)
@@ -199,6 +202,7 @@ const childGroupPointList = ref<ReportTask.ReportTaskPointRecord[]>([])
const clientUnitOptions = ref<Array<{ label: string; value: string }>>([])
const reportModelOptions = ref<Array<{ label: string; value: string }>>([])
const clientUnitLabelMap = computed(() => buildOptionLabelMap(clientUnitOptions.value))
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))
@@ -415,6 +419,7 @@ const openChildDetailDialog = async (row: ReportTask.ReportTaskRecord) => {
childDetailDialogVisible.value = true
childDetailLoading.value = true
try {
await ensureCreateOptionsReady()
const [detailResponse, groupReportsResponse] = await Promise.all([
getReportTaskDetailApi(reportId),
listReportTaskGroupReportsApi(reportId)
@@ -439,14 +444,20 @@ const openChildGroupDialog = async (row: ReportTask.ReportTaskRecord) => {
}
currentChildGroupRecord.value = row
childGroupPointList.value = []
childGroupReports.value = []
childGroupDialogVisible.value = true
childGroupLoading.value = true
try {
const response = await listReportTaskPointsApi(reportId)
childGroupPointList.value = normalizeReportTaskPointList(response)
await ensureCreateOptionsReady()
const [pointResponse, groupReportResponse] = await Promise.all([
listReportTaskPointsApi(reportId),
listReportTaskGroupReportsApi(reportId)
])
childGroupPointList.value = normalizeReportTaskPointList(pointResponse)
childGroupReports.value = normalizeReportTaskGroupReportList(groupReportResponse)
} catch (error) {
childGroupDialogVisible.value = false
ElMessage.error(getReportTaskErrorMessage(error, '子报告监测点查询失败'))
ElMessage.error(getReportTaskErrorMessage(error, '子报告分组数据查询失败'))
} finally {
childGroupLoading.value = false
}
@@ -560,7 +571,7 @@ const handleBatchDelete = async (ids: string[]) => {
onMounted(async () => {
try {
await ensureDictOptionsReady()
await Promise.all([ensureDictOptionsReady(), loadClientUnitOptions()])
} catch (error) {
ElMessage.error(getReportTaskErrorMessage(error, '字典加载失败'))
}