145 lines
4.6 KiB
TypeScript
145 lines
4.6 KiB
TypeScript
import type {
|
|
ConsignmentPricingConfig,
|
|
ConsignmentSpecField,
|
|
ConsignmentSpecFieldType,
|
|
ConsignmentSpecValues,
|
|
} from '@/types/consignment'
|
|
|
|
/**
|
|
* 旧版客服手工入库表单的选项常量。
|
|
* 新链路以「每个寄卖配置的 specs」为准,这里只给没有配置快照的旧数据兜底。
|
|
*/
|
|
export const CONSIGNMENT_SYSTEM_OPTIONS = [
|
|
{ label: '苹果', value: '苹果' },
|
|
{ label: '安卓', value: '安卓' },
|
|
{ label: '通用', value: '通用' },
|
|
] as const
|
|
|
|
export const CONSIGNMENT_ZONE_OPTIONS = [
|
|
{ label: '微信区', value: '微信区' },
|
|
{ label: 'QQ区', value: 'QQ区' },
|
|
{ label: '互通(QQ/微信)', value: '互通' },
|
|
] as const
|
|
|
|
export const SPEC_FIELD_TYPE_OPTIONS: Array<{ label: string; value: ConsignmentSpecFieldType }> = [
|
|
{ label: '文本', value: 'text' },
|
|
{ label: '长文本', value: 'textarea' },
|
|
{ label: '下拉', value: 'select' },
|
|
{ label: '单选', value: 'radio' },
|
|
{ label: '数字', value: 'number' },
|
|
{ label: '日期', value: 'date' },
|
|
]
|
|
|
|
export function specFieldTypeLabel(type: ConsignmentSpecFieldType): string {
|
|
return SPEC_FIELD_TYPE_OPTIONS.find((item) => item.value === type)?.label || '文本'
|
|
}
|
|
|
|
export function isOptionSpecField(field: Pick<ConsignmentSpecField, 'type'>): boolean {
|
|
return field.type === 'select' || field.type === 'radio'
|
|
}
|
|
|
|
export function formatSpecValue(
|
|
field: ConsignmentSpecField,
|
|
value: unknown,
|
|
options: { forItemName?: boolean } = {},
|
|
): string {
|
|
const raw = String(value ?? '').trim()
|
|
if (!raw) return ''
|
|
if (options.forItemName && (field.key === 'diamondLevel' || field.key === 'brillianceCount')) {
|
|
const numeric = Number(raw)
|
|
if (!Number.isFinite(numeric)) return raw
|
|
if (field.key === 'brillianceCount' && numeric <= 0) return ''
|
|
return String(Math.round(numeric))
|
|
}
|
|
if (isOptionSpecField(field)) {
|
|
const option = field.options.find((item) => item.value === raw)
|
|
if (options.forItemName && field.key === 'serverZone' && field.wildcardValues.includes(raw)) {
|
|
return ''
|
|
}
|
|
return option?.label || raw
|
|
}
|
|
return raw
|
|
}
|
|
|
|
export function buildSpecText(
|
|
specs: ConsignmentSpecField[],
|
|
values: ConsignmentSpecValues | undefined,
|
|
): string {
|
|
if (!values) return ''
|
|
return specs
|
|
.slice()
|
|
.sort((a, b) => a.sortOrder - b.sortOrder || a.key.localeCompare(b.key))
|
|
.map((field) => {
|
|
const display = formatSpecValue(field, values[field.key])
|
|
return display ? `${field.label} ${display}` : ''
|
|
})
|
|
.filter(Boolean)
|
|
.join(' / ')
|
|
}
|
|
|
|
export function buildSpecItemName(
|
|
template: string,
|
|
specs: ConsignmentSpecField[],
|
|
values: ConsignmentSpecValues | undefined,
|
|
categoryName = '',
|
|
): string {
|
|
if (!values) return categoryName
|
|
const tokens = new Map<string, string>()
|
|
for (const field of specs) {
|
|
tokens.set(field.key, formatSpecValue(field, values[field.key], { forItemName: true }))
|
|
}
|
|
tokens.set('category', categoryName)
|
|
let result = String(template || '').trim()
|
|
if (result) {
|
|
result = result.replace(
|
|
/\{([a-zA-Z][a-zA-Z0-9_]*)\}/g,
|
|
(_, key: string) => tokens.get(key) || '',
|
|
)
|
|
} else {
|
|
result = specs
|
|
.filter((field) => field.showInItemName)
|
|
.map((field) => formatSpecValue(field, values[field.key], { forItemName: true }))
|
|
.filter(Boolean)
|
|
.join('')
|
|
}
|
|
const normalized = result.replace(/\s+/g, ' ').trim()
|
|
return normalized || categoryName || '寄卖物品'
|
|
}
|
|
|
|
export type SpecPriceResult = {
|
|
priceFen: number
|
|
capFen: number
|
|
templatePriceFen: number
|
|
breakdown: Array<{ label: string; amountFen: number }>
|
|
}
|
|
|
|
/** 与后端 computeConsignmentPrice 保持同口径,仅用于配置面板预览。 */
|
|
export function computeSpecPrice(
|
|
pricing: ConsignmentPricingConfig,
|
|
templatePriceFen = 0,
|
|
): SpecPriceResult {
|
|
const template = Math.max(0, Math.round(Number(templatePriceFen || 0)))
|
|
const breakdown: Array<{ label: string; amountFen: number }> = []
|
|
let priceFen = 0
|
|
|
|
if (pricing.priceType === 'fixed') {
|
|
priceFen = Math.max(0, Math.round(Number(pricing.fixedPriceFen || 0)))
|
|
if (priceFen > 0) breakdown.push({ label: '固定价格', amountFen: priceFen })
|
|
} else if (template > 0) {
|
|
const discount = Math.min(10, Math.max(0.1, Number(pricing.discount || 10)))
|
|
priceFen = Math.round((template * discount) / 10)
|
|
breakdown.push({ label: '接单模板价', amountFen: template })
|
|
if (discount !== 10) {
|
|
breakdown.push({ label: `${discount} 折`, amountFen: priceFen - template })
|
|
}
|
|
}
|
|
|
|
priceFen = Math.max(0, Math.round(priceFen))
|
|
return {
|
|
priceFen,
|
|
capFen: priceFen,
|
|
templatePriceFen: template,
|
|
breakdown,
|
|
}
|
|
}
|