import { useEffect, useState } from 'react'; import { Col, InputNumber, message, Radio, Row, Space, Tag, Typography } from 'antd'; import { calculatePricing, LogisticsLevel, validateLogisticsLevel } from '@/pricing/pricing'; import { getFxRate } from '@/services/fx'; import { ProductDetail } from '@/services/product'; import { apiErrorMessage } from '@/services/api'; import FieldLabel, { fieldRowStyle } from './FieldLabel'; const { Text } = Typography; interface Props { product: ProductDetail; onSave: (p: Partial) => void; } // eslint-disable-next-line @typescript-eslint/no-explicit-any type PricingPayload = Record; /** 包装信息 → 计价输入:重量统一 g */ function weightGrams(product: ProductDetail): number { const w = product.weight ?? 0; return product.weight_unit === 'kg' ? w * 1000 : w; } /** 包装信息 → 计价输入:尺寸统一 cm */ function dimsCm(product: ProductDetail): { l: number; w: number; h: number } { const scale = product.dimension_unit === 'cm' ? 1 : 0.1; // mm → cm return { l: (product.depth ?? 0) * scale, w: (product.width ?? 0) * scale, h: (product.height ?? 0) * scale, }; } /** * 售价信息:售价/划线价(CNY)+ 定价参数(进货价/净利率/物流等级/汇率/划线价倍数)。 * 重量、尺寸直接读「主要信息」的包装字段,不重复填写。 * 任一定价参数变化即重算并回填售价(= 销售价)与划线价(= 销售价 × (1 + 倍数%))。 */ export default function PriceInfoPanel({ product, onSave }: Props) { const pricing = (product.pricing ?? {}) as PricingPayload; const [purchasePrice, setPurchasePrice] = useState(pricing.purchasePrice ?? 30); const [profitRate, setProfitRate] = useState(pricing.profitRate ?? 100); const [level, setLevel] = useState((pricing.logisticsLevel as LogisticsLevel) ?? 'low'); const [multiplier, setMultiplier] = useState(pricing.lineMultiplier ?? 100); const [fxRate, setFxRate] = useState(product.fx_rate ?? pricing.fxRate ?? 0); // 币种固定人民币:历史数据(默认 RUB)打开编辑页时纠正一次 useEffect(() => { if (product.currency_code !== 'CNY') onSave({ currency_code: 'CNY' }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // 汇率:未快照时拉取;已有计价记录的则跟随重算 useEffect(() => { if (!fxRate) { getFxRate() .then((r) => { setFxRate(r.rate); if (pricing.calculatedAt) recalc({ fxRate: r.rate }); }) .catch((e) => message.error(apiErrorMessage(e))); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const tdPrice = pricing.tdPrice ?? 3; /** 用当前参数(可被 patch 覆盖)实时计算,不落库 */ const compute = (patch: Partial<{ purchasePrice: number; profitRate: number; level: LogisticsLevel; multiplier: number; fxRate: number }> = {}) => calculatePricing({ purchasePrice: patch.purchasePrice ?? purchasePrice, profitRate: patch.profitRate ?? profitRate, logisticsLevel: patch.level ?? level, weightG: weightGrams(product), dims: dimsCm(product), tdPrice, lineMultiplier: patch.multiplier ?? multiplier, fxRate: patch.fxRate ?? fxRate, }); /** 重算并落库:定价参数 + 售价/划线价一并保存 */ const recalc = (patch: Partial<{ purchasePrice: number; profitRate: number; level: LogisticsLevel; multiplier: number; fxRate: number }> = {}) => { const p = { purchasePrice: patch.purchasePrice ?? purchasePrice, profitRate: patch.profitRate ?? profitRate, level: patch.level ?? level, multiplier: patch.multiplier ?? multiplier, fxRate: patch.fxRate ?? fxRate, }; const r = compute(patch); onSave({ pricing: { ...pricing, purchasePrice: p.purchasePrice, profitRate: p.profitRate, logisticsLevel: p.level, weightG: weightGrams(product), dims: dimsCm(product), tdPrice, lineMultiplier: p.multiplier, fxRate: p.fxRate, logisticsFee: r.logisticsFee, fullCommission: r.fullCommission, totalCost: r.totalCost, sellingPriceCny: r.sellingPriceCny, sellingPriceRub: r.sellingPriceRub, calculatedAt: new Date().toISOString(), }, fx_rate: p.fxRate, price: r.sellingPriceCny, old_price: r.linePriceCny, currency_code: 'CNY', }); }; const preview = fxRate ? compute() : null; const hint = preview ? validateLogisticsLevel(preview.sellingPriceRub, level) : ''; return (
{/* 售价 / 划线价 / 币种 */} 售价 ¥ onSave({ price: v ?? null, currency_code: 'CNY' })} /> 划线价 ¥ onSave({ old_price: v ?? null })} /> 币种 人民币(CNY) 售价/划线价随定价参数自动回填,也可手动微调 {/* 定价参数 */} 进货价 ¥ { setPurchasePrice(v ?? 0); recalc({ purchasePrice: v ?? 0 }); }} /> 净利率 % { setProfitRate(v ?? 0); recalc({ profitRate: v ?? 0 }); }} /> 汇率 ¥→₽ { setFxRate(v ?? 0); recalc({ fxRate: v ?? 0 }); }} /> 划线价倍数 % { setMultiplier(v ?? 0); recalc({ multiplier: v ?? 0 }); }} /> 重量/尺寸取自包装信息 {/* 物流等级 */}
物流等级 { setLevel(e.target.value); recalc({ level: e.target.value }); }} options={[ { value: 'low', label: '低 (low)' }, { value: 'high', label: '高 (high)' }, { value: 'high2', label: 'Premium (high2)' }, ]} />
{/* 实时结果摘要 */} {preview && ( 完全成本 ¥ {preview.totalCost.toFixed(2)} 物流费 ¥ {preview.logisticsFee.toFixed(2)} 销售价 ₽ {preview.sellingPriceRub.toFixed(0)}(参考) {hint && {hint}} )}
); }