2 Commits

Author SHA1 Message Date
dk
6984f66da4 fix(ai-report): 修复报告任务表单字段长度限制和选项值处理问题 2026-07-15 13:26:02 +08:00
dk
fd83d49e66 refactor(aireport): 调整测试报告数据字段类型和初始化值
- 将接口中 data 字段类型从 string 改为 Record<string, unknown> | unknown[]
- 初始化表单数据时将 data 默认值从空字符串改为 '{}'
- 在数据提交前通过 JSON.parse 处理 data 字段
- 统一了表单重置和编辑回填时的数据初始化逻辑
2026-07-14 16:38:44 +08:00
5 changed files with 148 additions and 52 deletions

View File

@@ -53,7 +53,7 @@ export namespace ReportTask {
createUnit: string createUnit: string
contractNumber: string contractNumber: string
standard: string[] standard: string[]
data: string data: Record<string, unknown> | unknown[]
batchId?: string batchId?: string
phonenumber?: string phonenumber?: string
} }

View File

@@ -41,16 +41,32 @@
<div v-else-if="currentStep === 'form'" class="flow-panel"> <div v-else-if="currentStep === 'form'" class="flow-panel">
<div class="form-panel"> <div class="form-panel">
<el-form ref="formRef" :model="formModel" :rules="formRules" label-width="108px" class="report-task-form"> <el-form
ref="formRef"
:model="formModel"
:rules="formRules"
label-width="108px"
class="report-task-form"
>
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :xs="24" :sm="12"> <el-col :xs="24" :sm="12">
<el-form-item label="报告编号" prop="no"> <el-form-item label="报告编号" prop="no">
<el-input v-model="formModel.no" maxlength="32" clearable placeholder="请输入报告编号" /> <el-input
v-model="formModel.no"
maxlength="32"
clearable
placeholder="请输入报告编号"
/>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12"> <el-col :xs="24" :sm="12">
<el-form-item label="委托单位" prop="clientUnitId"> <el-form-item label="委托单位" prop="clientUnitId">
<el-select v-model="formModel.clientUnitId" filterable clearable placeholder="请选择委托单位"> <el-select
v-model="formModel.clientUnitId"
filterable
clearable
placeholder="请选择委托单位"
>
<el-option <el-option
v-for="option in clientUnitSelectOptions" v-for="option in clientUnitSelectOptions"
:key="option.value" :key="option.value"
@@ -62,7 +78,12 @@
</el-col> </el-col>
<el-col :xs="24" :sm="12"> <el-col :xs="24" :sm="12">
<el-form-item label="报告模板" prop="reportModelId"> <el-form-item label="报告模板" prop="reportModelId">
<el-select v-model="formModel.reportModelId" filterable clearable placeholder="请选择报告模板"> <el-select
v-model="formModel.reportModelId"
filterable
clearable
placeholder="请选择报告模板"
>
<el-option <el-option
v-for="option in reportModelSelectOptions" v-for="option in reportModelSelectOptions"
:key="option.value" :key="option.value"
@@ -74,7 +95,12 @@
</el-col> </el-col>
<el-col :xs="24" :sm="12"> <el-col :xs="24" :sm="12">
<el-form-item label="检测公司" prop="createUnit"> <el-form-item label="检测公司" prop="createUnit">
<el-select v-model="formModel.createUnit" filterable clearable placeholder="请选择检测公司"> <el-select
v-model="formModel.createUnit"
filterable
clearable
placeholder="请选择检测公司"
>
<el-option <el-option
v-for="option in companySelectOptions" v-for="option in companySelectOptions"
:key="option.value" :key="option.value"
@@ -86,7 +112,12 @@
</el-col> </el-col>
<el-col :xs="24" :sm="12"> <el-col :xs="24" :sm="12">
<el-form-item label="合同编号" prop="contractNumber"> <el-form-item label="合同编号" prop="contractNumber">
<el-input v-model="formModel.contractNumber" maxlength="32" clearable placeholder="请输入合同编号" /> <el-input
v-model="formModel.contractNumber"
maxlength="32"
clearable
placeholder="请输入合同编号"
/>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12"> <el-col :xs="24" :sm="12">
@@ -110,7 +141,12 @@
</el-col> </el-col>
<el-col :xs="24" :sm="12"> <el-col :xs="24" :sm="12">
<el-form-item label="联系电话" prop="phonenumber"> <el-form-item label="联系电话" prop="phonenumber">
<el-input v-model="formModel.phonenumber" maxlength="32" clearable placeholder="请输入联系电话" /> <el-input
v-model="formModel.phonenumber"
maxlength="32"
clearable
placeholder="请输入联系电话"
/>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@@ -157,7 +193,9 @@
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="监测点数">{{ importSummary.pointCount }}</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.groupCount }}</el-descriptions-item>
<el-descriptions-item label="汇总文件">{{ importSummary.summaryFileName }}</el-descriptions-item> <el-descriptions-item label="汇总文件">
{{ importSummary.summaryFileName }}
</el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
</div> </div>
@@ -165,29 +203,37 @@
<div class="log-panel" :class="{ 'is-collapsed': !logPanelExpanded }"> <div class="log-panel" :class="{ 'is-collapsed': !logPanelExpanded }">
<button type="button" class="log-panel-header" @click="logPanelExpanded = !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> <div class="log-title">日志信息</div>
</button> </button>
<div v-show="logPanelExpanded" class="log-panel-body"> <div v-show="logPanelExpanded" class="log-panel-body">
<div class="log-popover-body"> <div class="log-popover-body">
<div v-if="logEntries.length" class="log-list"> <div v-if="logEntries.length" class="log-list">
<div v-for="entry in logEntries" :key="entry.id" class="log-item"> <div v-for="entry in logEntries" :key="entry.id" class="log-item">
<div class="log-meta"> <div class="log-meta">
<el-tag size="small" :type="resolveLogTagType(entry.type)">{{ resolveLogTypeText(entry.type) }}</el-tag> <el-tag size="small" :type="resolveLogTagType(entry.type)">
<span class="log-time">{{ entry.time }}</span> {{ resolveLogTypeText(entry.type) }}
</el-tag>
<span class="log-time">{{ entry.time }}</span>
</div>
<div class="log-message">{{ entry.message }}</div>
</div> </div>
<div class="log-message">{{ entry.message }}</div>
</div> </div>
</div> <el-empty v-else description="当前暂无日志输出" :image-size="60" />
<el-empty v-else description="当前暂无日志输出" :image-size="60" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<template #footer> <template #footer>
<el-button :disabled="saving" @click="visibleProxy = false">{{ currentStep === 'done' ? '关闭' : '取消' }}</el-button> <el-button :disabled="saving" @click="visibleProxy = false">
{{ currentStep === 'done' ? '关闭' : '取消' }}
</el-button>
<template v-if="currentStep === 'import'"> <template v-if="currentStep === 'import'">
<el-button type="primary" :disabled="!canSubmit" @click="goToFormStep">下一步</el-button> <el-button type="primary" :disabled="!canSubmit" @click="goToFormStep">下一步</el-button>
@@ -211,7 +257,9 @@ import {
checkReportTaskLedgerPrepared, checkReportTaskLedgerPrepared,
getReportTaskErrorMessage, getReportTaskErrorMessage,
normalizeReportTaskLedgerSnapshot, normalizeReportTaskLedgerSnapshot,
normalizeReportTaskSelectedOptionValues,
parseReportTaskStandardIds, parseReportTaskStandardIds,
resolveReportTaskOptionValue,
stringifyReportTaskStandardIds stringifyReportTaskStandardIds
} from '../utils/testReport' } from '../utils/testReport'
@@ -288,7 +336,7 @@ const formModel = reactive({
createUnit: '', createUnit: '',
contractNumber: '', contractNumber: '',
standardIds: [] as string[], standardIds: [] as string[],
data: '', data: '{}',
phonenumber: '' phonenumber: ''
}) })
@@ -388,7 +436,7 @@ const resetForm = () => {
formModel.createUnit = '' formModel.createUnit = ''
formModel.contractNumber = '' formModel.contractNumber = ''
formModel.standardIds = [] formModel.standardIds = []
formModel.data = '' formModel.data = '{}'
formModel.phonenumber = '' formModel.phonenumber = ''
activeDraftId.value = '' activeDraftId.value = ''
currentStep.value = 'import' currentStep.value = 'import'
@@ -432,10 +480,14 @@ const fillForm = async () => {
formModel.no = record?.no || '' formModel.no = record?.no || ''
formModel.clientUnitId = record?.clientUnitId || '' formModel.clientUnitId = record?.clientUnitId || ''
formModel.reportModelId = record?.reportModelId || '' formModel.reportModelId = record?.reportModelId || ''
formModel.createUnit = record?.createUnit || '' // 关键业务节点:编辑态详情可能返回展示名称而不是字典 value这里必须先归一化成真实 option value避免下拉重复补项和提交时报“检测公司不存在”。
formModel.createUnit = resolveReportTaskOptionValue(props.companyOptions, record?.createUnit || '')
formModel.contractNumber = record?.contractNumber || '' formModel.contractNumber = record?.contractNumber || ''
formModel.standardIds = parseReportTaskStandardIds(record?.standard) formModel.standardIds = normalizeReportTaskSelectedOptionValues(
formModel.data = record?.data || '' props.standardOptions,
parseReportTaskStandardIds(record?.standard)
)
formModel.data = record?.data || '{}'
formModel.phonenumber = record?.phonenumber || '' formModel.phonenumber = record?.phonenumber || ''
activeDraftId.value = buildDraftId() activeDraftId.value = buildDraftId()
@@ -548,7 +600,12 @@ const submitForm = async () => {
return return
} }
pushLog('info', props.mode === 'create' ? '开始提交新增任务基础信息并写入台账数据' : '开始提交编辑后的任务基础信息并更新台账数据') pushLog(
'info',
props.mode === 'create'
? '开始提交新增任务基础信息并写入台账数据'
: '开始提交编辑后的任务基础信息并更新台账数据'
)
emit('submit', { emit('submit', {
id: activeDraftId.value || formModel.id || undefined, id: activeDraftId.value || formModel.id || undefined,
no: formModel.no.trim(), no: formModel.no.trim(),
@@ -557,7 +614,7 @@ const submitForm = async () => {
createUnit: formModel.createUnit, createUnit: formModel.createUnit,
contractNumber: formModel.contractNumber.trim(), contractNumber: formModel.contractNumber.trim(),
standard: stringifyReportTaskStandardIds(formModel.standardIds), standard: stringifyReportTaskStandardIds(formModel.standardIds),
data: formModel.data.trim(), data: JSON.parse(formModel.data.trim() || '{}'),
batchId: ledgerPreparedState.value.batchId || undefined, batchId: ledgerPreparedState.value.batchId || undefined,
phonenumber: formModel.phonenumber.trim() || undefined phonenumber: formModel.phonenumber.trim() || undefined
}) })
@@ -891,6 +948,5 @@ const submitForm = async () => {
border-right: none; border-right: none;
border-bottom: 1px solid var(--el-border-color-lighter); border-bottom: 1px solid var(--el-border-color-lighter);
} }
} }
</style> </style>

View File

@@ -28,7 +28,10 @@ export const REPORT_TASK_STATE_OPTIONS: Array<{ label: string; value: ReportTask
{ label: '已退回', value: '04' }, { label: '已退回', value: '04' },
{ label: '已删除', value: '05' } { label: '已删除', value: '05' }
] ]
export const REPORT_TASK_GENERATE_STATE_MAP: Record<ReportTask.ReportTaskGenerateState, { text: string; tagType: TagType }> = { export const REPORT_TASK_GENERATE_STATE_MAP: Record<
ReportTask.ReportTaskGenerateState,
{ text: string; tagType: TagType }
> = {
0: { text: '未生成', tagType: 'info' }, 0: { text: '未生成', tagType: 'info' },
1: { text: '生成中', tagType: 'warning' }, 1: { text: '生成中', tagType: 'warning' },
2: { text: '已生成', tagType: 'success' } 2: { text: '已生成', tagType: 'success' }
@@ -43,11 +46,11 @@ export const REPORT_TASK_GROUP_REPORT_GENERATE_STATUS_TAG_TYPE_MAP: Record<
number, number,
'warning' | 'success' | 'danger' | undefined 'warning' | 'success' | 'danger' | undefined
> = { > = {
0: undefined, 0: undefined,
1: 'warning', 1: 'warning',
2: 'success', 2: 'success',
3: 'danger' 3: 'danger'
} }
export const resolveReportTaskText = (value: unknown) => { export const resolveReportTaskText = (value: unknown) => {
if (value === null || value === undefined) return '-' if (value === null || value === undefined) return '-'
@@ -103,18 +106,27 @@ export const getReportTaskErrorMessage = (error: unknown, fallback: string) => {
} }
export const isReportTaskExcelFileName = (fileName: string) => { export const isReportTaskExcelFileName = (fileName: string) => {
const normalizedName = String(fileName || '').trim().toLowerCase() const normalizedName = String(fileName || '')
.trim()
.toLowerCase()
return TEST_REPORT_EXCEL_EXTENSIONS.some(extension => normalizedName.endsWith(extension)) return TEST_REPORT_EXCEL_EXTENSIONS.some(extension => normalizedName.endsWith(extension))
} }
export const isReportTaskWordFileName = (fileName: string) => { export const isReportTaskWordFileName = (fileName: string) => {
const normalizedName = String(fileName || '').trim().toLowerCase() const normalizedName = String(fileName || '')
.trim()
.toLowerCase()
return TEST_REPORT_WORD_EXTENSIONS.some(extension => normalizedName.endsWith(extension)) return TEST_REPORT_WORD_EXTENSIONS.some(extension => normalizedName.endsWith(extension))
} }
export const isReportTaskLedgerSummaryFileName = (fileName: string) => { export const isReportTaskLedgerSummaryFileName = (fileName: string) => {
const normalizedName = String(fileName || '').trim().toLowerCase() const normalizedName = String(fileName || '')
return normalizedName.includes(TEST_REPORT_LEDGER_SUMMARY_FILE_BASE_NAME.toLowerCase()) && isReportTaskExcelFileName(normalizedName) .trim()
.toLowerCase()
return (
normalizedName.includes(TEST_REPORT_LEDGER_SUMMARY_FILE_BASE_NAME.toLowerCase()) &&
isReportTaskExcelFileName(normalizedName)
)
} }
export const isReportTaskLedgerAttachmentFileName = (fileName: string) => export const isReportTaskLedgerAttachmentFileName = (fileName: string) =>
@@ -132,10 +144,7 @@ export const normalizeReportTaskPointList = (
export const normalizeReportTaskLedgerImportResult = ( export const normalizeReportTaskLedgerImportResult = (
response: response:
| ResultData<ReportTask.ReportTaskLedgerImportResult> ResultData<ReportTask.ReportTaskLedgerImportResult> | ReportTask.ReportTaskLedgerImportResult | null | undefined
| ReportTask.ReportTaskLedgerImportResult
| null
| undefined
): ReportTask.ReportTaskLedgerImportResult => { ): ReportTask.ReportTaskLedgerImportResult => {
const payload = unwrapReportTaskPayload<ReportTask.ReportTaskLedgerImportResult | null | undefined>( const payload = unwrapReportTaskPayload<ReportTask.ReportTaskLedgerImportResult | null | undefined>(
response as ResultData<ReportTask.ReportTaskLedgerImportResult> response as ResultData<ReportTask.ReportTaskLedgerImportResult>
@@ -193,7 +202,9 @@ export const normalizeReportTaskLedgerValidateResult = (
requiredSheets: Array.isArray(payload?.requiredSheets) ? payload.requiredSheets.filter(Boolean) : [], requiredSheets: Array.isArray(payload?.requiredSheets) ? payload.requiredSheets.filter(Boolean) : [],
existingSheets: Array.isArray(payload?.existingSheets) ? payload.existingSheets.filter(Boolean) : [], existingSheets: Array.isArray(payload?.existingSheets) ? payload.existingSheets.filter(Boolean) : [],
missingSheets: Array.isArray(payload?.missingSheets) ? payload.missingSheets.filter(Boolean) : [], missingSheets: Array.isArray(payload?.missingSheets) ? payload.missingSheets.filter(Boolean) : [],
orphanAttachmentNames: Array.isArray(payload?.orphanAttachmentNames) ? payload.orphanAttachmentNames.filter(Boolean) : [], orphanAttachmentNames: Array.isArray(payload?.orphanAttachmentNames)
? payload.orphanAttachmentNames.filter(Boolean)
: [],
failReasons: Array.isArray(payload?.failReasons) ? payload.failReasons.filter(Boolean) : [], failReasons: Array.isArray(payload?.failReasons) ? payload.failReasons.filter(Boolean) : [],
points: Array.isArray(payload?.points) points: Array.isArray(payload?.points)
? payload.points.map(point => ({ ? payload.points.map(point => ({
@@ -308,9 +319,8 @@ export const getReportTaskGenerateStateText = (state?: number | null) =>
export const getReportTaskGenerateStateTagType = (state?: number | null): TagType => export const getReportTaskGenerateStateTagType = (state?: number | null): TagType =>
REPORT_TASK_GENERATE_STATE_MAP[state as ReportTask.ReportTaskGenerateState]?.tagType || 'info' REPORT_TASK_GENERATE_STATE_MAP[state as ReportTask.ReportTaskGenerateState]?.tagType || 'info'
export const checkReportTaskLedgerPrepared = ( export const checkReportTaskLedgerPrepared = (state: ReportTask.ReportTaskLedgerPreparedState | null | undefined) =>
state: ReportTask.ReportTaskLedgerPreparedState | null | undefined Boolean(state?.draftId && state.hasImported && !state.pendingRevalidate)
) => Boolean(state?.draftId && state.hasImported && !state.pendingRevalidate)
export const buildDictSelectOptions = (dictData: Dict[] = []) => export const buildDictSelectOptions = (dictData: Dict[] = []) =>
dictData dictData
@@ -326,6 +336,32 @@ export const buildOptionLabelMap = (options: Array<{ label: string; value: strin
return result return result
}, {}) }, {})
export const resolveReportTaskOptionValue = (
options: Array<{ label: string; value: string }>,
value?: string | null
) => {
const text = String(value || '').trim()
if (!text) return ''
const matchedByValue = options.find(option => option.value === text)
if (matchedByValue) return matchedByValue.value
const matchedByLabel = options.find(option => option.label === text)
return matchedByLabel?.value || text
}
export const normalizeReportTaskSelectedOptionValues = (
options: Array<{ label: string; value: string }>,
values: string[] = []
) =>
values.reduce<string[]>((result, value) => {
const normalizedValue = resolveReportTaskOptionValue(options, value)
if (normalizedValue && !result.includes(normalizedValue)) {
result.push(normalizedValue)
}
return result
}, [])
export const parseReportTaskStandardIds = (value?: string[] | string | null) => { export const parseReportTaskStandardIds = (value?: string[] | string | null) => {
if (Array.isArray(value)) { if (Array.isArray(value)) {
return value.map(item => String(item).trim()).filter(Boolean) return value.map(item => String(item).trim()).filter(Boolean)
@@ -387,7 +423,9 @@ export const checkReportTaskGroupingCompleted = (
const pointIds = pointList.map(point => String(point.id || '').trim()).filter(Boolean) const pointIds = pointList.map(point => String(point.id || '').trim()).filter(Boolean)
if (!pointIds.length) return false if (!pointIds.length) return false
const groupedIds = payload.groups.flatMap(group => group.pointIds.map(pointId => String(pointId || '').trim()).filter(Boolean)) const groupedIds = payload.groups.flatMap(group =>
group.pointIds.map(pointId => String(pointId || '').trim()).filter(Boolean)
)
if (groupedIds.length !== pointIds.length) return false if (groupedIds.length !== pointIds.length) return false
const uniqueGroupedIds = new Set(groupedIds) const uniqueGroupedIds = new Set(groupedIds)
@@ -397,7 +435,9 @@ export const checkReportTaskGroupingCompleted = (
const hasInvalidPointId = Array.from(uniqueGroupedIds).some(pointId => !pointIdSet.has(pointId)) const hasInvalidPointId = Array.from(uniqueGroupedIds).some(pointId => !pointIdSet.has(pointId))
if (hasInvalidPointId) return false if (hasInvalidPointId) return false
return payload.groups.every(group => Number.isInteger(group.groupNo) && group.groupNo > 0 && group.pointIds.length > 0) return payload.groups.every(
group => Number.isInteger(group.groupNo) && group.groupNo > 0 && group.pointIds.length > 0
)
} }
export const resolveReportTaskCreateUnitText = ( export const resolveReportTaskCreateUnitText = (

View File

@@ -22,7 +22,7 @@ export const createDefaultReportTaskFormModel = (): ReportTaskFormModel => ({
createUnit: '', createUnit: '',
contractNumber: '', contractNumber: '',
standardIds: [], standardIds: [],
data: '', data: '{}',
phonenumber: '' phonenumber: ''
}) })
@@ -45,7 +45,7 @@ export const buildReportTaskFormContext = (mode: 'create' | 'edit', record: Repo
formModel.createUnit = record?.createUnit || '' formModel.createUnit = record?.createUnit || ''
formModel.contractNumber = record?.contractNumber || '' formModel.contractNumber = record?.contractNumber || ''
formModel.standardIds = parseReportTaskStandardIds(record?.standard) formModel.standardIds = parseReportTaskStandardIds(record?.standard)
formModel.data = record?.data || '' formModel.data = record?.data || '{}'
formModel.phonenumber = record?.phonenumber || '' formModel.phonenumber = record?.phonenumber || ''
return { return {
@@ -67,6 +67,6 @@ export const buildReportTaskSavePayload = (
createUnit: formModel.createUnit, createUnit: formModel.createUnit,
contractNumber: formModel.contractNumber.trim(), contractNumber: formModel.contractNumber.trim(),
standard: stringifyReportTaskStandardIds(formModel.standardIds), standard: stringifyReportTaskStandardIds(formModel.standardIds),
data: formModel.data.trim(), data: JSON.parse(formModel.data.trim() || '{}'),
phonenumber: formModel.phonenumber.trim() || undefined phonenumber: formModel.phonenumber.trim() || undefined
}) })

View File

@@ -7,10 +7,10 @@
<el-input :value="dictTypeName" disabled/> <el-input :value="dictTypeName" disabled/>
</el-form-item> </el-form-item>
<el-form-item label="名称" :label-width="100" prop="name"> <el-form-item label="名称" :label-width="100" prop="name">
<el-input v-model="formContent.name" placeholder="请输入" autocomplete="off" maxlength="32" show-word-limit/> <el-input v-model="formContent.name" placeholder="请输入" autocomplete="off" maxlength="64" show-word-limit/>
</el-form-item> </el-form-item>
<el-form-item label="编码" :label-width="100" prop="code"> <el-form-item label="编码" :label-width="100" prop="code">
<el-input v-model="formContent.code" placeholder="请输入" autocomplete="off" maxlength="32" show-word-limit/> <el-input v-model="formContent.code" placeholder="请输入" autocomplete="off" maxlength="30" show-word-limit/>
</el-form-item> </el-form-item>
<el-form-item label="开启值描述" :label-width="100" > <el-form-item label="开启值描述" :label-width="100" >
<el-radio-group v-model="formContent.openValue" @change="handleOpenValueChange"> <el-radio-group v-model="formContent.openValue" @change="handleOpenValueChange">