198 lines
8.8 KiB
TypeScript
198 lines
8.8 KiB
TypeScript
export const CERTIFICATE_REPORT_MATERIAL_TYPE = '证书-检测报告'
|
||
|
||
export const CERTIFICATE_REPORT_KIND = {
|
||
FINISHED: '成品',
|
||
RAW: '原材料',
|
||
} as const
|
||
|
||
export type CertificateReportKind = (typeof CERTIFICATE_REPORT_KIND)[keyof typeof CERTIFICATE_REPORT_KIND]
|
||
|
||
export const CERTIFICATE_REPORT_PLATFORM_OPTIONS = ['大货', '品牌'] as const
|
||
|
||
// 这是每份报告的一个可编辑文本字段,编号和换行属于原文内容。
|
||
export const DEFAULT_CERTIFICATE_REPORT_USAGE_RULES = `1、检测报告各电商平台官方正常认可的有效期为1年内,需注意检测时间;
|
||
2、检测报告均为由CMA资质的机构:常州检验检测标准认证研究院 出具,委托检测单位由不同主体委托,若使用中对委托送检主体有要求的,必须要核对好对应主体是否可以使用;
|
||
3、检测报告项目检测结果仅代表当批次产品,新品和其他的款式是否能通过需送检后方能确认;
|
||
4、检测报告检测项目各有不同,常规检测项目主要为:甲醛检测、结构安全、力学性能检测等,现有检测报告因需求不同,部分报告只做了甲醛项目、或者只做了力学性能检测未做甲醛检测等,使用前需核对好使用需求和报告内容;
|
||
5、检测项报告中:规格型号主要按品类名称制作,少部分单独特殊需求的检测报告有对应产品SPU型号,使用时需注意核对;部分检测报告因特殊需求附上产品图片,需注意核对报告上的图片是否和实际需求相符;
|
||
6、检测报告文件夹中,标明为品牌的,除顾家品牌店铺外禁止使用,品牌检测报告需求对接伯阳;;
|
||
7、检测报告若有需求的,需由部门负责人申请钉钉流程:质检报告申请流程,费用需送检部门支付,可以根据需求单独做某一项,常见检测报告收费范围:甲醛检测(1600-2500元,15个工作日左右)、结构安全和力学性能(800-2000元,7个工作日左右),同时需求部门需安装准备好对应样品,样品事先可以对接:守仁确认;`
|
||
|
||
export const CERTIFICATE_REPORT_ASSET_TYPE = {
|
||
PDF: 'report-pdf',
|
||
IMAGE: 'report-image',
|
||
} as const
|
||
|
||
export const CERTIFICATE_REPORT_UPLOAD_LIMITS = {
|
||
maxFiles: 20,
|
||
maxImageBytes: 100 * 1024 * 1024,
|
||
maxPdfBytes: 512 * 1024 * 1024,
|
||
maxTotalBytes: 1024 * 1024 * 1024,
|
||
} as const
|
||
|
||
const CERTIFICATE_REPORT_IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.webp'])
|
||
|
||
export type CertificateReportAssetType =
|
||
(typeof CERTIFICATE_REPORT_ASSET_TYPE)[keyof typeof CERTIFICATE_REPORT_ASSET_TYPE]
|
||
|
||
export type CertificateReportFileKind = 'pdf' | 'image'
|
||
|
||
export function isCertificateReportMaterialType(materialType: string | null | undefined): boolean {
|
||
return String(materialType || '').trim() === CERTIFICATE_REPORT_MATERIAL_TYPE
|
||
}
|
||
|
||
// 上传完成接口将报告 PDF 放入公开展示目录;它不是受保护的本地源文件。
|
||
export function isPublicCertificateReportPdf(assetType: unknown, fileUrl: unknown, materialId: string): boolean {
|
||
if (assetType !== CERTIFICATE_REPORT_ASSET_TYPE.PDF || typeof fileUrl !== 'string') return false
|
||
try {
|
||
const url = new URL(fileUrl)
|
||
if (url.protocol !== 'https:' && url.protocol !== 'http:') return false
|
||
const segments = url.pathname.split('/').map(decodeURIComponent)
|
||
const index = segments.indexOf('public-display')
|
||
return index >= 0
|
||
&& segments[index + 1] === 'certificate-report'
|
||
&& segments[index + 2] === materialId
|
||
&& segments[index + 3] === 'pdf'
|
||
&& segments.length === index + 5
|
||
&& /\.pdf$/i.test(segments[index + 4])
|
||
} catch {
|
||
return false
|
||
}
|
||
}
|
||
|
||
export function normalizeReportTitle(value: string | null | undefined): string | null {
|
||
const normalized = typeof value === 'string' ? value.trim() : ''
|
||
return normalized || null
|
||
}
|
||
|
||
export function parseCertificateDate(value: unknown): Date | null {
|
||
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null
|
||
const parsed = new Date(`${value}T00:00:00.000Z`)
|
||
return Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value
|
||
? null
|
||
: parsed
|
||
}
|
||
|
||
export function getDefaultCertificateExpiryDate(testedOn: string | Date | null | undefined): Date | null {
|
||
const dateText = testedOn instanceof Date
|
||
? (Number.isNaN(testedOn.getTime()) ? '' : testedOn.toISOString().slice(0, 10))
|
||
: testedOn?.slice(0, 10)
|
||
const testedDate = parseCertificateDate(dateText)
|
||
if (!testedDate) return null
|
||
const expiresDate = new Date(testedDate)
|
||
expiresDate.setUTCFullYear(testedDate.getUTCFullYear() + 1)
|
||
// 闰年 2 月 29 日顺延到次年 2 月末,避免溢出到 3 月。
|
||
if (expiresDate.getUTCMonth() !== testedDate.getUTCMonth()) expiresDate.setUTCDate(0)
|
||
return expiresDate
|
||
}
|
||
|
||
export function validateCertificateReportMetadata(input: {
|
||
reportKind: unknown
|
||
productModel: unknown
|
||
reportTitle: unknown
|
||
certificateTestedOn: unknown
|
||
certificateExpiresOn?: unknown
|
||
certificateNumber: unknown
|
||
reportUsageRules: unknown
|
||
platformName: unknown
|
||
}, legacyPlatformName = ''):
|
||
| { error: string }
|
||
| { value: {
|
||
reportKind: CertificateReportKind
|
||
certificateTestedOn: Date
|
||
certificateExpiresOn: Date
|
||
certificateNumber: string
|
||
reportUsageRules: string
|
||
} } {
|
||
const reportKind = input.reportKind
|
||
if (reportKind !== CERTIFICATE_REPORT_KIND.FINISHED && reportKind !== CERTIFICATE_REPORT_KIND.RAW) {
|
||
return { error: '请选择报告类别(成品或原材料)' }
|
||
}
|
||
const productModel = String(input.productModel || '').trim()
|
||
const reportTitle = String(input.reportTitle || '').trim()
|
||
if (reportKind === CERTIFICATE_REPORT_KIND.RAW && !reportTitle) {
|
||
return { error: '原材料检测报告必须填写报告名称' }
|
||
}
|
||
if (reportKind === CERTIFICATE_REPORT_KIND.FINISHED && !productModel && !reportTitle) {
|
||
return { error: 'SPU 和 报告名称/检测对象 至少填写一个' }
|
||
}
|
||
const certificateTestedOn = parseCertificateDate(input.certificateTestedOn)
|
||
if (!certificateTestedOn) return { error: '请选择有效的证书检测日期' }
|
||
const expiryIsEmpty = input.certificateExpiresOn == null
|
||
|| (typeof input.certificateExpiresOn === 'string' && input.certificateExpiresOn.trim() === '')
|
||
const certificateExpiresOn = expiryIsEmpty
|
||
? getDefaultCertificateExpiryDate(certificateTestedOn)
|
||
: parseCertificateDate(input.certificateExpiresOn)
|
||
if (!certificateExpiresOn) return { error: '请选择有效的证书到期时间' }
|
||
if (certificateExpiresOn < certificateTestedOn) return { error: '证书到期时间不能早于检测日期' }
|
||
const certificateNumber = typeof input.certificateNumber === 'string' ? input.certificateNumber.trim() : ''
|
||
if (!certificateNumber || certificateNumber.length > 120) {
|
||
return { error: '请填写证书编号(不超过120字)' }
|
||
}
|
||
const reportUsageRules = typeof input.reportUsageRules === 'string' ? input.reportUsageRules.trim() : ''
|
||
if (!reportUsageRules || reportUsageRules.length > 10000) {
|
||
return { error: '请填写检测报告使用规则说明(不超过10000字)' }
|
||
}
|
||
const platformName = typeof input.platformName === 'string' ? input.platformName.trim() : ''
|
||
if (platformName &&
|
||
!CERTIFICATE_REPORT_PLATFORM_OPTIONS.some((option) => option === platformName) &&
|
||
platformName !== legacyPlatformName) {
|
||
return { error: '检测报告的平台分类只能选择大货或品牌' }
|
||
}
|
||
return {
|
||
value: {
|
||
reportKind,
|
||
certificateTestedOn,
|
||
certificateExpiresOn,
|
||
certificateNumber,
|
||
reportUsageRules,
|
||
},
|
||
}
|
||
}
|
||
|
||
export function getCertificateReportDisplayTitle(input: {
|
||
productModel?: string | null
|
||
reportTitle?: string | null
|
||
}): string {
|
||
const productModel = String(input.productModel || '').trim()
|
||
const reportTitle = String(input.reportTitle || '').trim()
|
||
if (reportTitle) {
|
||
return reportTitle
|
||
}
|
||
if (productModel) {
|
||
return productModel
|
||
}
|
||
return '未命名报告'
|
||
}
|
||
|
||
export function getCertificateReportFileKindByAssetType(assetType: string | null | undefined): CertificateReportFileKind | null {
|
||
if (assetType === CERTIFICATE_REPORT_ASSET_TYPE.PDF) {
|
||
return 'pdf'
|
||
}
|
||
|
||
if (assetType === CERTIFICATE_REPORT_ASSET_TYPE.IMAGE) {
|
||
return 'image'
|
||
}
|
||
|
||
return null
|
||
}
|
||
|
||
export function detectFileKindFromName(fileName: string | null | undefined): CertificateReportFileKind {
|
||
const lowerName = String(fileName || '').trim().toLowerCase()
|
||
if (lowerName.endsWith('.pdf')) {
|
||
return 'pdf'
|
||
}
|
||
return 'image'
|
||
}
|
||
|
||
export function isSupportedCertificateReportFileName(fileName: string | null | undefined): boolean {
|
||
const normalized = String(fileName || '').trim().toLowerCase()
|
||
if (normalized.endsWith('.pdf')) {
|
||
return true
|
||
}
|
||
|
||
const extensionIndex = normalized.lastIndexOf('.')
|
||
const extension = extensionIndex >= 0 ? normalized.slice(extensionIndex) : ''
|
||
return CERTIFICATE_REPORT_IMAGE_EXTENSIONS.has(extension)
|
||
}
|