443 lines
14 KiB
TypeScript
443 lines
14 KiB
TypeScript
export interface StandardModelWhiteBgView {
|
|
key: StandardModelWhiteBgKey
|
|
label: string
|
|
fieldName: string
|
|
}
|
|
|
|
export type StandardModelWhiteBgKey =
|
|
| 'leftFront45'
|
|
| 'rightFront45'
|
|
| 'leftBack45'
|
|
| 'rightBack45'
|
|
| 'front'
|
|
| 'back'
|
|
| 'left'
|
|
| 'right'
|
|
|
|
export const STANDARD_MODEL_WHITE_BG_VIEWS: StandardModelWhiteBgView[] = [
|
|
{ key: 'leftFront45', label: '左前45°', fieldName: 'whiteBgLeftFront45' },
|
|
{ key: 'rightFront45', label: '右前45°', fieldName: 'whiteBgRightFront45' },
|
|
{ key: 'leftBack45', label: '左后45°', fieldName: 'whiteBgLeftBack45' },
|
|
{ key: 'rightBack45', label: '右后45°', fieldName: 'whiteBgRightBack45' },
|
|
{ key: 'front', label: '正视图', fieldName: 'whiteBgFront' },
|
|
{ key: 'back', label: '后视图', fieldName: 'whiteBgBack' },
|
|
{ key: 'left', label: '左视图', fieldName: 'whiteBgLeft' },
|
|
{ key: 'right', label: '右视图', fieldName: 'whiteBgRight' },
|
|
]
|
|
|
|
export const STANDARD_MODEL_WHITE_BG_OTHER_FIELD_NAME = 'whiteBgOtherImages'
|
|
|
|
export const LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE = 'standard-model-white-bg-zip'
|
|
export const LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX = 'standard-model-white-bg-zip:'
|
|
export const STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX = 'standard-model-white-bg-zip-v2:'
|
|
export const STANDARD_MODEL_WHITE_BG_VIEW_ASSET_TYPE_PREFIX = 'standard-model-white-bg-view:'
|
|
export const STANDARD_MODEL_WHITE_BG_OTHER_ASSET_TYPE_PREFIX = 'standard-model-white-bg-other:'
|
|
|
|
export interface ParsedStandardModelWhiteBgAssetType {
|
|
kind: 'view' | 'other' | 'zip'
|
|
skuId: string
|
|
viewKey?: StandardModelWhiteBgKey
|
|
index?: number
|
|
}
|
|
|
|
export interface WhiteBackgroundAssetLike {
|
|
assetType: string
|
|
sortOrder?: number | string | bigint | null
|
|
}
|
|
|
|
export interface GroupedWhiteBackgroundAssets<T extends WhiteBackgroundAssetLike> {
|
|
skuId: string
|
|
fixed: Partial<Record<StandardModelWhiteBgKey, T>>
|
|
other: T[]
|
|
zipAssets: T[]
|
|
}
|
|
|
|
export function getWhiteBackgroundFieldName(key: StandardModelWhiteBgKey): string {
|
|
const target = STANDARD_MODEL_WHITE_BG_VIEWS.find((item) => item.key === key)
|
|
return target ? target.fieldName : `whiteBg${key}`
|
|
}
|
|
|
|
export function getMissingWhiteBackgroundViews(
|
|
files: Partial<Record<StandardModelWhiteBgKey, File | null>>,
|
|
): StandardModelWhiteBgKey[] {
|
|
return STANDARD_MODEL_WHITE_BG_VIEWS
|
|
.map((item) => item.key)
|
|
.filter((key) => !files[key])
|
|
}
|
|
|
|
export function hasCompleteWhiteBackgroundSet(
|
|
files: Partial<Record<StandardModelWhiteBgKey, File | null>>,
|
|
): boolean {
|
|
return getMissingWhiteBackgroundViews(files).length === 0
|
|
}
|
|
|
|
export function normalizeWhiteBackgroundSkuName(value: string): string {
|
|
return String(value || '').replace(/[^a-zA-Z0-9\-_.]/g, '').toUpperCase().trim()
|
|
}
|
|
|
|
function sanitizeWhiteBackgroundFilePart(value: string): string {
|
|
return String(value || '')
|
|
.replace(/[\/\\]/g, '-')
|
|
.replace(/[<>:"|?*]/g, '')
|
|
.replace(/\s+/g, '')
|
|
.trim()
|
|
}
|
|
|
|
export function buildWhiteBackgroundFixedFileName(
|
|
skuName: string,
|
|
productCategoryName: string,
|
|
viewLabel: string,
|
|
ext: string,
|
|
): string {
|
|
const normalizedSkuName = normalizeWhiteBackgroundSkuName(skuName)
|
|
const safeCategoryName = sanitizeWhiteBackgroundFilePart(productCategoryName)
|
|
const safeViewLabel = sanitizeWhiteBackgroundFilePart(viewLabel)
|
|
const normalizedExt = ext.startsWith('.') ? ext.toLowerCase() : `.${ext.toLowerCase()}`
|
|
const baseName = [normalizedSkuName, safeCategoryName, safeViewLabel].filter(Boolean).join('-')
|
|
|
|
return `${baseName || 'white-background'}${normalizedExt}`
|
|
}
|
|
|
|
export function getCurrentWhiteBackgroundAssetType(skuId: string): string {
|
|
return `${STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX}${skuId}`
|
|
}
|
|
|
|
export function getWhiteBackgroundViewAssetType(
|
|
skuId: string,
|
|
key: StandardModelWhiteBgKey,
|
|
): string {
|
|
return `${STANDARD_MODEL_WHITE_BG_VIEW_ASSET_TYPE_PREFIX}${skuId}:${key}`
|
|
}
|
|
|
|
export function getWhiteBackgroundOtherAssetType(skuId: string, index: number): string {
|
|
return `${STANDARD_MODEL_WHITE_BG_OTHER_ASSET_TYPE_PREFIX}${skuId}:${index}`
|
|
}
|
|
|
|
export function getLegacyWhiteBackgroundAssetTypes(skuId: string): string[] {
|
|
return [
|
|
LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE,
|
|
`${LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX}${skuId}`,
|
|
]
|
|
}
|
|
|
|
export function getAllWhiteBackgroundAssetTypesForSku(skuId: string): string[] {
|
|
return [
|
|
getCurrentWhiteBackgroundAssetType(skuId),
|
|
...getLegacyWhiteBackgroundAssetTypes(skuId),
|
|
]
|
|
}
|
|
|
|
export function getWhiteBackgroundViewAssetPrefixForSku(skuId: string): string {
|
|
return `${STANDARD_MODEL_WHITE_BG_VIEW_ASSET_TYPE_PREFIX}${skuId}:`
|
|
}
|
|
|
|
export function getWhiteBackgroundOtherAssetPrefixForSku(skuId: string): string {
|
|
return `${STANDARD_MODEL_WHITE_BG_OTHER_ASSET_TYPE_PREFIX}${skuId}:`
|
|
}
|
|
|
|
export function getWhiteBackgroundAssetWhereForSku(skuId: string) {
|
|
return {
|
|
OR: [
|
|
{ assetType: getCurrentWhiteBackgroundAssetType(skuId) },
|
|
{ assetType: LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE },
|
|
{ assetType: `${LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX}${skuId}` },
|
|
{ assetType: { startsWith: getWhiteBackgroundViewAssetPrefixForSku(skuId) } },
|
|
{ assetType: { startsWith: getWhiteBackgroundOtherAssetPrefixForSku(skuId) } },
|
|
],
|
|
}
|
|
}
|
|
|
|
export function getWhiteBackgroundRelativeDirectory(materialId: string, skuId: string): string {
|
|
return ['uploads', 'skus', materialId, 'white-background', skuId].join('/')
|
|
}
|
|
|
|
export function replaceWhiteBackgroundFileExtension(fileName: string, ext: string = '.webp'): string {
|
|
const normalizedExt = ext.startsWith('.') ? ext.toLowerCase() : `.${ext.toLowerCase()}`
|
|
const normalizedFileName = String(fileName || '').trim().replace(/\\/g, '/')
|
|
if (!normalizedFileName) {
|
|
return `white-background${normalizedExt}`
|
|
}
|
|
|
|
const lastSlashIndex = normalizedFileName.lastIndexOf('/')
|
|
const directory = lastSlashIndex >= 0 ? normalizedFileName.slice(0, lastSlashIndex + 1) : ''
|
|
const rawBaseName = lastSlashIndex >= 0 ? normalizedFileName.slice(lastSlashIndex + 1) : normalizedFileName
|
|
const nextBaseName = rawBaseName.replace(/\.[^/.]+$/, '') || 'white-background'
|
|
|
|
return `${directory}${nextBaseName}${normalizedExt}`
|
|
}
|
|
|
|
export function getWhiteBackgroundRelativeFilePath(
|
|
materialId: string,
|
|
skuId: string,
|
|
fileName: string,
|
|
): string {
|
|
return `${getWhiteBackgroundRelativeDirectory(materialId, skuId)}/${fileName}`.replace(/\\/g, '/')
|
|
}
|
|
|
|
export function getWhiteBackgroundPreviewRelativeDirectory(materialId: string, skuId: string): string {
|
|
return `${getWhiteBackgroundRelativeDirectory(materialId, skuId)}/preview`.replace(/\\/g, '/')
|
|
}
|
|
|
|
export function getWhiteBackgroundPreviewRelativeFilePath(
|
|
materialId: string,
|
|
skuId: string,
|
|
fileName: string,
|
|
): string {
|
|
return `${getWhiteBackgroundPreviewRelativeDirectory(materialId, skuId)}/${replaceWhiteBackgroundFileExtension(fileName)}`.replace(/\\/g, '/')
|
|
}
|
|
|
|
export function getWhiteBackgroundOtherPreviewRelativeFilePath(
|
|
materialId: string,
|
|
skuId: string,
|
|
fileName: string,
|
|
): string {
|
|
return `${getWhiteBackgroundPreviewRelativeDirectory(materialId, skuId)}/other/${replaceWhiteBackgroundFileExtension(fileName)}`.replace(/\\/g, '/')
|
|
}
|
|
|
|
export function getWhiteBackgroundPreviewRelativePathFromAssetPath(fileUrl: string): string | null {
|
|
const normalizedFileUrl = String(fileUrl || '').trim().replace(/\\/g, '/')
|
|
if (!normalizedFileUrl || /^https?:\/\//i.test(normalizedFileUrl)) {
|
|
return null
|
|
}
|
|
|
|
const normalizedRelativePath = normalizedFileUrl.replace(/^\.?\//, '')
|
|
const segments = normalizedRelativePath.split('/').filter(Boolean)
|
|
const whiteBackgroundIndex = segments.indexOf('white-background')
|
|
if (whiteBackgroundIndex < 0 || !segments[whiteBackgroundIndex + 1]) {
|
|
return null
|
|
}
|
|
|
|
const skuRootSegments = segments.slice(0, whiteBackgroundIndex + 2)
|
|
const assetSegments = segments.slice(whiteBackgroundIndex + 2)
|
|
if (assetSegments.length === 0) {
|
|
return null
|
|
}
|
|
|
|
if (assetSegments[0] === 'preview') {
|
|
return normalizedRelativePath
|
|
}
|
|
|
|
if (assetSegments[0] === 'other') {
|
|
const otherFileName = assetSegments.slice(1).join('/')
|
|
if (!otherFileName) {
|
|
return null
|
|
}
|
|
|
|
return `${skuRootSegments.join('/')}/preview/other/${replaceWhiteBackgroundFileExtension(otherFileName)}`.replace(/\\/g, '/')
|
|
}
|
|
|
|
return `${skuRootSegments.join('/')}/preview/${replaceWhiteBackgroundFileExtension(assetSegments.join('/'))}`.replace(/\\/g, '/')
|
|
}
|
|
|
|
export function getWhiteBackgroundZipFileName(skuName: string): string {
|
|
return `${skuName}_白底图.zip`
|
|
}
|
|
|
|
export function countWhiteBackgroundFixedViews(
|
|
group?: Pick<GroupedWhiteBackgroundAssets<WhiteBackgroundAssetLike>, 'fixed'> | null,
|
|
): number {
|
|
if (!group) {
|
|
return 0
|
|
}
|
|
|
|
return STANDARD_MODEL_WHITE_BG_VIEWS.filter((view) => Boolean(group.fixed[view.key])).length
|
|
}
|
|
|
|
export function countWhiteBackgroundOtherImages(
|
|
group?: Pick<GroupedWhiteBackgroundAssets<WhiteBackgroundAssetLike>, 'other'> | null,
|
|
): number {
|
|
return group?.other?.length || 0
|
|
}
|
|
|
|
export function countWhiteBackgroundAssets(
|
|
group?: Pick<GroupedWhiteBackgroundAssets<WhiteBackgroundAssetLike>, 'fixed' | 'other' | 'zipAssets'> | null,
|
|
): number {
|
|
if (!group) {
|
|
return 0
|
|
}
|
|
|
|
return (
|
|
countWhiteBackgroundFixedViews(group) +
|
|
countWhiteBackgroundOtherImages(group) +
|
|
(group.zipAssets?.length || 0)
|
|
)
|
|
}
|
|
|
|
export function hasAnyWhiteBackgroundAssetGroup(
|
|
group?: Pick<GroupedWhiteBackgroundAssets<WhiteBackgroundAssetLike>, 'fixed' | 'other' | 'zipAssets'> | null,
|
|
): boolean {
|
|
return countWhiteBackgroundAssets(group) > 0
|
|
}
|
|
|
|
export function getMissingWhiteBackgroundViewsFromAssetGroup(
|
|
group?: Pick<GroupedWhiteBackgroundAssets<WhiteBackgroundAssetLike>, 'fixed'> | null,
|
|
): StandardModelWhiteBgView[] {
|
|
if (!group) {
|
|
return [...STANDARD_MODEL_WHITE_BG_VIEWS]
|
|
}
|
|
|
|
return STANDARD_MODEL_WHITE_BG_VIEWS.filter((view) => !group.fixed[view.key])
|
|
}
|
|
|
|
export function parseStandardModelWhiteBgAssetType(
|
|
assetType: string,
|
|
): ParsedStandardModelWhiteBgAssetType | null {
|
|
const trimmed = String(assetType || '').trim()
|
|
if (!trimmed) {
|
|
return null
|
|
}
|
|
|
|
if (trimmed.startsWith(STANDARD_MODEL_WHITE_BG_VIEW_ASSET_TYPE_PREFIX)) {
|
|
const remainder = trimmed.slice(STANDARD_MODEL_WHITE_BG_VIEW_ASSET_TYPE_PREFIX.length)
|
|
const separatorIndex = remainder.indexOf(':')
|
|
if (separatorIndex <= 0) {
|
|
return null
|
|
}
|
|
|
|
const skuId = remainder.slice(0, separatorIndex)
|
|
const viewKey = remainder.slice(separatorIndex + 1) as StandardModelWhiteBgKey
|
|
if (!STANDARD_MODEL_WHITE_BG_VIEWS.some((view) => view.key === viewKey)) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
kind: 'view',
|
|
skuId,
|
|
viewKey,
|
|
}
|
|
}
|
|
|
|
if (trimmed.startsWith(STANDARD_MODEL_WHITE_BG_OTHER_ASSET_TYPE_PREFIX)) {
|
|
const remainder = trimmed.slice(STANDARD_MODEL_WHITE_BG_OTHER_ASSET_TYPE_PREFIX.length)
|
|
const separatorIndex = remainder.indexOf(':')
|
|
if (separatorIndex <= 0) {
|
|
return null
|
|
}
|
|
|
|
const skuId = remainder.slice(0, separatorIndex)
|
|
const index = Number(remainder.slice(separatorIndex + 1))
|
|
if (!Number.isInteger(index) || index <= 0) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
kind: 'other',
|
|
skuId,
|
|
index,
|
|
}
|
|
}
|
|
|
|
if (trimmed === LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE) {
|
|
return {
|
|
kind: 'zip',
|
|
skuId: '',
|
|
}
|
|
}
|
|
|
|
if (trimmed.startsWith(LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX)) {
|
|
return {
|
|
kind: 'zip',
|
|
skuId: trimmed.slice(LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX.length),
|
|
}
|
|
}
|
|
|
|
if (trimmed.startsWith(STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX)) {
|
|
return {
|
|
kind: 'zip',
|
|
skuId: trimmed.slice(STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX.length),
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
export function isWhiteBackgroundAssetType(assetType: string): boolean {
|
|
return parseStandardModelWhiteBgAssetType(assetType) !== null
|
|
}
|
|
|
|
function getAssetSortOrder(asset: WhiteBackgroundAssetLike): number {
|
|
const raw = asset.sortOrder
|
|
if (typeof raw === 'bigint') {
|
|
return Number(raw)
|
|
}
|
|
if (typeof raw === 'string') {
|
|
const parsed = Number(raw)
|
|
return Number.isFinite(parsed) ? parsed : 0
|
|
}
|
|
if (typeof raw === 'number') {
|
|
return Number.isFinite(raw) ? raw : 0
|
|
}
|
|
return 0
|
|
}
|
|
|
|
export function groupWhiteBackgroundAssetsBySku<T extends WhiteBackgroundAssetLike>(
|
|
assets: T[],
|
|
): Map<string, GroupedWhiteBackgroundAssets<T>> {
|
|
const groups = new Map<string, GroupedWhiteBackgroundAssets<T>>()
|
|
|
|
for (const asset of assets) {
|
|
const parsed = parseStandardModelWhiteBgAssetType(asset.assetType)
|
|
if (!parsed) {
|
|
continue
|
|
}
|
|
|
|
const groupSkuId = parsed.skuId
|
|
if (!groupSkuId) {
|
|
continue
|
|
}
|
|
|
|
const current = groups.get(groupSkuId) || {
|
|
skuId: groupSkuId,
|
|
fixed: {},
|
|
other: [],
|
|
zipAssets: [],
|
|
}
|
|
|
|
if (parsed.kind === 'view' && parsed.viewKey) {
|
|
current.fixed[parsed.viewKey] = asset
|
|
} else if (parsed.kind === 'other') {
|
|
current.other.push(asset)
|
|
} else if (parsed.kind === 'zip') {
|
|
current.zipAssets.push(asset)
|
|
}
|
|
|
|
groups.set(groupSkuId, current)
|
|
}
|
|
|
|
groups.forEach((group) => {
|
|
group.other.sort((left, right) => getAssetSortOrder(left) - getAssetSortOrder(right))
|
|
group.zipAssets.sort((left, right) => getAssetSortOrder(left) - getAssetSortOrder(right))
|
|
})
|
|
|
|
return groups
|
|
}
|
|
|
|
export function hasCompleteWhiteBackgroundAssetGroup(
|
|
group?: Pick<GroupedWhiteBackgroundAssets<WhiteBackgroundAssetLike>, 'fixed'> | null,
|
|
): boolean {
|
|
if (!group) {
|
|
return false
|
|
}
|
|
|
|
return STANDARD_MODEL_WHITE_BG_VIEWS.every((view) => Boolean(group.fixed[view.key]))
|
|
}
|
|
|
|
export function getNextWhiteBackgroundOtherIndex(
|
|
group?: Pick<GroupedWhiteBackgroundAssets<WhiteBackgroundAssetLike>, 'other'> | null,
|
|
): number {
|
|
if (!group?.other?.length) {
|
|
return 1
|
|
}
|
|
|
|
let maxIndex = 0
|
|
|
|
for (const asset of group.other) {
|
|
const parsed = parseStandardModelWhiteBgAssetType(asset.assetType)
|
|
if (parsed?.kind === 'other' && parsed.index && parsed.index > maxIndex) {
|
|
maxIndex = parsed.index
|
|
}
|
|
}
|
|
|
|
return maxIndex + 1
|
|
}
|