|
@@ -210,14 +210,14 @@
|
|
|
<view class="time-tip" v-if="form.sendType == 1">请在所选时间段内到店自取,逾期订单将自动取消</view>
|
|
<view class="time-tip" v-if="form.sendType == 1">请在所选时间段内到店自取,逾期订单将自动取消</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- 跑腿配送:说明 + 平台报价(无区块标题) -->
|
|
|
|
|
|
|
+ <!-- 跑腿配送:说明 + 平台报价/自定义计费(无区块标题) -->
|
|
|
<view class="affirm-card affirm-card--flat" v-if="form.sendType == 2">
|
|
<view class="affirm-card affirm-card--flat" v-if="form.sendType == 2">
|
|
|
- <view v-if="form.openIntraCity == 0">
|
|
|
|
|
- <text class="warn-text">运费自理或到付(请联系客服)</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view v-else-if="form.openIntraCity == 2">
|
|
|
|
|
|
|
+ <view v-if="form.openIntraCity == 2">
|
|
|
<text class="error-text">跑腿配送暂不可用,请选其它方式</text>
|
|
<text class="error-text">跑腿配送暂不可用,请选其它方式</text>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+ <view v-else-if="!runnerFeeAvailable">
|
|
|
|
|
+ <text class="warn-text">运费自理或到付(请联系客服)</text>
|
|
|
|
|
+ </view>
|
|
|
<block v-else>
|
|
<block v-else>
|
|
|
<view v-if="unMeetWarningText" class="delivery-note-box delivery-note-box--runner">
|
|
<view v-if="unMeetWarningText" class="delivery-note-box delivery-note-box--runner">
|
|
|
<view class="delivery-explain-line">
|
|
<view class="delivery-explain-line">
|
|
@@ -246,7 +246,8 @@
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="form.openIntraCity == 1">
|
|
|
|
|
|
|
+ <!-- 仅 calcType=0 且走平台报价时展示跑腿平台卡片 -->
|
|
|
|
|
+ <view v-if="showPlatformQuotes">
|
|
|
<view v-if="deliveryQuotes.length > 0" class="delivery-quotes-container">
|
|
<view v-if="deliveryQuotes.length > 0" class="delivery-quotes-container">
|
|
|
<view class="delivery-quotes-grid">
|
|
<view class="delivery-quotes-grid">
|
|
|
<view
|
|
<view
|
|
@@ -271,6 +272,9 @@
|
|
|
<text v-else class="quotes-hint">请先选择收货地址,将自动计算配送运费</text>
|
|
<text v-else class="quotes-hint">请先选择收货地址,将自动计算配送运费</text>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+ <view v-else-if="freightSource === 'customFallback'" class="quotes-loading">
|
|
|
|
|
+ <text class="quotes-hint">跑腿报价异常,已按门店自定义规则计费</text>
|
|
|
|
|
+ </view>
|
|
|
</block>
|
|
</block>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
@@ -281,7 +285,8 @@
|
|
|
<view class="fee-value">
|
|
<view class="fee-value">
|
|
|
<text v-if="deliveryQuotesLoading" class="fee-value--loading">正在计算距离...</text>
|
|
<text v-if="deliveryQuotesLoading" class="fee-value--loading">正在计算距离...</text>
|
|
|
<template v-else>
|
|
<template v-else>
|
|
|
- <text v-if="showDistance > 100" class="accent-text">{{ (showDistance / 1000).toFixed(1) }}公里</text>
|
|
|
|
|
|
|
+ <!-- 与计费一致展示到 0.01 公里,避免 6.95 被显示成 7.0 造成运费核对偏差 -->
|
|
|
|
|
+ <text v-if="showDistance > 100" class="accent-text">{{ (showDistance / 1000).toFixed(2) }}公里</text>
|
|
|
<text v-else class="accent-text">{{ showDistance }} 米</text>
|
|
<text v-else class="accent-text">{{ showDistance }} 米</text>
|
|
|
<text v-if="showDeliveryPolicy && form.sendType != 2" class="delivery-policy-link" @click.stop="showDeliveryPolicyModal">免费配送条件</text>
|
|
<text v-if="showDeliveryPolicy && form.sendType != 2" class="delivery-policy-link" @click.stop="showDeliveryPolicyModal">免费配送条件</text>
|
|
|
</template>
|
|
</template>
|
|
@@ -491,11 +496,13 @@
|
|
|
/**
|
|
/**
|
|
|
* 混合结算页(花束+花材),立即购买
|
|
* 混合结算页(花束+花材),立即购买
|
|
|
* 花束与花材合并下单,支持跑腿/自取/快递等多种配送及红包抵扣
|
|
* 花束与花材合并下单,支持跑腿/自取/快递等多种配送及红包抵扣
|
|
|
|
|
+ * 跑腿运费:包运费花束 / 不包运费花束 / 纯花材满减;calcType 跑腿或自定义,跑腿异常兜底自定义
|
|
|
*/
|
|
*/
|
|
|
import SpriteIcon from "@/components/sprite-icon/index.vue";
|
|
import SpriteIcon from "@/components/sprite-icon/index.vue";
|
|
|
import productMins from "@/mixins/cgProduct";
|
|
import productMins from "@/mixins/cgProduct";
|
|
|
import { createMixOrder, inform } from "@/api/order";
|
|
import { createMixOrder, inform } from "@/api/order";
|
|
|
import { allDeliveryQuotes } from "@/api/express/delivery";
|
|
import { allDeliveryQuotes } from "@/api/express/delivery";
|
|
|
|
|
+import { getUserDistance } from "@/api/express";
|
|
|
import { currentInfo } from "@/api/user";
|
|
import { currentInfo } from "@/api/user";
|
|
|
import { getInfo } from "@/api/shop";
|
|
import { getInfo } from "@/api/shop";
|
|
|
import { getAddressList } from "@/api/user-address";
|
|
import { getAddressList } from "@/api/user-address";
|
|
@@ -568,6 +575,8 @@ export default {
|
|
|
selectedDeliveryIndex: -1,
|
|
selectedDeliveryIndex: -1,
|
|
|
selectedDeliveryData: null,
|
|
selectedDeliveryData: null,
|
|
|
isFreeDelivery: false,
|
|
isFreeDelivery: false,
|
|
|
|
|
+ /** 跑腿运费来源:platform / custom / customFallback / bouquetIncluded / reduceFree */
|
|
|
|
|
+ freightSource: "",
|
|
|
deliveryPolicyList: [],
|
|
deliveryPolicyList: [],
|
|
|
validAddress: false,
|
|
validAddress: false,
|
|
|
hbData: [],
|
|
hbData: [],
|
|
@@ -746,18 +755,75 @@ export default {
|
|
|
});
|
|
});
|
|
|
return bigLength;
|
|
return bigLength;
|
|
|
},
|
|
},
|
|
|
|
|
+ /** 最低消费数量用:花材扎数 + 花束份数(1 份花束计 1) */
|
|
|
|
|
+ mixOrderBigNum() {
|
|
|
|
|
+ let bouquetNum = 0;
|
|
|
|
|
+ (this.list || []).forEach((item) => {
|
|
|
|
|
+ if (this.isBouquetItem(item)) {
|
|
|
|
|
+ bouquetNum += Number(item.bigCount) || 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ return (Number(this.mixItemBigNum) || 0) + bouquetNum;
|
|
|
|
|
+ },
|
|
|
/** 未达最低消费提示(来自批量预览缓存,与下单 calcFee 同源) */
|
|
/** 未达最低消费提示(来自批量预览缓存,与下单 calcFee 同源) */
|
|
|
unMeetWarningText() {
|
|
unMeetWarningText() {
|
|
|
return (this.unMeetFeePreview && this.unMeetFeePreview.tip) || "";
|
|
return (this.unMeetFeePreview && this.unMeetFeePreview.tip) || "";
|
|
|
},
|
|
},
|
|
|
displaySendCost() {
|
|
displaySendCost() {
|
|
|
- if (this.form.sendType == 2) {
|
|
|
|
|
- if ([0, 2].includes(this.form.openIntraCity)) {
|
|
|
|
|
- return false;
|
|
|
|
|
|
|
+ if (Number(this.form.sendType) !== 2) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (Number(this.form.openIntraCity) === 2) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return this.runnerFeeAvailable;
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 当前跑腿方式配置(xhPsMethod style=2) */
|
|
|
|
|
+ runnerPsConfig() {
|
|
|
|
|
+ return (this.psMethodList || []).find((item) => Number(item.style) === 2) || null;
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 自定义计费三项是否完整 */
|
|
|
|
|
+ hasValidRunnerCustomConfig() {
|
|
|
|
|
+ const m = this.runnerPsConfig;
|
|
|
|
|
+ if (!m) return false;
|
|
|
|
|
+ return Number(m.startKm) > 0 && Number(m.startPrice) > 0 && Number(m.perKmPrice) > 0;
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 是否可自动计跑腿/自定义运费 */
|
|
|
|
|
+ runnerFeeAvailable() {
|
|
|
|
|
+ if (Number(this.form.openIntraCity) === 2) return false;
|
|
|
|
|
+ if (Number(this.form.openIntraCity) === 1) return true;
|
|
|
|
|
+ return this.hasValidRunnerCustomConfig;
|
|
|
|
|
+ },
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 跑腿运费模式
|
|
|
|
|
+ * bouquetIncluded=含包运费花束;bouquetNotIncluded=花束均不包运费;flowerOnly=纯花材
|
|
|
|
|
+ */
|
|
|
|
|
+ freightCalcMode() {
|
|
|
|
|
+ if (Number(this.form.sendType) !== 2) {
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ const list = this.list || [];
|
|
|
|
|
+ const bouquets = list.filter((item) => this.isBouquetItem(item));
|
|
|
|
|
+ if (bouquets.length > 0) {
|
|
|
|
|
+ if (bouquets.some((item) => Number(item.freightType) === 1)) {
|
|
|
|
|
+ return "bouquetIncluded";
|
|
|
}
|
|
}
|
|
|
- return true;
|
|
|
|
|
|
|
+ return "bouquetNotIncluded";
|
|
|
}
|
|
}
|
|
|
- return false;
|
|
|
|
|
|
|
+ return "flowerOnly";
|
|
|
|
|
+ },
|
|
|
|
|
+ runnerCalcType() {
|
|
|
|
|
+ return Number((this.runnerPsConfig && this.runnerPsConfig.calcType) || 0);
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 是否展示跑腿平台报价卡片 */
|
|
|
|
|
+ showPlatformQuotes() {
|
|
|
|
|
+ if (!this.runnerFeeAvailable) return false;
|
|
|
|
|
+ if (this.freightCalcMode === "bouquetIncluded") return false;
|
|
|
|
|
+ if (this.runnerCalcType === 1) return false;
|
|
|
|
|
+ if (["custom", "customFallback", "bouquetIncluded", "reduceFree"].includes(this.freightSource)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return Number(this.form.openIntraCity) === 1;
|
|
|
},
|
|
},
|
|
|
showDeliveryPolicy() {
|
|
showDeliveryPolicy() {
|
|
|
if (!this.shopInfo) return false;
|
|
if (!this.shopInfo) return false;
|
|
@@ -858,15 +924,31 @@ export default {
|
|
|
return Number(a.id || 0) - Number(b.id || 0);
|
|
return Number(a.id || 0) - Number(b.id || 0);
|
|
|
});
|
|
});
|
|
|
},
|
|
},
|
|
|
- /** 跑腿配送说明主文案 */
|
|
|
|
|
|
|
+ /** 跑腿配送说明主文案(随运费模式变化) */
|
|
|
runnerDeliveryNoteText() {
|
|
runnerDeliveryNoteText() {
|
|
|
|
|
+ const mode = this.freightCalcMode;
|
|
|
|
|
+ if (mode === "bouquetIncluded") {
|
|
|
|
|
+ return "订单含包运费花束,按花束包配送规则计费";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (mode === "bouquetNotIncluded") {
|
|
|
|
|
+ return this.runnerCalcType === 1
|
|
|
|
|
+ ? "花束不包运费,按门店自定义规则计费"
|
|
|
|
|
+ : "花束不包运费,按跑腿平台报价计费;报价异常时按自定义规则兜底";
|
|
|
|
|
+ }
|
|
|
if (this.runnerReduceRules.length > 0) {
|
|
if (this.runnerReduceRules.length > 0) {
|
|
|
- return "配送运费按跑腿平台报价计算,满足下方满减规则可免跑腿费。具体运费将在填写地址后自动计算";
|
|
|
|
|
|
|
+ return this.runnerCalcType === 1
|
|
|
|
|
+ ? "纯花材订单满足下方满减可免跑腿费,否则按自定义规则计费"
|
|
|
|
|
+ : "纯花材订单满足下方满减可免跑腿费,否则按跑腿报价;报价异常时按自定义规则兜底";
|
|
|
}
|
|
}
|
|
|
- return "配送运费按跑腿平台报价计算,具体运费将在填写地址后自动计算";
|
|
|
|
|
|
|
+ return this.runnerCalcType === 1
|
|
|
|
|
+ ? "配送运费按门店自定义规则计算"
|
|
|
|
|
+ : "配送运费按跑腿平台报价计算,异常时按自定义规则兜底";
|
|
|
},
|
|
},
|
|
|
- /** 跑腿免费配送条件(页内直接展示,来自 xhPsReduceRule) */
|
|
|
|
|
|
|
+ /** 花材满减规则文案:仅纯花材订单展示 */
|
|
|
inlineDeliveryPolicyLines() {
|
|
inlineDeliveryPolicyLines() {
|
|
|
|
|
+ if (this.freightCalcMode !== "flowerOnly") {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
return this.runnerReduceRules
|
|
return this.runnerReduceRules
|
|
|
.map((rule) => this.formatReduceRuleLine(rule))
|
|
.map((rule) => this.formatReduceRuleLine(rule))
|
|
|
.filter(Boolean);
|
|
.filter(Boolean);
|
|
@@ -979,8 +1061,8 @@ export default {
|
|
|
this.form.showAddress = item.showAddress || "";
|
|
this.form.showAddress = item.showAddress || "";
|
|
|
this.validAddress = !this.$util.isEmpty(item.lat) && !this.$util.isEmpty(item.long);
|
|
this.validAddress = !this.$util.isEmpty(item.lat) && !this.$util.isEmpty(item.long);
|
|
|
|
|
|
|
|
- if (this.form.sendType == 2 && Number(this.form.openIntraCity) == 1) {
|
|
|
|
|
- this.getDeliveryQuotes();
|
|
|
|
|
|
|
+ if (this.form.sendType == 2 && this.runnerFeeAvailable) {
|
|
|
|
|
+ this.refreshRunnerFreight();
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
/** 启用且按 sort 排序的配送方式列表 */
|
|
/** 启用且按 sort 排序的配送方式列表 */
|
|
@@ -1009,13 +1091,18 @@ export default {
|
|
|
} else {
|
|
} else {
|
|
|
this.applyCurrentUnMeetFee();
|
|
this.applyCurrentUnMeetFee();
|
|
|
this.ensureDefaultAddressIfNeeded();
|
|
this.ensureDefaultAddressIfNeeded();
|
|
|
|
|
+ // 配送方式配置加载完成后,若已是跑腿则按新规则重算运费
|
|
|
|
|
+ if (Number(this.form.sendType) === 2 && this.validAddress && this.runnerFeeAvailable) {
|
|
|
|
|
+ this.refreshRunnerFreight();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
/** 批量预览接口入参(商品金额 + 花材扎数) */
|
|
/** 批量预览接口入参(商品金额 + 花材扎数) */
|
|
|
getUnMeetFeeBatchParams() {
|
|
getUnMeetFeeBatchParams() {
|
|
|
return {
|
|
return {
|
|
|
itemTotalAmount: Number(this.mixGoodsPrice) || 0,
|
|
itemTotalAmount: Number(this.mixGoodsPrice) || 0,
|
|
|
- bigNum: Number(this.mixItemBigNum) || 0
|
|
|
|
|
|
|
+ // 附加费最低消费数量:花束份数一并计入,避免「还差 N 扎」少算已购花束
|
|
|
|
|
+ bigNum: Number(this.mixOrderBigNum) || 0
|
|
|
};
|
|
};
|
|
|
},
|
|
},
|
|
|
/** 写入各 style 附加费缓存并应用到当前购买方式 */
|
|
/** 写入各 style 附加费缓存并应用到当前购买方式 */
|
|
@@ -1106,9 +1193,8 @@ export default {
|
|
|
this.shopInfo = { ...this.shopInfo, ...intraShop };
|
|
this.shopInfo = { ...this.shopInfo, ...intraShop };
|
|
|
this.form.openIntraCity = intraShop.openIntraCity;
|
|
this.form.openIntraCity = intraShop.openIntraCity;
|
|
|
|
|
|
|
|
- if (Number(this.form.openIntraCity) == 1 && this.form.sendType == 2 && this.validAddress) {
|
|
|
|
|
- this.getDeliveryQuotes();
|
|
|
|
|
- this.checkFreeDelivery();
|
|
|
|
|
|
|
+ if (this.form.sendType == 2 && this.validAddress && this.runnerFeeAvailable) {
|
|
|
|
|
+ this.refreshRunnerFreight();
|
|
|
}
|
|
}
|
|
|
this.getHbData();
|
|
this.getHbData();
|
|
|
}
|
|
}
|
|
@@ -1350,40 +1436,35 @@ export default {
|
|
|
}
|
|
}
|
|
|
return `${freeKm}公里内免跑腿费`;
|
|
return `${freeKm}公里内免跑腿费`;
|
|
|
},
|
|
},
|
|
|
- /** 跑腿模式下商品/距离变化后重算是否免跑腿费 */
|
|
|
|
|
|
|
+ /** 商品金额/扎数变化后:纯花材可能影响满减,需重算跑腿费 */
|
|
|
recheckRunnerFreeDelivery() {
|
|
recheckRunnerFreeDelivery() {
|
|
|
- if (Number(this.form.sendType) !== 2 || !this.selectedDeliveryData) {
|
|
|
|
|
|
|
+ if (Number(this.form.sendType) !== 2 || !this.validAddress || !this.runnerFeeAvailable) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- this.checkFreeDelivery();
|
|
|
|
|
|
|
+ if (this.freightCalcMode === "flowerOnly") {
|
|
|
|
|
+ this.refreshRunnerFreight();
|
|
|
|
|
+ }
|
|
|
},
|
|
},
|
|
|
/**
|
|
/**
|
|
|
- * 跑腿是否免配送费:按当前购买方式 xhPsReduceRule 判断
|
|
|
|
|
- * 规则:满 meetNum 扎且满 meetAmount 元,且在 freeKm 公里内
|
|
|
|
|
|
|
+ * 纯花材是否命中 xhPsReduceRule 免跑腿(含花束订单不读满减)
|
|
|
*/
|
|
*/
|
|
|
checkFreeDelivery() {
|
|
checkFreeDelivery() {
|
|
|
- if (this.displaySendCost == false) {
|
|
|
|
|
|
|
+ if (Number(this.form.sendType) !== 2 || this.freightCalcMode !== "flowerOnly") {
|
|
|
this.isFreeDelivery = false;
|
|
this.isFreeDelivery = false;
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- if (Number(this.form.sendType) !== 2) {
|
|
|
|
|
|
|
+ if (!this.displaySendCost) {
|
|
|
this.isFreeDelivery = false;
|
|
this.isFreeDelivery = false;
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
const distanceInMeters = Number(this.showDistance) || 0;
|
|
const distanceInMeters = Number(this.showDistance) || 0;
|
|
|
const flowerCount = Number(this.mixItemBigNum) || 0;
|
|
const flowerCount = Number(this.mixItemBigNum) || 0;
|
|
|
const flowerPrice = Number(this.mixGoodsPrice) || 0;
|
|
const flowerPrice = Number(this.mixGoodsPrice) || 0;
|
|
|
- const rules = this.runnerReduceRules;
|
|
|
|
|
-
|
|
|
|
|
- for (const rule of rules) {
|
|
|
|
|
|
|
+ for (const rule of this.runnerReduceRules) {
|
|
|
const meetNum = Number(rule.meetNum) || 0;
|
|
const meetNum = Number(rule.meetNum) || 0;
|
|
|
const meetAmount = Number(rule.meetAmount) || 0;
|
|
const meetAmount = Number(rule.meetAmount) || 0;
|
|
|
const freeKm = Number(rule.freeKm) || 0;
|
|
const freeKm = Number(rule.freeKm) || 0;
|
|
|
- if (freeKm <= 0) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (freeKm <= 0) continue;
|
|
|
const freeKmInMeters = freeKm * 1000;
|
|
const freeKmInMeters = freeKm * 1000;
|
|
|
const numOk = meetNum <= 0 || flowerCount >= meetNum;
|
|
const numOk = meetNum <= 0 || flowerCount >= meetNum;
|
|
|
const amountOk = meetAmount <= 0 || flowerPrice >= meetAmount;
|
|
const amountOk = meetAmount <= 0 || flowerPrice >= meetAmount;
|
|
@@ -1392,10 +1473,80 @@ export default {
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
this.isFreeDelivery = false;
|
|
this.isFreeDelivery = false;
|
|
|
return false;
|
|
return false;
|
|
|
},
|
|
},
|
|
|
|
|
+ /** 花束包运费:hsFreeKm 内 0,超出按 hsPerKmPrice 每公里(向上取整) */
|
|
|
|
|
+ calcBouquetFreight(distanceMeters) {
|
|
|
|
|
+ const m = this.runnerPsConfig || {};
|
|
|
|
|
+ const freeMeters = (Number(m.hsFreeKm) || 0) * 1000;
|
|
|
|
|
+ const perKm = Number(m.hsPerKmPrice) || 0;
|
|
|
|
|
+ const dist = Number(distanceMeters) || 0;
|
|
|
|
|
+ if (dist <= freeMeters) return 0;
|
|
|
|
|
+ const km = Math.ceil((dist - freeMeters) / 1000);
|
|
|
|
|
+ return Math.round(km * perKm * 100) / 100;
|
|
|
|
|
+ },
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 自定义计费:起步价 + 超出公里 * 每公里价,再乘临时涨降价
|
|
|
|
|
+ * 超出部分不足 1 公里按 1 公里计(向上取整)
|
|
|
|
|
+ * 例:5km 内 2 元、超出每公里 1 元、涨 3%、距离 7km → (2+2)*1.03=4.12
|
|
|
|
|
+ */
|
|
|
|
|
+ calcCustomFreight(distanceKm) {
|
|
|
|
|
+ const m = this.runnerPsConfig || {};
|
|
|
|
|
+ const startKm = Number(m.startKm) || 0;
|
|
|
|
|
+ const startPrice = Number(m.startPrice) || 0;
|
|
|
|
|
+ const perKmPrice = Number(m.perKmPrice) || 0;
|
|
|
|
|
+ const changeRate = Number(m.changeRate) || 0;
|
|
|
|
|
+ if (startKm <= 0 || startPrice <= 0 || perKmPrice <= 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 与后端一致,公里保留两位再计费
|
|
|
|
|
+ const km = Math.round((Number(distanceKm) || 0) * 100) / 100;
|
|
|
|
|
+ if (km <= 0) return null;
|
|
|
|
|
+ let sendCost = startPrice;
|
|
|
|
|
+ if (km > startKm) {
|
|
|
|
|
+ const extra = Math.round((km - startKm) * 100) / 100;
|
|
|
|
|
+ // 不足 1 公里按 1 公里
|
|
|
|
|
+ const billableExtra = Math.ceil(extra - 1e-9);
|
|
|
|
|
+ sendCost += billableExtra * perKmPrice;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (changeRate !== 0) {
|
|
|
|
|
+ sendCost = sendCost * (1 + changeRate / 100);
|
|
|
|
|
+ }
|
|
|
|
|
+ return Math.round(sendCost * 100) / 100;
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 拉取门店到收货地址距离(米;勿用 showDistance,其为公里字符串) */
|
|
|
|
|
+ fetchDeliveryDistanceMeters() {
|
|
|
|
|
+ const lat = this.form.lat || this.userInfo.lat;
|
|
|
|
|
+ const lng = this.form.long || this.userInfo.long;
|
|
|
|
|
+ return getUserDistance({ lat, lng }).then((res) => {
|
|
|
|
|
+ if (res.code == 1 && res.data) {
|
|
|
|
|
+ const meters = Number(res.data.distance);
|
|
|
|
|
+ return Number.isFinite(meters) && meters > 0 ? meters : 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }).catch(() => 0);
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 应用自定义/兜底运费到表单 */
|
|
|
|
|
+ applyCustomFreightResult(distanceMeters, source) {
|
|
|
|
|
+ const distanceKm = (Number(distanceMeters) || 0) / 1000;
|
|
|
|
|
+ const fee = this.calcCustomFreight(distanceKm);
|
|
|
|
|
+ this.showDistance = distanceMeters;
|
|
|
|
|
+ this.deliveryQuotes = [];
|
|
|
|
|
+ this.selectedDeliveryIndex = -1;
|
|
|
|
|
+ this.selectedDeliveryData = null;
|
|
|
|
|
+ if (fee == null) {
|
|
|
|
|
+ this.form.sendCost = 0;
|
|
|
|
|
+ this.freightSource = "";
|
|
|
|
|
+ this.isFreeDelivery = false;
|
|
|
|
|
+ this.$msg("自定义计费规则未配置完整,无法计算运费");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.form.sendCost = fee;
|
|
|
|
|
+ this.freightSource = source;
|
|
|
|
|
+ this.isFreeDelivery = fee === 0;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ },
|
|
|
selTimeFn(e) {
|
|
selTimeFn(e) {
|
|
|
this.form.reachDate = e.detail.value;
|
|
this.form.reachDate = e.detail.value;
|
|
|
},
|
|
},
|
|
@@ -1487,9 +1638,8 @@ export default {
|
|
|
this.ensureDefaultAddressIfNeeded();
|
|
this.ensureDefaultAddressIfNeeded();
|
|
|
}
|
|
}
|
|
|
if (num == 2) {
|
|
if (num == 2) {
|
|
|
- if (Number(this.form.openIntraCity) == 1 && this.validAddress) {
|
|
|
|
|
- this.getDeliveryQuotes();
|
|
|
|
|
- this.checkFreeDelivery();
|
|
|
|
|
|
|
+ if (this.runnerFeeAvailable && this.validAddress) {
|
|
|
|
|
+ this.refreshRunnerFreight();
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
this.deliveryQuotes = [];
|
|
this.deliveryQuotes = [];
|
|
@@ -1498,6 +1648,8 @@ export default {
|
|
|
this.form.sendCost = 0;
|
|
this.form.sendCost = 0;
|
|
|
this.deliveryQuotesLoading = false;
|
|
this.deliveryQuotesLoading = false;
|
|
|
this.showDistance = 0;
|
|
this.showDistance = 0;
|
|
|
|
|
+ this.freightSource = "";
|
|
|
|
|
+ this.isFreeDelivery = false;
|
|
|
}
|
|
}
|
|
|
this.applyCurrentUnMeetFee();
|
|
this.applyCurrentUnMeetFee();
|
|
|
},
|
|
},
|
|
@@ -1521,25 +1673,97 @@ export default {
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
},
|
|
},
|
|
|
- getDeliveryQuotes() {
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 按规则刷新跑腿运费:包运费花束 / 花材满减 / 跑腿报价 / 自定义与异常兜底
|
|
|
|
|
+ */
|
|
|
|
|
+ refreshRunnerFreight() {
|
|
|
|
|
+ if (Number(this.form.sendType) !== 2) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!this.runnerFeeAvailable) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
if (this.$util.isEmpty(this.userInfo.lat) || this.$util.isEmpty(this.userInfo.long)) {
|
|
if (this.$util.isEmpty(this.userInfo.lat) || this.$util.isEmpty(this.userInfo.long)) {
|
|
|
this.form.sendCost = 0;
|
|
this.form.sendCost = 0;
|
|
|
|
|
+ this.freightSource = "";
|
|
|
this.$msg("请填写地址哈");
|
|
this.$msg("请填写地址哈");
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
+ const mode = this.freightCalcMode;
|
|
|
|
|
+ this.deliveryQuotesLoading = true;
|
|
|
|
|
+ this.isFreeDelivery = false;
|
|
|
|
|
+ this.freightSource = "";
|
|
|
|
|
+
|
|
|
|
|
+ // A:含包运费花束 → 只计距 + 花束运费
|
|
|
|
|
+ if (mode === "bouquetIncluded") {
|
|
|
|
|
+ this.deliveryQuotes = [];
|
|
|
|
|
+ this.selectedDeliveryIndex = -1;
|
|
|
|
|
+ this.selectedDeliveryData = null;
|
|
|
|
|
+ this.fetchDeliveryDistanceMeters().then((meters) => {
|
|
|
|
|
+ this.showDistance = meters;
|
|
|
|
|
+ const fee = this.calcBouquetFreight(meters);
|
|
|
|
|
+ this.form.sendCost = fee;
|
|
|
|
|
+ this.freightSource = "bouquetIncluded";
|
|
|
|
|
+ this.isFreeDelivery = fee === 0;
|
|
|
|
|
+ }).finally(() => {
|
|
|
|
|
+ this.deliveryQuotesLoading = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 先取距离,再判满减 / 自定义 / 跑腿
|
|
|
|
|
+ this.fetchDeliveryDistanceMeters().then((meters) => {
|
|
|
|
|
+ this.showDistance = meters;
|
|
|
|
|
+ if (mode === "flowerOnly" && this.checkFreeDelivery()) {
|
|
|
|
|
+ this.form.sendCost = 0;
|
|
|
|
|
+ this.freightSource = "reduceFree";
|
|
|
|
|
+ this.deliveryQuotes = [];
|
|
|
|
|
+ this.selectedDeliveryIndex = -1;
|
|
|
|
|
+ this.selectedDeliveryData = null;
|
|
|
|
|
+ this.deliveryQuotesLoading = false;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.runnerCalcType === 1) {
|
|
|
|
|
+ this.applyCustomFreightResult(meters, "custom");
|
|
|
|
|
+ this.deliveryQuotesLoading = false;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ // calcType=0:先尝试平台报价,失败则自定义兜底
|
|
|
|
|
+ this.fetchPlatformQuotesWithFallback(meters);
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ this.deliveryQuotesLoading = false;
|
|
|
|
|
+ if (this.hasValidRunnerCustomConfig) {
|
|
|
|
|
+ this.fetchDeliveryDistanceMeters().then((meters) => {
|
|
|
|
|
+ this.applyCustomFreightResult(meters, "customFallback");
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.$msg("获取距离失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 拉跑腿平台报价;失败或无可用平台时改用自定义兜底 */
|
|
|
|
|
+ fetchPlatformQuotesWithFallback(distanceMeters) {
|
|
|
const hasBouquet = (this.list || []).some((item) => this.isBouquetItem(item));
|
|
const hasBouquet = (this.list || []).some((item) => this.isBouquetItem(item));
|
|
|
if (Number(this.allCount.weight) <= 0 && !hasBouquet) {
|
|
if (Number(this.allCount.weight) <= 0 && !hasBouquet) {
|
|
|
- this.$msg("商品没有重量,无法使用跑腿");
|
|
|
|
|
- return false;
|
|
|
|
|
|
|
+ this.deliveryQuotesLoading = false;
|
|
|
|
|
+ if (this.hasValidRunnerCustomConfig) {
|
|
|
|
|
+ this.applyCustomFreightResult(distanceMeters, "customFallback");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.$msg("商品没有重量,无法使用跑腿");
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (Number(this.form.openIntraCity) !== 1) {
|
|
|
|
|
+ this.deliveryQuotesLoading = false;
|
|
|
|
|
+ this.applyCustomFreightResult(distanceMeters, "customFallback");
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
this.deliveryQuotes = [];
|
|
this.deliveryQuotes = [];
|
|
|
this.selectedDeliveryIndex = -1;
|
|
this.selectedDeliveryIndex = -1;
|
|
|
this.selectedDeliveryData = null;
|
|
this.selectedDeliveryData = null;
|
|
|
- this.showDistance = 0;
|
|
|
|
|
- this.deliveryQuotesLoading = true;
|
|
|
|
|
-
|
|
|
|
|
const hasItem = (this.list || []).some((item) => !this.isBouquetItem(item));
|
|
const hasItem = (this.list || []).some((item) => !this.isBouquetItem(item));
|
|
|
- const buyType = hasItem || (hasItem && hasBouquet) ? "huaCai" : hasBouquet ? "huaShu" : "huaCai";
|
|
|
|
|
|
|
+ const buyType = hasItem ? "huaCai" : hasBouquet ? "huaShu" : "huaCai";
|
|
|
const { account, hdId } = this.syncShopContext();
|
|
const { account, hdId } = this.syncShopContext();
|
|
|
allDeliveryQuotes({
|
|
allDeliveryQuotes({
|
|
|
buyType,
|
|
buyType,
|
|
@@ -1556,18 +1780,15 @@ export default {
|
|
|
.filter((item) => item.isAble !== false)
|
|
.filter((item) => item.isAble !== false)
|
|
|
.map((item) => {
|
|
.map((item) => {
|
|
|
const price = item.price ? (item.price / 100).toFixed(1) : "0.0";
|
|
const price = item.price ? (item.price / 100).toFixed(1) : "0.0";
|
|
|
-
|
|
|
|
|
let displayName = item.name;
|
|
let displayName = item.name;
|
|
|
let bracketContent = "";
|
|
let bracketContent = "";
|
|
|
if (displayName && displayName.includes("(")) {
|
|
if (displayName && displayName.includes("(")) {
|
|
|
bracketContent = displayName.split("(")[1].replace(")", "");
|
|
bracketContent = displayName.split("(")[1].replace(")", "");
|
|
|
displayName = displayName.split("(")[0].trim();
|
|
displayName = displayName.split("(")[0].trim();
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
if (item.en_name == "didi" && bracketContent != "") {
|
|
if (item.en_name == "didi" && bracketContent != "") {
|
|
|
item.type = bracketContent;
|
|
item.type = bracketContent;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
return {
|
|
return {
|
|
|
...item,
|
|
...item,
|
|
|
displayName,
|
|
displayName,
|
|
@@ -1576,25 +1797,25 @@ export default {
|
|
|
bracketContent
|
|
bracketContent
|
|
|
};
|
|
};
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
const firstAvailableIndex = this.deliveryQuotes.findIndex((item) => item.isAble);
|
|
const firstAvailableIndex = this.deliveryQuotes.findIndex((item) => item.isAble);
|
|
|
if (firstAvailableIndex !== -1) {
|
|
if (firstAvailableIndex !== -1) {
|
|
|
this.selectDelivery(this.deliveryQuotes[firstAvailableIndex], firstAvailableIndex);
|
|
this.selectDelivery(this.deliveryQuotes[firstAvailableIndex], firstAvailableIndex);
|
|
|
- } else {
|
|
|
|
|
- this.form.sendCost = 0;
|
|
|
|
|
- this.showDistance = 0;
|
|
|
|
|
|
|
+ this.freightSource = "platform";
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
- this.showDistance = 0;
|
|
|
|
|
- uni.showToast({ title: "没有跑腿可用", icon: "none" });
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+ // 无可用平台 → 自定义兜底
|
|
|
|
|
+ this.applyCustomFreightResult(distanceMeters || this.showDistance, "customFallback");
|
|
|
}).catch(() => {
|
|
}).catch(() => {
|
|
|
- this.showDistance = 0;
|
|
|
|
|
- uni.showToast({ title: "获取报价失败", icon: "none" });
|
|
|
|
|
|
|
+ this.applyCustomFreightResult(distanceMeters || this.showDistance, "customFallback");
|
|
|
}).finally(() => {
|
|
}).finally(() => {
|
|
|
this.deliveryQuotesLoading = false;
|
|
this.deliveryQuotesLoading = false;
|
|
|
});
|
|
});
|
|
|
},
|
|
},
|
|
|
|
|
+ /** 兼容旧调用名 */
|
|
|
|
|
+ getDeliveryQuotes() {
|
|
|
|
|
+ this.refreshRunnerFreight();
|
|
|
|
|
+ },
|
|
|
selectDelivery(item, index) {
|
|
selectDelivery(item, index) {
|
|
|
if (!item.isAble) {
|
|
if (!item.isAble) {
|
|
|
return;
|
|
return;
|
|
@@ -1602,8 +1823,18 @@ export default {
|
|
|
this.selectedDeliveryIndex = index;
|
|
this.selectedDeliveryIndex = index;
|
|
|
this.selectedDeliveryData = item;
|
|
this.selectedDeliveryData = item;
|
|
|
this.form.sendCost = item.priceNumber;
|
|
this.form.sendCost = item.priceNumber;
|
|
|
- this.showDistance = item.distance || 0;
|
|
|
|
|
- this.checkFreeDelivery();
|
|
|
|
|
|
|
+ this.showDistance = item.distance || this.showDistance || 0;
|
|
|
|
|
+ this.freightSource = "platform";
|
|
|
|
|
+ // 平台报价场景下花材满减已在 refresh 前置处理;此处仅同步免运标记
|
|
|
|
|
+ if (this.freightCalcMode === "flowerOnly") {
|
|
|
|
|
+ this.checkFreeDelivery();
|
|
|
|
|
+ if (this.isFreeDelivery) {
|
|
|
|
|
+ this.form.sendCost = 0;
|
|
|
|
|
+ this.freightSource = "reduceFree";
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.isFreeDelivery = false;
|
|
|
|
|
+ }
|
|
|
},
|
|
},
|
|
|
confirmForm() {
|
|
confirmForm() {
|
|
|
const hdId = this.option.hdId ? this.option.hdId : uni.getStorageSync("hdId") || 0;
|
|
const hdId = this.option.hdId ? this.option.hdId : uni.getStorageSync("hdId") || 0;
|
|
@@ -1693,10 +1924,14 @@ export default {
|
|
|
if (this.isFreeDelivery) {
|
|
if (this.isFreeDelivery) {
|
|
|
params.sendCost = 0;
|
|
params.sendCost = 0;
|
|
|
}
|
|
}
|
|
|
- if (this.form.sendType == 2 && this.selectedDeliveryData) {
|
|
|
|
|
|
|
+ if (this.form.sendType == 2) {
|
|
|
|
|
+ params.freightSource = this.freightSource || "";
|
|
|
|
|
+ params.deliveryDistance = this.showDistance || 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.form.sendType == 2 && this.freightSource === "platform" && this.selectedDeliveryData) {
|
|
|
params.deliveryPlatform = this.selectedDeliveryData.en_name;
|
|
params.deliveryPlatform = this.selectedDeliveryData.en_name;
|
|
|
params.deliveryPrice = this.selectedDeliveryData.priceNumber;
|
|
params.deliveryPrice = this.selectedDeliveryData.priceNumber;
|
|
|
- params.deliveryDistance = this.selectedDeliveryData.distance || 0;
|
|
|
|
|
|
|
+ params.deliveryDistance = this.selectedDeliveryData.distance || this.showDistance || 0;
|
|
|
params.deliveryBracketContent = this.selectedDeliveryData.bracketContent;
|
|
params.deliveryBracketContent = this.selectedDeliveryData.bracketContent;
|
|
|
}
|
|
}
|
|
|
if (this.form.sendType == 2 || this.form.sendType == 0) {
|
|
if (this.form.sendType == 2 || this.form.sendType == 0) {
|
|
@@ -1706,12 +1941,20 @@ export default {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
if (this.form.sendType == 2) {
|
|
if (this.form.sendType == 2) {
|
|
|
- if (Number(this.form.sendCost) < 0) {
|
|
|
|
|
|
|
+ if (Number(this.form.openIntraCity) == 2 || !this.runnerFeeAvailable) {
|
|
|
|
|
+ this.$msg("跑腿不可用,请用其它方式");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.freightSource === "platform" && !this.selectedDeliveryData) {
|
|
|
|
|
+ this.$msg("请选择跑腿");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!this.freightSource && Number(this.form.sendCost) < 0) {
|
|
|
this.$msg("没有获取到跑腿费");
|
|
this.$msg("没有获取到跑腿费");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
- if (Number(this.form.openIntraCity) == 2) {
|
|
|
|
|
- this.$msg("跑腿不可用,请用其它方式");
|
|
|
|
|
|
|
+ if (!this.isFreeDelivery && !this.freightSource && Number(this.form.sendCost) === 0 && this.freightCalcMode !== "bouquetIncluded") {
|
|
|
|
|
+ this.$msg("没有获取到跑腿费");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|