/**
|
|
* 价格计算工具类
|
|
* 根据当前地址、日期、宠物类型等因素计算价格,保留2位小数
|
|
*/
|
|
class PriceCalculator {
|
|
constructor(priceConfig) {
|
|
this.priceConfig = priceConfig || this.getDefaultPriceConfig();
|
|
}
|
|
|
|
// 获取默认价格配置
|
|
getDefaultPriceConfig() {
|
|
return {
|
|
basePrice: {
|
|
normal: 75,
|
|
holiday: 85,
|
|
weekend: 80
|
|
},
|
|
memberDiscount: {
|
|
'new': 0.95,
|
|
'regular': 0.9,
|
|
'silver': 0.88,
|
|
'gold': 0.85
|
|
},
|
|
preFamiliarize: {
|
|
price: 40,
|
|
holidayRate: 1.2
|
|
},
|
|
multiService: {
|
|
two: {
|
|
price: 45,
|
|
holidayRate: 1.1
|
|
},
|
|
three: {
|
|
price: 130,
|
|
holidayRate: 1.1
|
|
}
|
|
},
|
|
petExtra: {
|
|
largeDog: {
|
|
price: 40,
|
|
holidayRate: 1.1
|
|
},
|
|
mediumDog: {
|
|
price: 30,
|
|
holidayRate: 1.1
|
|
},
|
|
smallDog: {
|
|
price: 15,
|
|
holidayRate: 1.1
|
|
},
|
|
cat: {
|
|
price: 10,
|
|
holidayRate: 1.1
|
|
}
|
|
},
|
|
freeQuota: {
|
|
threshold: 30,
|
|
rules: [
|
|
{
|
|
type: 'cat',
|
|
count: 3,
|
|
freeAmount: 30,
|
|
description: '3只及以上猫免费30元'
|
|
},
|
|
{
|
|
type: 'smallDog',
|
|
count: 2,
|
|
freeAmount: 30,
|
|
description: '2只及以上小型犬免费30元'
|
|
},
|
|
{
|
|
type: 'mediumDog',
|
|
count: 1,
|
|
freeAmount: 30,
|
|
description: '1只及以上中型犬免费30元'
|
|
},
|
|
{
|
|
type: 'mixed',
|
|
count: 0,
|
|
freeAmount: 25,
|
|
description: '混合类型免费25元(1猫1小型犬)'
|
|
}
|
|
]
|
|
},
|
|
holidays: [
|
|
'2024-07-15', '2024-07-16', '2024-07-17',
|
|
'2024-10-01', '2024-10-02', '2024-10-03'
|
|
],
|
|
weekends: [0, 6],
|
|
customServices: {
|
|
priceConfig: {},
|
|
holidayRate: 1.1
|
|
},
|
|
cityConfig: {
|
|
priceRates: {
|
|
'beijing': 1.2,
|
|
'shanghai': 1.15,
|
|
'guangzhou': 1.1,
|
|
'shenzhen': 1.15,
|
|
'default': 1.0
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// 保留2位小数
|
|
roundToTwoDecimals(value) {
|
|
return Math.round(value * 100) / 100;
|
|
}
|
|
|
|
// 根据地址获取城市代码
|
|
getCityCode(address) {
|
|
if (!address) return 'default';
|
|
|
|
const cityMapping = {
|
|
'北京': 'beijing',
|
|
'上海': 'shanghai',
|
|
'广州': 'guangzhou',
|
|
'深圳': 'shenzhen'
|
|
};
|
|
|
|
for (const [cityName, cityCode] of Object.entries(cityMapping)) {
|
|
if (address.includes(cityName)) {
|
|
return cityCode;
|
|
}
|
|
}
|
|
|
|
return 'default';
|
|
}
|
|
|
|
// 获取城市价格倍率
|
|
getCityPriceRate(address) {
|
|
const cityCode = this.getCityCode(address);
|
|
return this.priceConfig.cityConfig.priceRates[cityCode] || 1.0;
|
|
}
|
|
|
|
// 判断日期类型
|
|
getDatePriceType(dateString) {
|
|
const date = new Date(dateString);
|
|
const dayOfWeek = date.getDay();
|
|
|
|
if (this.priceConfig.holidays.includes(dateString)) {
|
|
return 'holiday';
|
|
}
|
|
|
|
if (this.priceConfig.weekends.includes(dayOfWeek)) {
|
|
return 'weekend';
|
|
}
|
|
|
|
return 'normal';
|
|
}
|
|
|
|
// 获取基础服务价格
|
|
getBasePriceByDate(dateString) {
|
|
const priceType = this.getDatePriceType(dateString);
|
|
return this.priceConfig.basePrice[priceType] || this.priceConfig.basePrice.normal;
|
|
}
|
|
|
|
// 获取宠物额外费用
|
|
getPetExtraCost(pet, dateString) {
|
|
const priceType = this.getDatePriceType(dateString);
|
|
let baseCost = 0;
|
|
let holidayRate = 1;
|
|
|
|
if (pet.petType === 'cat') {
|
|
baseCost = this.priceConfig.petExtra.cat.price;
|
|
holidayRate = this.priceConfig.petExtra.cat.holidayRate;
|
|
} else if (pet.petType === 'dog' && pet.bodyType.includes('小型')) {
|
|
baseCost = this.priceConfig.petExtra.smallDog.price;
|
|
holidayRate = this.priceConfig.petExtra.smallDog.holidayRate;
|
|
} else if (pet.petType === 'dog' && pet.bodyType.includes('中型')) {
|
|
baseCost = this.priceConfig.petExtra.mediumDog.price;
|
|
holidayRate = this.priceConfig.petExtra.mediumDog.holidayRate;
|
|
} else if (pet.petType === 'dog' && pet.bodyType.includes('大型')) {
|
|
baseCost = this.priceConfig.petExtra.largeDog.price;
|
|
holidayRate = this.priceConfig.petExtra.largeDog.holidayRate;
|
|
}
|
|
|
|
if (priceType === 'holiday') {
|
|
baseCost = baseCost * holidayRate;
|
|
}
|
|
|
|
return this.roundToTwoDecimals(baseCost);
|
|
}
|
|
|
|
// 获取多次服务费用
|
|
getMultiServiceCost(feedCount, dateString) {
|
|
const priceType = this.getDatePriceType(dateString);
|
|
let baseCost = 0;
|
|
let holidayRate = 1;
|
|
|
|
if (feedCount === 2) {
|
|
baseCost = this.priceConfig.multiService.two.price;
|
|
holidayRate = this.priceConfig.multiService.two.holidayRate;
|
|
} else if (feedCount === 3) {
|
|
baseCost = this.priceConfig.multiService.three.price;
|
|
holidayRate = this.priceConfig.multiService.three.holidayRate;
|
|
}
|
|
|
|
if (priceType === 'holiday') {
|
|
baseCost = baseCost * holidayRate;
|
|
}
|
|
|
|
return this.roundToTwoDecimals(baseCost);
|
|
}
|
|
|
|
// 获取提前熟悉费用
|
|
getPreFamiliarizeCost(dateString) {
|
|
const priceType = this.getDatePriceType(dateString);
|
|
let baseCost = this.priceConfig.preFamiliarize.price;
|
|
|
|
if (priceType === 'holiday') {
|
|
baseCost = baseCost * this.priceConfig.preFamiliarize.holidayRate;
|
|
}
|
|
|
|
return this.roundToTwoDecimals(baseCost);
|
|
}
|
|
|
|
// 计算套餐免费额度
|
|
calculateFreeQuota(pets) {
|
|
let totalPetCost = pets.reduce((acc, pet) => {
|
|
return acc + this.getPetExtraCost(pet, pet.serviceDate);
|
|
}, 0);
|
|
|
|
if (totalPetCost <= this.priceConfig.freeQuota.threshold) {
|
|
return 0;
|
|
}
|
|
|
|
let freeAmount = 0;
|
|
const petCounts = {
|
|
cat: pets.filter(pet => pet.petType === 'cat').length,
|
|
smallDog: pets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes('小型')).length,
|
|
mediumDog: pets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes('中型')).length,
|
|
largeDog: pets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes('大型')).length
|
|
};
|
|
|
|
for (const rule of this.priceConfig.freeQuota.rules) {
|
|
if (rule.type === 'cat' && petCounts.cat >= rule.count) {
|
|
freeAmount += rule.freeAmount;
|
|
petCounts.cat -= rule.count;
|
|
} else if (rule.type === 'smallDog' && petCounts.smallDog >= rule.count) {
|
|
freeAmount += rule.freeAmount;
|
|
petCounts.smallDog -= rule.count;
|
|
} else if (rule.type === 'mediumDog' && petCounts.mediumDog >= rule.count) {
|
|
freeAmount += rule.freeAmount;
|
|
petCounts.mediumDog -= rule.count;
|
|
} else if (rule.type === 'mixed' && petCounts.cat >= 1 && petCounts.smallDog >= 1) {
|
|
freeAmount += rule.freeAmount;
|
|
petCounts.cat -= 1;
|
|
petCounts.smallDog -= 1;
|
|
}
|
|
}
|
|
|
|
return this.roundToTwoDecimals(freeAmount);
|
|
}
|
|
|
|
// 计算会员折扣
|
|
calculateMemberDiscount(originalPrice, memberLevel) {
|
|
const discount = this.priceConfig.memberDiscount[memberLevel] || 1;
|
|
const discountedPrice = originalPrice * discount;
|
|
return this.roundToTwoDecimals(discountedPrice);
|
|
}
|
|
|
|
// 计算定制服务费用
|
|
calculateCustomServiceCost(customServices, dateString) {
|
|
const priceType = this.getDatePriceType(dateString);
|
|
let totalCost = 0;
|
|
|
|
customServices.forEach(service => {
|
|
let servicePrice = service.price * service.quantity;
|
|
|
|
if (priceType === 'holiday') {
|
|
servicePrice = servicePrice * this.priceConfig.customServices.holidayRate;
|
|
}
|
|
|
|
totalCost += servicePrice;
|
|
});
|
|
|
|
return this.roundToTwoDecimals(totalCost);
|
|
}
|
|
|
|
// 计算单日总价
|
|
calculateDailyPrice(pets, dateString, feedCount = 1, customServices = []) {
|
|
// 明细数组
|
|
let priceDetails = [];
|
|
// 基础服务费用
|
|
let baseServiceCost = this.getBasePriceByDate(dateString);
|
|
priceDetails.push({
|
|
name: '专业喂养',
|
|
formula: `¥${baseServiceCost.toFixed(2)} x ${pets.length}天`,
|
|
amount: this.roundToTwoDecimals(baseServiceCost * pets.length)
|
|
});
|
|
// 宠物额外费用
|
|
let petExtraCost = pets.reduce((acc, pet) => {
|
|
return acc + this.getPetExtraCost(pet, dateString);
|
|
}, 0);
|
|
if (petExtraCost > 0) {
|
|
// 细分每只宠物
|
|
pets.forEach(pet => {
|
|
const extra = this.getPetExtraCost(pet, dateString);
|
|
if (extra > 0) {
|
|
priceDetails.push({
|
|
name: '额外宠物费用',
|
|
formula: `${pet.petType === 'cat' ? '猫' : pet.bodyType}${pet.petType === 'dog' ? '犬' : ''} 1只 x ¥${extra.toFixed(2)}`,
|
|
amount: extra
|
|
});
|
|
}
|
|
});
|
|
}
|
|
// 多次服务费用
|
|
let multiServiceCost = this.getMultiServiceCost(feedCount, dateString);
|
|
if (multiServiceCost > 0) {
|
|
priceDetails.push({
|
|
name: '上门次数',
|
|
formula: `${feedCount === 2 ? '1天2次' : feedCount === 3 ? '1天3次' : '1天1次'} x ¥${multiServiceCost.toFixed(2)}`,
|
|
amount: multiServiceCost
|
|
});
|
|
}
|
|
// 定制服务费用
|
|
let customServiceCost = this.calculateCustomServiceCost(customServices, dateString);
|
|
if (customServiceCost > 0) {
|
|
customServices.forEach(service => {
|
|
if (service.quantity > 0) {
|
|
priceDetails.push({
|
|
name: '定制服务',
|
|
formula: `${service.name} | ¥${service.price} x ${service.quantity}次`,
|
|
amount: this.roundToTwoDecimals(service.price * service.quantity)
|
|
});
|
|
}
|
|
});
|
|
}
|
|
// 计算免费额度
|
|
let freeQuota = this.calculateFreeQuota(pets);
|
|
if (freeQuota > 0) {
|
|
priceDetails.push({
|
|
name: '套餐免费额度',
|
|
formula: `-¥${freeQuota.toFixed(2)}`,
|
|
amount: -freeQuota
|
|
});
|
|
}
|
|
// 总价(未折扣)
|
|
let totalOriginalPrice = baseServiceCost + petExtraCost + multiServiceCost + customServiceCost - freeQuota;
|
|
return {
|
|
baseServiceCost: this.roundToTwoDecimals(baseServiceCost),
|
|
petExtraCost: this.roundToTwoDecimals(petExtraCost),
|
|
multiServiceCost: this.roundToTwoDecimals(multiServiceCost),
|
|
customServiceCost: this.roundToTwoDecimals(customServiceCost),
|
|
freeQuota: this.roundToTwoDecimals(freeQuota),
|
|
totalOriginalPrice: this.roundToTwoDecimals(totalOriginalPrice),
|
|
priceType: this.getDatePriceType(dateString),
|
|
priceDetails // 新增明细
|
|
};
|
|
}
|
|
|
|
// 计算订单总价(含城市倍率和会员折扣)
|
|
calculateOrderTotal(orderData, userAddress, memberLevel = 'new') {
|
|
const cityRate = this.getCityPriceRate(userAddress);
|
|
// 计算每日费用
|
|
const dailyPrices = orderData.pets.map(pet => {
|
|
const dailyPrice = this.calculateDailyPrice(
|
|
[pet],
|
|
pet.serviceDate,
|
|
pet.feedCount,
|
|
pet.customServices || []
|
|
);
|
|
// 应用城市倍率
|
|
dailyPrice.totalWithCityRate = this.roundToTwoDecimals(dailyPrice.totalOriginalPrice * cityRate);
|
|
// 明细也加上城市倍率
|
|
dailyPrice.priceDetails = dailyPrice.priceDetails.map(detail => ({
|
|
...detail,
|
|
amount: this.roundToTwoDecimals(detail.amount * cityRate),
|
|
formula: cityRate !== 1.0 ? `${detail.formula} x 城市倍率${cityRate}` : detail.formula
|
|
}));
|
|
return dailyPrice;
|
|
});
|
|
// 汇总明细
|
|
let priceDetails = [];
|
|
dailyPrices.forEach((d, idx) => {
|
|
d.priceDetails.forEach(item => {
|
|
priceDetails.push({
|
|
...item,
|
|
date: orderData.pets[idx]?.serviceDate || ''
|
|
});
|
|
});
|
|
});
|
|
// 提前熟悉费用
|
|
let preFamiliarizeCost = 0;
|
|
if (orderData.needPreFamiliarize && orderData.needPreFamiliarize.length > 0) {
|
|
const firstServiceDate = orderData.pets[0]?.serviceDate;
|
|
if (firstServiceDate) {
|
|
preFamiliarizeCost = this.getPreFamiliarizeCost(firstServiceDate);
|
|
preFamiliarizeCost = this.roundToTwoDecimals(preFamiliarizeCost * cityRate);
|
|
priceDetails.push({
|
|
name: '提前熟悉',
|
|
formula: `¥${this.getPreFamiliarizeCost(firstServiceDate)} x 城市倍率${cityRate}`,
|
|
amount: preFamiliarizeCost
|
|
});
|
|
}
|
|
}
|
|
// 计算总价
|
|
let totalOriginalPrice = dailyPrices.reduce((acc, daily) => {
|
|
return acc + daily.totalWithCityRate;
|
|
}, 0) + preFamiliarizeCost;
|
|
// 应用会员折扣
|
|
const totalWithDiscount = this.calculateMemberDiscount(totalOriginalPrice, memberLevel);
|
|
return {
|
|
dailyPrices,
|
|
preFamiliarizeCost,
|
|
totalOriginalPrice: this.roundToTwoDecimals(totalOriginalPrice),
|
|
totalWithDiscount: this.roundToTwoDecimals(totalWithDiscount),
|
|
cityRate: this.roundToTwoDecimals(cityRate),
|
|
memberDiscount: this.roundToTwoDecimals(totalOriginalPrice - totalWithDiscount),
|
|
memberLevel,
|
|
priceDetails // 新增明细
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
export default PriceCalculator;
|