diff --git a/frontend/build/getEnv.ts b/frontend/build/getEnv.ts index 1487dc5..aa9f42a 100644 --- a/frontend/build/getEnv.ts +++ b/frontend/build/getEnv.ts @@ -30,7 +30,9 @@ export function wrapperEnv(envConf: Recordable): ViteEnv { if (envName === "VITE_PROXY") { try { realName = JSON.parse(realName); - } catch (error) {} + } catch (error) { + // Keep the original string when the proxy config is not valid JSON. + } } ret[envName] = realName; } diff --git a/frontend/src/types/vite-plugin-eslint.d.ts b/frontend/src/types/vite-plugin-eslint.d.ts new file mode 100644 index 0000000..2788d8c --- /dev/null +++ b/frontend/src/types/vite-plugin-eslint.d.ts @@ -0,0 +1,7 @@ +declare module 'vite-plugin-eslint' { + import type { Plugin } from 'vite' + + const eslintPlugin: (options?: Record) => Plugin + + export default eslintPlugin +} diff --git a/frontend/src/views/aireport/testReport/components/ReportTaskBaseInfoStep.vue b/frontend/src/views/aireport/testReport/components/ReportTaskBaseInfoStep.vue new file mode 100644 index 0000000..2620f9a --- /dev/null +++ b/frontend/src/views/aireport/testReport/components/ReportTaskBaseInfoStep.vue @@ -0,0 +1,221 @@ + + + + + diff --git a/frontend/src/views/aireport/testReport/components/ReportTaskCompleteStep.vue b/frontend/src/views/aireport/testReport/components/ReportTaskCompleteStep.vue new file mode 100644 index 0000000..f7c628d --- /dev/null +++ b/frontend/src/views/aireport/testReport/components/ReportTaskCompleteStep.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/frontend/src/views/aireport/testReport/components/ReportTaskImportStep.vue b/frontend/src/views/aireport/testReport/components/ReportTaskImportStep.vue new file mode 100644 index 0000000..a743e38 --- /dev/null +++ b/frontend/src/views/aireport/testReport/components/ReportTaskImportStep.vue @@ -0,0 +1,263 @@ + + + + + diff --git a/frontend/src/views/aireport/testReport/components/ReportTaskPrepareStep.vue b/frontend/src/views/aireport/testReport/components/ReportTaskPrepareStep.vue new file mode 100644 index 0000000..f5685e8 --- /dev/null +++ b/frontend/src/views/aireport/testReport/components/ReportTaskPrepareStep.vue @@ -0,0 +1,588 @@ + + + + + diff --git a/frontend/src/views/aireport/testReport/contracts/check-ledger-summary-file-contract.mjs b/frontend/src/views/aireport/testReport/contracts/check-ledger-summary-file-contract.mjs new file mode 100644 index 0000000..99936c7 --- /dev/null +++ b/frontend/src/views/aireport/testReport/contracts/check-ledger-summary-file-contract.mjs @@ -0,0 +1,40 @@ +import fs from 'node:fs' +import path from 'node:path' +import assert from 'node:assert/strict' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const pageDir = path.resolve(__dirname, '..') +const utilsFile = path.join(pageDir, 'utils/testReport.ts') +const panelFile = path.join(pageDir, 'components/ReportTaskPrepareStep.vue') + +const read = file => fs.readFileSync(file, 'utf8') + +const utilsSource = read(utilsFile) +const panelSource = read(panelFile) + +assert.match( + utilsSource, + /export const TEST_REPORT_LEDGER_SUMMARY_FILE_BASE_NAME = '台账信息汇总'/, + 'utils/testReport.ts should define the ledger summary base file name constant' +) + +assert.match( + utilsSource, + /export const isReportTaskLedgerSummaryFileName = \(fileName: string\) =>[\s\S]*includes\(TEST_REPORT_LEDGER_SUMMARY_FILE_BASE_NAME\.toLowerCase\(\)\)[\s\S]*isReportTaskExcelFileName\(normalizedName\)/, + 'utils/testReport.ts should treat any Excel file containing 台账信息汇总 as the summary ledger file' +) + +assert.match( + panelSource, + /isReportTaskLedgerSummaryFileName/, + 'ReportTaskPrepareStep.vue should reuse the shared summary ledger matcher' +) + +assert.doesNotMatch( + panelSource, + /const isSummaryLedgerFileName =/, + 'ReportTaskPrepareStep.vue should not keep a local exact-match summary file matcher' +) + +console.log('ledger summary file contract passed') diff --git a/frontend/src/views/aireport/testReport/utils/reportTaskFlow.ts b/frontend/src/views/aireport/testReport/utils/reportTaskFlow.ts new file mode 100644 index 0000000..bd12021 --- /dev/null +++ b/frontend/src/views/aireport/testReport/utils/reportTaskFlow.ts @@ -0,0 +1,88 @@ +import type { ReportTask } from '@/api/aireport/testReport/interface' +import { checkReportTaskLedgerPrepared } from './testReport' + +export type ReportTaskFlowStep = 'prepare' | 'import' | 'form' | 'done' + +export interface ReportTaskFlowStepOption { + index: number + value: ReportTaskFlowStep + title: string + description: string +} + +export const REPORT_TASK_FLOW_STEPS: ReportTaskFlowStepOption[] = [ + { + index: 1, + value: 'prepare', + title: '文件选择与校验', + description: '准备台账文件并完成校验' + }, + { + index: 2, + value: 'import', + title: '文件导入', + description: '使用已校验批次执行正式导入' + }, + { + index: 3, + value: 'form', + title: '基础信息维护', + description: '填写并确认任务基础信息' + }, + { + index: 4, + value: 'done', + title: '完成', + description: '查看结果并结束本次流程' + } +] + +export const createEmptyLedgerPreparedState = (): ReportTask.ReportTaskLedgerPreparedState => ({ + draftId: '', + validateSessionId: '', + validateResult: null, + latestImportResult: null, + snapshot: null, + hasImported: false, + validatedBatchId: '', + pendingRevalidate: false +}) + +export const hasValidatedLedgerBatch = (state: ReportTask.ReportTaskLedgerPreparedState | null | undefined) => + Boolean(state?.draftId && state?.validatedBatchId && state?.validateResult?.success && !state?.pendingRevalidate) + +export const resolveInitialFlowStep = ( + mode: 'create' | 'edit', + state: ReportTask.ReportTaskLedgerPreparedState +): ReportTaskFlowStep => { + if (mode === 'edit' && checkReportTaskLedgerPrepared(state)) { + return 'form' + } + + return 'prepare' +} + +export const canReachFlowStep = ( + step: ReportTaskFlowStep, + state: ReportTask.ReportTaskLedgerPreparedState, + currentStep: ReportTaskFlowStep +) => { + if (step === 'prepare') return true + if (step === 'import') { + return hasValidatedLedgerBatch(state) || state.hasImported || Boolean(state.snapshot) || currentStep === 'import' || currentStep === 'form' || currentStep === 'done' + } + if (step === 'form') return checkReportTaskLedgerPrepared(state) || currentStep === 'form' || currentStep === 'done' + return currentStep === 'done' +} + +export const resolveFlowStepStatus = ( + step: ReportTaskFlowStep, + currentStep: ReportTaskFlowStep, + state: ReportTask.ReportTaskLedgerPreparedState +) => { + if (currentStep === step) return 'active' + if (step === 'prepare' && (hasValidatedLedgerBatch(state) || currentStep === 'form' || currentStep === 'done')) return 'done' + if (step === 'import' && (checkReportTaskLedgerPrepared(state) || currentStep === 'done')) return 'done' + if (step === 'form' && currentStep === 'done') return 'done' + return 'pending' +} diff --git a/frontend/src/views/aireport/testReport/utils/testReport.ts b/frontend/src/views/aireport/testReport/utils/testReport.ts index f556e61..b6ff977 100644 --- a/frontend/src/views/aireport/testReport/utils/testReport.ts +++ b/frontend/src/views/aireport/testReport/utils/testReport.ts @@ -1,15 +1,38 @@ +import type { TagProps } from 'element-plus' import type { Dict, ResultData, ResPage } from '@/api/interface' import type { ReportTask } from '@/api/aireport/testReport/interface' +type TagType = TagProps['type'] + export const TEST_REPORT_EXCEL_EXTENSIONS = ['.xls', '.xlsx'] export const TEST_REPORT_WORD_EXTENSIONS = ['.doc', '.docx'] +export const TEST_REPORT_LEDGER_SUMMARY_FILE_BASE_NAME = '台账信息汇总' export const TEST_REPORT_LEDGER_IMPORT_STAGE_ORDER: ReportTask.ReportTaskLedgerImportStage[] = [ '选择文件', '校验文件', - '文件上传', + '文件导入', '基础资料维护', '完成' ] +export const REPORT_TASK_STATE_MAP: Record = { + '01': { text: '编制中', tagType: 'info' }, + '02': { text: '审核中', tagType: 'warning' }, + '03': { text: '已通过', tagType: 'success' }, + '04': { text: '已退回', tagType: 'danger' }, + '05': { text: '已删除', tagType: 'info' } +} +export const REPORT_TASK_STATE_OPTIONS: Array<{ label: string; value: ReportTask.ReportTaskState }> = [ + { label: '编制中', value: '01' }, + { label: '审核中', value: '02' }, + { label: '已通过', value: '03' }, + { label: '已退回', value: '04' }, + { label: '已删除', value: '05' } +] +export const REPORT_TASK_GENERATE_STATE_MAP: Record = { + 0: { text: '未生成', tagType: 'info' }, + 1: { text: '生成中', tagType: 'warning' }, + 2: { text: '已生成', tagType: 'success' } +} export const REPORT_TASK_GROUP_REPORT_GENERATE_STATUS_TEXT_MAP: Record = { 0: '未生成', 1: '生成中', @@ -43,6 +66,7 @@ export const normalizeReportTaskListParams = ( keyword: String(params.keyword || '').trim() || undefined, searchBeginTime: searchBeginTime || params.searchBeginTime || undefined, searchEndTime: searchEndTime || params.searchEndTime || undefined, + state: params.state || undefined, pageNum: params.pageNum || 1, pageSize: params.pageSize || 10 } @@ -88,6 +112,11 @@ export const isReportTaskWordFileName = (fileName: string) => { return TEST_REPORT_WORD_EXTENSIONS.some(extension => normalizedName.endsWith(extension)) } +export const isReportTaskLedgerSummaryFileName = (fileName: string) => { + const normalizedName = String(fileName || '').trim().toLowerCase() + return normalizedName.includes(TEST_REPORT_LEDGER_SUMMARY_FILE_BASE_NAME.toLowerCase()) && isReportTaskExcelFileName(normalizedName) +} + export const isReportTaskLedgerAttachmentFileName = (fileName: string) => isReportTaskExcelFileName(fileName) || isReportTaskWordFileName(fileName) @@ -119,7 +148,9 @@ export const normalizeReportTaskLedgerImportResult = ( currentStage: payload?.currentStage || undefined, success: payload?.success ?? false, summaryFileName: payload?.summaryFileName || undefined, + tempStoragePath: payload?.tempStoragePath || undefined, totalFileCount: payload?.totalFileCount ?? 0, + uploadedFileCount: payload?.uploadedFileCount ?? 0, pointCount: payload?.pointCount ?? 0, excelAttachmentCount: payload?.excelAttachmentCount ?? 0, wordAttachmentCount: payload?.wordAttachmentCount ?? 0, @@ -137,6 +168,61 @@ export const normalizeReportTaskLedgerImportResult = ( } } +export const normalizeReportTaskLedgerValidateResult = ( + response: + | ResultData + | ReportTask.ReportTaskLedgerValidateResult + | null + | undefined +): ReportTask.ReportTaskLedgerValidateResult => { + const payload = unwrapReportTaskPayload( + response as ResultData + ) + + return { + sessionId: payload?.sessionId || undefined, + batchId: payload?.batchId || undefined, + success: payload?.success ?? false, + summaryFileName: payload?.summaryFileName || undefined, + summaryFilePresent: payload?.summaryFilePresent ?? false, + tempStoragePath: payload?.tempStoragePath || undefined, + uploadedFileCount: payload?.uploadedFileCount ?? 0, + pointCount: payload?.pointCount ?? 0, + missingAttachmentCount: payload?.missingAttachmentCount ?? 0, + canContinueUpload: payload?.canContinueUpload ?? false, + requiredSheets: Array.isArray(payload?.requiredSheets) ? payload.requiredSheets.filter(Boolean) : [], + existingSheets: Array.isArray(payload?.existingSheets) ? payload.existingSheets.filter(Boolean) : [], + missingSheets: Array.isArray(payload?.missingSheets) ? payload.missingSheets.filter(Boolean) : [], + orphanAttachmentNames: Array.isArray(payload?.orphanAttachmentNames) ? payload.orphanAttachmentNames.filter(Boolean) : [], + failReasons: Array.isArray(payload?.failReasons) ? payload.failReasons.filter(Boolean) : [], + points: Array.isArray(payload?.points) + ? payload.points.map(point => ({ + rowNo: point?.rowNo ?? null, + pointKey: point?.pointKey || undefined, + substationName: point?.substationName || undefined, + pointName: point?.pointName || undefined, + complete: point?.complete ?? false, + missingAttachmentCount: point?.missingAttachmentCount ?? 0, + testDeviceNoValid: point?.testDeviceNoValid ?? false, + testDeviceNoMessage: point?.testDeviceNoMessage || undefined, + clientUnitNameValid: point?.clientUnitNameValid ?? false, + clientUnitNameMessage: point?.clientUnitNameMessage || undefined, + attachments: Array.isArray(point?.attachments) + ? point.attachments.map(attachment => ({ + attachmentName: attachment?.attachmentName || undefined, + attachmentType: attachment?.attachmentType || undefined, + referenced: attachment?.referenced ?? false, + uploaded: attachment?.uploaded ?? false, + missing: attachment?.missing ?? false, + reuploadAllowed: attachment?.reuploadAllowed ?? false, + message: attachment?.message || undefined + })) + : [] + })) + : [] + } +} + export const normalizeReportTaskGroupReportList = ( response: | ResultData @@ -210,9 +296,21 @@ export const resolveReportTaskGroupReportGenerateStateTagType = (value: number | return REPORT_TASK_GROUP_REPORT_GENERATE_STATUS_TAG_TYPE_MAP[value] } +export const getReportTaskStateText = (state?: string | null) => + REPORT_TASK_STATE_MAP[state as ReportTask.ReportTaskState]?.text || '未知状态' + +export const getReportTaskStateTagType = (state?: string | null): TagType => + REPORT_TASK_STATE_MAP[state as ReportTask.ReportTaskState]?.tagType || 'info' + +export const getReportTaskGenerateStateText = (state?: number | null) => + REPORT_TASK_GENERATE_STATE_MAP[state as ReportTask.ReportTaskGenerateState]?.text || '未知状态' + +export const getReportTaskGenerateStateTagType = (state?: number | null): TagType => + REPORT_TASK_GENERATE_STATE_MAP[state as ReportTask.ReportTaskGenerateState]?.tagType || 'info' + export const checkReportTaskLedgerPrepared = ( state: ReportTask.ReportTaskLedgerPreparedState | null | undefined -) => Boolean(state?.draftId && state.hasImported && !state.pendingReimport) +) => Boolean(state?.draftId && state.hasImported && !state.pendingRevalidate) export const buildDictSelectOptions = (dictData: Dict[] = []) => dictData diff --git a/frontend/src/views/aireport/testReport/utils/testReportForm.ts b/frontend/src/views/aireport/testReport/utils/testReportForm.ts new file mode 100644 index 0000000..03161d6 --- /dev/null +++ b/frontend/src/views/aireport/testReport/utils/testReportForm.ts @@ -0,0 +1,72 @@ +import type { ReportTask } from '@/api/aireport/testReport/interface' +import { generateUUID } from '@/utils' +import { normalizeReportTaskLedgerSnapshot, parseReportTaskStandardIds, stringifyReportTaskStandardIds } 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: '' +}) + +export const buildReportTaskDraftId = (mode: 'create' | 'edit', record: ReportTask.ReportTaskRecord | null) => { + if (mode === 'edit') { + return String(record?.id || '').trim() + } + + return window.crypto?.randomUUID?.() || generateUUID() +} + +export const buildReportTaskFormContext = (mode: 'create' | 'edit', record: ReportTask.ReportTaskRecord | null) => { + const snapshot = normalizeReportTaskLedgerSnapshot(record?.data) + const formModel = createDefaultReportTaskFormModel() + + 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 || '' + + return { + formModel, + snapshot, + draftId: buildReportTaskDraftId(mode, record), + hasPreparedImport: Boolean(snapshot || Number(record?.pointCount) > 0) + } +} + +export const buildReportTaskSavePayload = ( + draftId: string, + formModel: ReportTaskFormModel +): 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: formModel.data.trim(), + phonenumber: formModel.phonenumber.trim() || undefined +}) diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json index 0608b89..7a3f274 100644 --- a/frontend/tsconfig.node.json +++ b/frontend/tsconfig.node.json @@ -5,7 +5,10 @@ "vitest.config.*", "cypress.config.*", "nightwatch.conf.*", - "playwright.config.*" + "playwright.config.*", + "build/**/*.ts", + "build/**/*.d.ts", + "src/types/**/*.d.ts" ], "compilerOptions": { "composite": true, diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 8483278..eb23ced 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,4 +1,5 @@ -import { ConfigEnv, defineConfig, loadEnv, UserConfig } from 'vite' +import { defineConfig, loadEnv } from 'vite' +import type { ConfigEnv, UserConfig } from 'vite' import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' import vue from '@vitejs/plugin-vue' import path from 'path' @@ -61,10 +62,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => { // dts: "src/types/components.d.ts", }), nodePolyfills({ - include: ['crypto'], - globals: { - crypto: true - } + include: ['crypto'] }) ], // 基础配置