feat: 新增通用导入中心
This commit is contained in:
92
apps/server/src/imports/imports.helpers.ts
Normal file
92
apps/server/src/imports/imports.helpers.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import * as ExcelJS from 'exceljs';
|
||||
import type { CellValue } from './imports.types';
|
||||
|
||||
export function parseJson<T>(raw: string | null | undefined): T | null {
|
||||
if (!raw) return null;
|
||||
try {
|
||||
return JSON.parse(raw) as T;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function textValue(value: CellValue): string {
|
||||
if (value === null || value === undefined) return '';
|
||||
return String(value).trim();
|
||||
}
|
||||
|
||||
export function normalizeHeader(value: string): string {
|
||||
return value
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[\s()()]/g, '');
|
||||
}
|
||||
|
||||
export function headerMatches(header: string, alias: string): boolean {
|
||||
const h = normalizeHeader(header);
|
||||
const a = normalizeHeader(alias);
|
||||
if (!h || !a) return false;
|
||||
return h === a || h.includes(a) || a.includes(h);
|
||||
}
|
||||
|
||||
export function cellValue(cell: ExcelJS.Cell | undefined): CellValue {
|
||||
if (!cell) return null;
|
||||
const value = cell.value;
|
||||
if (value === null || value === undefined) return null;
|
||||
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
if (value instanceof Date) return value;
|
||||
if (typeof value === 'object') {
|
||||
const candidate = value as { text?: unknown; result?: unknown };
|
||||
if (typeof candidate.text === 'string') return candidate.text;
|
||||
if (typeof candidate.result === 'string' || typeof candidate.result === 'number') {
|
||||
return candidate.result;
|
||||
}
|
||||
if (candidate.result instanceof Date) return candidate.result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function parseDateValue(value: CellValue): string | null {
|
||||
if (value instanceof Date && !Number.isNaN(value.getTime())) {
|
||||
return value.toISOString().slice(0, 10);
|
||||
}
|
||||
const raw = textValue(value);
|
||||
if (!raw) return null;
|
||||
const match = /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})/.exec(raw);
|
||||
if (!match) return null;
|
||||
const year = Number(match[1]);
|
||||
const month = Number(match[2]);
|
||||
const day = Number(match[3]);
|
||||
const date = new Date(Date.UTC(year, month - 1, day));
|
||||
if (
|
||||
date.getUTCFullYear() !== year ||
|
||||
date.getUTCMonth() !== month - 1 ||
|
||||
date.getUTCDate() !== day
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
export function safeError(error: unknown): string {
|
||||
if (error instanceof Error) return error.message.slice(0, 120);
|
||||
return '未知错误';
|
||||
}
|
||||
|
||||
export function applyString(target: object, key: string, value: CellValue): void {
|
||||
const text = textValue(value);
|
||||
if (text) (target as Record<string, unknown>)[key] = text;
|
||||
}
|
||||
|
||||
export function optionalNumber(value: CellValue): number | null {
|
||||
const text = textValue(value);
|
||||
if (!text) return null;
|
||||
const parsed = Number(text);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
export function csvCell(value: string): string {
|
||||
return `"${value.replace(/"/g, '""')}"`;
|
||||
}
|
||||
Reference in New Issue
Block a user