Просмотр исходного кода

配送方式 正式开始大改

shish 1 месяц назад
Родитель
Сommit
c09dbc2aee

+ 111 - 44
hdApp/src/admin/billing/affirmGhs.vue

@@ -50,7 +50,20 @@
 					</view>
 				</view>
 
-				<view class="module-com input-line-wrap">
+				<view class="module-com input-line-wrap" v-if="isShMethodPanelShop">
+					<affirm-sh-method-panel
+						v-if="ghsInfo.pfLevel == 0"
+						ref="shMethodPanel"
+						:ghs-info="ghsInfo"
+						:ghs-id="option.id"
+						:commodity-price="allPriceFun"
+						:commodity-big-count="allCountFun.bigLength"
+						:commodity-weight="allCountFun.weight"
+						:select-list="selectList"
+						@change="onShMethodPanelChange"
+					/>
+				</view>
+				<view class="module-com input-line-wrap" v-else>
 					<block v-if="ghsInfo.pfLevel && ghsInfo.pfLevel == 1">
 
 						<tui-list-cell class="line-cell" :hover="false" :arrow="false" v-if="ghsInfo.shopId == 14636">
@@ -407,6 +420,11 @@ import { shopHouponHas } from '@/api/coupon'
 import {currentShop} from "@/api/shop"
 import { allDeliveryQuotes } from "@/api/express/delivery";
 import ModalModule from "@/components/plugin/modal";
+import AffirmShMethodPanel from "@/admin/billing/components/affirm-sh-method-panel";
+
+/** 使用 getShMethod 配送业务面板的门店 shopId,后续可追加 */
+const SH_METHOD_PANEL_SHOP_IDS = [36523];
+
 export default {
 	name: "affirmGhs",
 	components: {
@@ -420,7 +438,8 @@ export default {
 		appAddressGroup,
 		appFormSend,
 		AppDatePicker,
-		ModalModule
+		ModalModule,
+		AffirmShMethodPanel
 	},
 	mixins: [productMins],
 	data() {
@@ -486,7 +505,8 @@ export default {
 			selectedDeliveryIndex: -1, // 选中的报价索引
 			selectedDeliveryData: null, // 选中的报价数据
 			deliveryPolicyList: [], // 包配送政策列表
-			validAddress:false
+			validAddress:false,
+			shMethodPanelState: null
 		};
 	},
 	onLoad() {
@@ -499,20 +519,34 @@ export default {
 			this.form.hbId = 0
 			this.hasCoupon()
 		}
-		if(this.form.sendType == 2){
-			//计算运费
-			//this.calcFreight()
+		if(this.isShMethodPanelActive && this.$refs.shMethodPanel){
+			this.$refs.shMethodPanel.onPageShow()
+		}else{
+			if(this.form.sendType == 2){
+				//计算运费
+				//this.calcFreight()
 
-			if(this.displaySendCost == true){
-				//各跑腿平台报价
-				this.getDeliveryQuotes()
+				if(this.displaySendCost == true){
+					//各跑腿平台报价
+					this.getDeliveryQuotes()
+				}
 			}
+			//修改地址更新
+			this.getCurrentShop()
 		}
-		//修改地址更新
-		this.getCurrentShop()
 	},
 	computed: {
 		...mapGetters(["getMerchantInfo", "getLoginInfo","getDictionariesInfo"]),
+		/** 当前门店是否使用 getShMethod 配送业务面板 */
+		isShMethodPanelShop() {
+			return this.ghsInfo &&
+				SH_METHOD_PANEL_SHOP_IDS.includes(Number(this.ghsInfo.shopId))
+		},
+		/** 配送业务面板生效:命中门店列表且 pfLevel 为同城 */
+		isShMethodPanelActive() {
+			return this.isShMethodPanelShop &&
+				Number(this.ghsInfo.pfLevel) === 0
+		},
 		/** 确认页列表每行价格展示(与 selectList 顺序一致) */
 		affirmCommodityPriceParts() {
 			const list = this.selectList || [];
@@ -638,6 +672,14 @@ export default {
 		 * @returns {number}
 		 */
 		modifyPrice(){
+			if (this.isShMethodPanelActive) {
+				if (this.shMethodPanelState) {
+					return this.shMethodPanelState.totalPrice
+				}
+				if (this.$refs.shMethodPanel) {
+					return this.$refs.shMethodPanel.panelTotalPrice
+				}
+			}
 			let allPrice = this.allPriceFun.toFixed(2)
 			allPrice = Number(allPrice)
 			let price = allPrice
@@ -658,12 +700,16 @@ export default {
 					price = price + this.form.packCost
 				}
 			}else{
-				//同城配送
-				if(this.form.sendType == 4){
-					//设置了订单金额低200需要付包装费,当前只付一次
-					if(Number(this.lackExpend) > 0 && allPrice < Number(this.lackExpend)){
-						price = price + this.form.packCost
-						this.needAddPackCost = true
+				if(this.isShMethodPanelShop){
+					// 配送业务面板门店费用由子组件计算
+				}else{
+					//同城配送
+					if(this.form.sendType == 4){
+						//设置了订单金额低200需要付包装费,当前只付一次
+						if(Number(this.lackExpend) > 0 && allPrice < Number(this.lackExpend)){
+							price = price + this.form.packCost
+							this.needAddPackCost = true
+						}
 					}
 				}
 			}
@@ -675,6 +721,19 @@ export default {
 	},
 	methods: {
 		...mapActions(["initMerchantInfo"]),
+		/** 同步配送业务面板状态到父页面表单 */
+		onShMethodPanelChange(state) {
+			this.shMethodPanelState = state
+			this.form.sendType = state.sendType
+			this.form.wlName = state.wlName
+			this.form.wlId = state.wlId
+			this.form.sendCost = state.sendCost
+			this.form.packCost = state.packCost
+			this.sendTimeWant = state.sendTimeWant
+			this.remark = state.remark
+			this.selectedDeliveryData = state.selectedDeliveryData
+			this.showDistance = state.showDistance
+		},
 		changeWlFn(e) {
 			this.wlSelData = this.wlList[e.detail.value];
 			this.form.wlId = this.wlSelData.id
@@ -1057,35 +1116,39 @@ export default {
 				that.lackExpend = res.data.lackExpend?parseFloat(res.data.lackExpend):0
 				that.form.wlName = res.data.customWlName ? res.data.customWlName : ''
 
-				//杭州斗南鲜花批发部不需要显示免费配送
-				if(that.ghsInfo.shopId && Number(that.ghsInfo.shopId) == 4608){
-					that.form.sendType = 1
-				}
-				//万花田济南仓默认自取
-				if(that.ghsInfo.shopId && Number(that.ghsInfo.shopId) == 90985){
-					that.form.sendType = 1
-				}
-				//鼎盛花卉默认自取
-				if(that.ghsInfo.shopId && Number(that.ghsInfo.shopId) == 36919){
-					that.form.sendType = 1
-				}
-				//天天鲜花虎门店默认自取
-				if(that.ghsInfo.shopId && Number(that.ghsInfo.shopId) == 91053){
-					that.form.sendType = 1
-				}
-				if(res.data && res.data.shopId == 14636){
-					//花瀚鲜花供应链默认冷链
-					this.changeTransType(2)
-				}else if(res.data && res.data.shopId == 3){
-					//石头花艺
-					this.changeTransType(2)
-				}else if(res.data && res.data.shopId == 29136){
-					this.changeTransType(0)
+				if(SH_METHOD_PANEL_SHOP_IDS.includes(Number(that.ghsInfo.shopId))){
+
 				}else{
-					this.changeTransType(4)
+					//杭州斗南鲜花批发部不需要显示免费配送
+					if(that.ghsInfo.shopId && Number(that.ghsInfo.shopId) == 4608){
+						that.form.sendType = 1
+					}
+					//万花田济南仓默认自取
+					if(that.ghsInfo.shopId && Number(that.ghsInfo.shopId) == 90985){
+						that.form.sendType = 1
+					}
+					//鼎盛花卉默认自取
+					if(that.ghsInfo.shopId && Number(that.ghsInfo.shopId) == 36919){
+						that.form.sendType = 1
+					}
+					//天天鲜花虎门店默认自取
+					if(that.ghsInfo.shopId && Number(that.ghsInfo.shopId) == 91053){
+						that.form.sendType = 1
+					}
+					if(res.data && res.data.shopId == 14636){
+						//花瀚鲜花供应链默认冷链
+						this.changeTransType(2)
+					}else if(res.data && res.data.shopId == 3){
+						//石头花艺
+						this.changeTransType(2)
+					}else if(res.data && res.data.shopId == 29136){
+						this.changeTransType(0)
+					}else{
+						this.changeTransType(4)
+					}
 				}
+			})
 
-			});
 		},
 		countAllPrices(e){
 			this.allPrices = this.allPrices+Number(this.form.sendCost)
@@ -1114,7 +1177,11 @@ export default {
 			})
 
 			if(this.ghsInfo.pfLevel == 0){
-				if(this.form.sendType == 3){
+				if (this.isShMethodPanelActive) {
+					if (!this.$refs.shMethodPanel || !this.$refs.shMethodPanel.validateBeforeSubmit()) {
+						return false
+					}
+				} else if(this.form.sendType == 3){
 					if (this.$util.isEmpty(this.form.wlName)){
 						this.$msg('请填选物流')
 						return false

+ 776 - 0
hdApp/src/admin/billing/components/affirm-sh-method-panel.vue

@@ -0,0 +1,776 @@
+<!--
+  36523 门店确认页配送业务面板:拉取配置、配送方式、地址、跑腿报价、费用计算、提交校验。
+  供 affirmGhs.vue 引用,通过 change 事件向父页面同步下单所需字段。
+-->
+<template>
+  <view>
+    <tui-list-cell class="line-cell" :hover="false" :arrow="false">
+      <button
+        v-for="(item, index) in visibleMethodList"
+        :key="item.style"
+        class="admin-button-com middle sh-method-btn"
+        :class="[
+          selectedMethodIndex === index ? 'blue' : 'default',
+          item.name && item.name.length <= 2 ? 'sh-method-btn--short' : 'sh-method-btn--long'
+        ]"
+        @click="selectMethodByIndex(index)"
+      >{{ item.name }}</button>
+    </tui-list-cell>
+
+    <tui-list-cell
+      v-if="currentMethodExplains.length > 0"
+      class="line-cell"
+      :hover="false"
+      :arrow="false"
+    >
+      <view>
+        <view v-for="(exp, index) in currentMethodExplains" :key="index">
+          <text :style="'width:100%;color:' + (Number(exp.color) === 2 ? 'red' : Number(exp.color) === 3 ? '#3385FF' : '#333333') + ';font-weight:' + (Number(exp.fontWeight) === 2 ? 'bold' : 'normal') + ';'">{{ exp.explain }}</text>
+        </view>
+      </view>
+    </tui-list-cell>
+
+    <block v-if="$util.isEmpty(wlList) && localForm.sendType == 3">
+      <tui-list-cell class="line-cell" :hover="false">
+        <view class="tui-title">配送物流</view>
+        <input type="text" placeholder="请填选物流" v-model="localForm.wlName" @focus="localForm.wlName=''" placeholder-class="tui-placeholder"/>
+      </tui-list-cell>
+    </block>
+    <block v-else>
+      <tui-list-cell class="line-cell" :arrow="true" v-if="localForm.sendType == 3">
+        <div class="tui-title">配送物流</div>
+        <picker mode="selector" :value="localForm.wlId" :range="wlList" range-key="name" @change="changeWlFn" class="tui-input">
+          <div v-if="localForm.wlId">{{ wlSelData.name }}</div>
+          <div class="tui-placeholder" v-else>请选择</div>
+        </picker>
+      </tui-list-cell>
+    </block>
+
+    <tui-list-cell class="line-cell" :hover="false" :arrow="false" v-if="showShMethodExtraFee">
+      <view>
+        <text style="color: red;width:100%;">{{ shMethodUnMeetWarningText }}</text>
+      </view>
+    </tui-list-cell>
+
+    <tui-list-cell v-if="localForm.sendType == 2 && ghsInfo.openIntraCity != 1" class="line-cell" :hover="false" :arrow="false">
+      <view>
+        <text v-if="ghsInfo.openIntraCity == 0" style="color: #3385FF;width:100%;font-size:30upx;font-weight:bold;">运费自理或到付(请联系客服)</text>
+        <text v-if="ghsInfo.openIntraCity == 2" style="color: red;width:100%;font-size:30upx;font-weight:bold;">跑腿配送暂不可用,请选其它方式</text>
+      </view>
+    </tui-list-cell>
+
+    <tui-list-cell class="line-cell" :hover="false" :arrow="true" v-if="displayAddress" @click="modifyAddress">
+      <view class="tui-title">收花地址</view>
+      <view v-if="validAddress || ghsInfo.shopId==10649 || ghsInfo.shopId ==33668">
+        <view style="overflow: hidden;text-overflow: ellipsis;white-space: nowrap;width:420upx;"><text>{{ shopInfo.address }}</text></view>
+        <view v-if="shopInfo.showAddress!=shopInfo.fullAddress" style="color:#848181;"><text>{{ shopInfo.showAddress }}</text></view>
+      </view>
+      <view v-else>
+        <view><text style="color:red;font-weight:bold;font-size:30upx;">请填写您的地址</text></view>
+      </view>
+    </tui-list-cell>
+
+    <tui-list-cell v-if="localForm.sendType == 2 && ghsInfo.openIntraCity == 1" class="line-cell" :hover="false">
+      <view v-if="ghsInfo.openIntraCity == 1 && deliveryQuotes.length > 0" class="delivery-quotes-container">
+        <view class="delivery-quotes-grid">
+          <view
+            v-for="(item, index) in deliveryQuotes"
+            :key="index"
+            class="delivery-quote-item"
+            :class="{
+              'active': selectedDeliveryIndex === index,
+              'disabled': !item.isAble,
+              'fixed-width': deliveryQuotes.length <= 2
+            }"
+            @click="selectDelivery(item, index)"
+          >
+            <view class="platform-name">{{ item.displayName || item.name }}</view>
+            <view class="platform-price">{{ item.priceText }}元</view>
+            <view v-if="item.type" class="platform-type">{{ item.type }}</view>
+          </view>
+        </view>
+      </view>
+      <view v-else style="text-align: center; padding: 20upx 0;">
+        <text v-if="validAddress">正在获取运费...</text>
+        <text v-else style="color:red;font-weight:bold;font-size:32upx;" @click="modifyAddress">收货地址缺失,请填写</text>
+      </view>
+    </tui-list-cell>
+
+    <tui-list-cell class="line-cell" :hover="false" v-if="displaySendCost && Number(showDistance)>0">
+      <view class="tui-title">距离</view>
+      <text v-if="showDistance > 100" style="color: #3385FF">{{ (showDistance/1000).toFixed(1) }}公里</text>
+      <text v-else style="color: #3385FF">{{ showDistance }} 米</text>
+      <text v-if="showDeliveryPolicy" class="delivery-policy-link" @click="showDeliveryPolicyModal">免跑腿费条件</text>
+    </tui-list-cell>
+
+    <tui-list-cell class="line-cell" :hover="false" v-if="displaySendCost">
+      <view class="tui-title">跑腿费</view>
+      <view>
+        <text :class="isFreeDelivery ? 'delivery-price-free' : 'delivery-price-normal'">¥{{ localForm.sendCost }}</text>
+        <text v-if="isFreeDelivery" class="free-delivery-tag">¥0【免跑腿费】</text>
+      </view>
+    </tui-list-cell>
+
+    <tui-list-cell class="line-cell" :hover="false" v-if="showShMethodExtraFee">
+      <view class="tui-title">{{ shMethodExtraFeeLabel }}</view>
+      <text style="color: #3385FF">¥{{ shMethodExtraFeeAmount }}</text>
+    </tui-list-cell>
+
+    <tui-list-cell class="line-cell" :hover="false" :arrow="false">
+      <view class="tui-title">总金额</view>
+      <text style="color: #3385FF">¥ {{ panelTotalPrice }}</text>
+    </tui-list-cell>
+
+    <tui-list-cell class="line-cell" :hover="false" :arrow="true">
+      <view class="tui-title">配送日期</view>
+      <picker mode="date" :value="sendTimeWant" @change="bindDateChange" style="width:400upx;">
+        <view class="uni-input" v-if="sendTimeWant==''" style="color:#CCCCCC;">请选日期,默认当天</view>
+        <view class="uni-input">{{ sendTimeWant }}</view>
+      </picker>
+    </tui-list-cell>
+
+    <tui-list-cell class="line-cell" :hover="false" :arrow="false">
+      <textarea style="height: 100upx" v-model="remark" placeholder="这里写备注"/>
+    </tui-list-cell>
+
+    <uni-popup ref="deliveryPolicyPopup" type="center" background-color="#fff">
+      <view class="policy-popup-content">
+        <view class="policy-title">免费配送条件</view>
+        <scroll-view scroll-y="true" class="policy-scroll">
+          <view class="policy-list">
+            <block v-for="(item, index) in deliveryPolicyList" :key="index">
+              <view class="policy-item">{{ item }}</view>
+            </block>
+          </view>
+        </scroll-view>
+        <view class="policy-btn" @click="closeDeliveryPolicy">知道了</view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
+
+<script>
+import { mapGetters } from "vuex";
+import TuiListCell from "@/components/plugin/list-cell";
+import { getWlList } from "@/api/ghs";
+import { getShMethod } from "@/api/purchase";
+import { currentShop } from "@/api/shop";
+import { allDeliveryQuotes } from "@/api/express/delivery";
+
+export default {
+  name: "AffirmShMethodPanel",
+  components: {
+    TuiListCell
+  },
+  props: {
+    ghsInfo: { type: Object, default: () => ({}) },
+    ghsId: { type: [Number, String], default: 0 },
+    commodityPrice: { type: [Number, String], default: 0 },
+    commodityBigCount: { type: [Number, String], default: 0 },
+    commodityWeight: { type: [Number, String], default: 0 },
+    selectList: { type: Array, default: () => [] }
+  },
+  data() {
+    return {
+      methodList: [],
+      /** 当前选中的配送方式下标,默认加载后选中第一项 */
+      selectedMethodIndex: -1,
+      wlList: [],
+      wlSelData: { id: 0, name: "" },
+      shopInfo: {
+        lat: "",
+        long: "",
+        fullAddress: "",
+        showAddress: "",
+        address: ""
+      },
+      validAddress: false,
+      localForm: {
+        sendType: 0,
+        wlName: "",
+        wlId: 0,
+        sendCost: 0,
+        packCost: 0
+      },
+      deliveryQuotes: [],
+      selectedDeliveryIndex: -1,
+      selectedDeliveryData: null,
+      showDistance: 0,
+      sendTimeWant: "",
+      remark: "",
+      deliveryPolicyList: []
+    };
+  },
+  computed: {
+    ...mapGetters(["getLoginInfo"]),
+    visibleMethodList() {
+      return (this.methodList || [])
+        .filter(item => Number(item.status) === 1)
+        .sort((a, b) => Number(a.sort) - Number(b.sort))
+    },
+    currentShMethod() {
+      const list = this.visibleMethodList
+      if (this.selectedMethodIndex >= 0 && this.selectedMethodIndex < list.length) {
+        return list[this.selectedMethodIndex]
+      }
+      return null
+    },
+    displayAddress() {
+      return Number(this.localForm.sendType) !== 1
+    },
+    displaySendCost() {
+      if (Number(this.localForm.sendType) === 2) {
+        return ![0, 2].includes(Number(this.ghsInfo.openIntraCity))
+      }
+      return false
+    },
+    isFreeDelivery() {
+      if (!this.displaySendCost || !this.validAddress) {
+        return false
+      }
+      const distanceInMeters = Number(this.showDistance) || 0
+      const freeKmInMeters = (Number(this.ghsInfo.hcFreeKm) || 0) * 1000
+      if (distanceInMeters < freeKmInMeters) {
+        return true
+      }
+      if (distanceInMeters > freeKmInMeters) {
+        let mapData = this.ghsInfo.hcMap
+        if (mapData && typeof mapData === "string") {
+          try {
+            mapData = JSON.parse(mapData)
+          } catch (e) {
+            mapData = []
+          }
+        }
+        if (mapData && Array.isArray(mapData) && mapData.length > 0) {
+          const flowerCount = Number(this.commodityBigCount) || 0
+          const flowerPrice = Number(this.commodityPrice) || 0
+          for (const rule of mapData) {
+            const ruleNum = Number(rule.num) || 0
+            const rulePrice = Number(rule.price) || 0
+            const ruleDistance = (Number(rule.distance) || 0) * 1000
+            if (flowerCount >= ruleNum && flowerPrice >= rulePrice && distanceInMeters <= ruleDistance) {
+              return true
+            }
+          }
+        }
+      }
+      return false
+    },
+    showDeliveryPolicy() {
+      if (!this.displaySendCost) {
+        return false
+      }
+      let hasPolicy = false
+      if (this.ghsInfo.hcFreeKm && Number(this.ghsInfo.hcFreeKm) > 0) {
+        hasPolicy = true
+      }
+      let mapData = this.ghsInfo.hcMap
+      if (mapData && typeof mapData === "string") {
+        try {
+          mapData = JSON.parse(mapData)
+        } catch (e) {}
+      }
+      if (mapData && Array.isArray(mapData) && mapData.length > 0) {
+        hasPolicy = true
+      }
+      return hasPolicy
+    },
+    showShMethodExtraFee() {
+      const method = this.currentShMethod
+      if (!method || !this.checkShMethodBelowMinimum(method)) {
+        return false
+      }
+      const unMeet = Number(method.unMeet) || 0
+      const fee = Number(method.unMeetFee) || 0
+      return (unMeet === 0 || unMeet === 2) && fee > 0
+    },
+    shMethodExtraFeeLabel() {
+      const method = this.currentShMethod
+      if (!method) {
+        return "包装费"
+      }
+      return Number(method.unMeet) === 2 ? "包装费" : "运费"
+    },
+    shMethodExtraFeeAmount() {
+      if (!this.showShMethodExtraFee) {
+        return 0
+      }
+      const method = this.currentShMethod
+      return parseFloat(Number(method && method.unMeetFee) || 0)
+    },
+    shMethodUnMeetWarningText() {
+      const method = this.currentShMethod
+      if (!method) {
+        return ""
+      }
+      const tips = []
+      const minAmount = Number(method.minAmount) || 0
+      const minNum = Number(method.minNum) || 0
+      if (minAmount > 0) {
+        tips.push("不满" + minAmount + "元")
+      }
+      if (minNum > 0) {
+        tips.push("不满" + minNum + "扎")
+      }
+      const conditionText = tips.length > 0 ? tips.join("且") : "未达最低消费"
+      return "当天订单" + conditionText + ",加" + this.shMethodExtraFeeLabel + this.shMethodExtraFeeAmount + "元"
+    },
+    currentMethodExplains() {
+      const method = this.currentShMethod
+      if (!method || !Array.isArray(method.explains)) {
+        return []
+      }
+      return method.explains
+        .filter(exp => exp && exp.explain)
+        .sort((a, b) => Number(a.sort) - Number(b.sort))
+    },
+    runnerFeeAmount() {
+      if (!this.displaySendCost || this.isFreeDelivery) {
+        return 0
+      }
+      return Number(this.localForm.sendCost) || 0
+    },
+    panelTotalPrice() {
+      let price = Number(this.commodityPrice) || 0
+      price += this.runnerFeeAmount
+      price += this.shMethodExtraFeeAmount
+      return parseFloat(price.toFixed(2))
+    }
+  },
+  watch: {
+    ghsId: {
+      immediate: true,
+      handler(val) {
+        if (val) {
+          this.loadPanelData(val)
+        }
+      }
+    },
+    commodityPrice() {
+      this.emitPanelChange()
+    },
+    commodityBigCount() {
+      this.emitPanelChange()
+    },
+    remark() {
+      this.emitPanelChange()
+    },
+    sendTimeWant() {
+      this.emitPanelChange()
+    },
+    "localForm.wlName"() {
+      this.emitPanelChange()
+    }
+  },
+  methods: {
+    /** 面板初始化:配送配置、物流列表、门店地址 */
+    loadPanelData(ghsId) {
+      getShMethod({ ghsId: ghsId }).then(res => {
+        if (res.code == 1) {
+          this.methodList = res.data && res.data.method ? res.data.method : []
+          if (this.visibleMethodList.length > 0) {
+            this.selectMethodByIndex(0)
+          } else {
+            this.selectedMethodIndex = -1
+            this.emitPanelChange()
+          }
+        }
+      })
+      getWlList({ id: ghsId }).then(res => {
+        if (res.code == 1) {
+          this.wlList = res.data.list ? res.data.list : []
+        }
+      })
+      this.loadShopInfo()
+    },
+    /** 页面 onShow 时刷新地址,并按需重拉跑腿报价 */
+    onPageShow() {
+      this.loadShopInfo(() => {
+        if (Number(this.localForm.sendType) === 2 && this.displaySendCost && this.validAddress) {
+          this.getDeliveryQuotes()
+        }
+      })
+    },
+    loadShopInfo(callback) {
+      currentShop().then(res => {
+        if (res.code == 1) {
+          this.shopInfo = res.data
+          this.validAddress = !this.$util.isEmpty(this.shopInfo.lat) &&
+            !this.$util.isEmpty(this.shopInfo.long) &&
+            !this.$util.isEmpty(this.shopInfo.address)
+        }
+        if (typeof callback === "function") {
+          callback()
+        }
+        this.emitPanelChange()
+      })
+    },
+    checkShMethodBelowMinimum(method) {
+      if (!method) {
+        return false
+      }
+      const minAmount = Number(method.minAmount) || 0
+      const minNum = Number(method.minNum) || 0
+      if (minAmount <= 0 && minNum <= 0) {
+        return false
+      }
+      const allPrice = Number(this.commodityPrice) || 0
+      const bigLength = Number(this.commodityBigCount) || 0
+      return (minAmount > 0 && allPrice < minAmount) || (minNum > 0 && bigLength < minNum)
+    },
+    /** 按列表下标切换配送方式,并同步 sendType 供下单与条件展示 */
+    selectMethodByIndex(index) {
+      const list = this.visibleMethodList
+      if (!list[index]) {
+        return
+      }
+      this.selectedMethodIndex = index
+      const sendType = Number(list[index].style)
+      this.localForm.sendType = sendType
+      if (sendType === 2) {
+        if (this.displaySendCost && Number(this.ghsInfo.openIntraCity) === 1 && this.validAddress) {
+          this.getDeliveryQuotes()
+        }
+      } else {
+        this.deliveryQuotes = []
+        this.selectedDeliveryIndex = -1
+        this.selectedDeliveryData = null
+        this.localForm.sendCost = 0
+        this.showDistance = 0
+      }
+      this.syncPackCostByMethod()
+      this.emitPanelChange()
+    },
+    syncPackCostByMethod() {
+      const method = this.currentShMethod
+      if (method && this.checkShMethodBelowMinimum(method)) {
+        const unMeet = Number(method.unMeet) || 0
+        const fee = parseFloat(Number(method.unMeetFee) || 0)
+        if ((unMeet === 0 || unMeet === 2) && fee > 0) {
+          this.localForm.packCost = fee
+          return
+        }
+      }
+      this.localForm.packCost = 0
+    },
+    changeWlFn(e) {
+      this.wlSelData = this.wlList[e.detail.value]
+      this.localForm.wlId = this.wlSelData.id
+      this.localForm.wlName = this.wlSelData.name
+      this.emitPanelChange()
+    },
+    getDeliveryQuotes() {
+      this.deliveryQuotes = []
+      this.selectedDeliveryIndex = -1
+      this.selectedDeliveryData = null
+      allDeliveryQuotes({
+        ghsId: this.ghsId,
+        totalPrice: this.commodityPrice,
+        weight: this.commodityWeight,
+        selectList: this.selectList,
+        weight: 1,
+        remark: ""
+      }).then(res => {
+        if (res.code === 1 && res.data && res.data.deliveryList) {
+          this.deliveryQuotes = res.data.deliveryList
+            .filter(item => item.isAble !== false)
+            .map(item => {
+              const price = item.price ? (item.price / 100).toFixed(1) : "0.0"
+              let displayName = item.name
+              let bracketContent = ""
+              if (displayName && displayName.includes("(")) {
+                bracketContent = displayName.split("(")[1].replace(")", "")
+                displayName = displayName.split("(")[0].trim()
+              }
+              if (item.en_name == "didi" && bracketContent != "") {
+                item.type = bracketContent
+              }
+              return {
+                ...item,
+                displayName: displayName,
+                priceText: price,
+                priceNumber: parseFloat(price),
+                bracketContent: bracketContent
+              }
+            })
+          const firstAvailableIndex = this.deliveryQuotes.findIndex(item => item.isAble)
+          if (firstAvailableIndex !== -1) {
+            this.selectDelivery(this.deliveryQuotes[firstAvailableIndex], firstAvailableIndex)
+          } else {
+            this.emitPanelChange()
+          }
+        } else {
+          uni.showToast({ title: "暂无跑腿可用", icon: "none" })
+        }
+      }).catch(() => {
+        uni.showToast({ title: "没有获取到跑腿费用", icon: "none" })
+      })
+    },
+    selectDelivery(item, index) {
+      this.selectedDeliveryIndex = index
+      this.selectedDeliveryData = item
+      this.localForm.sendCost = item.priceNumber
+      this.showDistance = item.distance || 0
+      this.emitPanelChange()
+    },
+    modifyAddress() {
+      const shopId = this.getLoginInfo.admin && this.getLoginInfo.admin.currentShopId
+      this.$util.pageTo({ url: "/admin/shop/add?id=" + shopId })
+    },
+    showDeliveryPolicyModal() {
+      this.deliveryPolicyList = []
+      if (this.ghsInfo.hcFreeKm && Number(this.ghsInfo.hcFreeKm) > 0) {
+        this.deliveryPolicyList.push(Number(this.ghsInfo.hcFreeKm) + "公里内免费配送")
+      }
+      let mapData = this.ghsInfo.hcMap
+      if (mapData && typeof mapData === "string") {
+        try {
+          mapData = JSON.parse(mapData)
+        } catch (e) {
+          mapData = []
+        }
+      }
+      if (mapData && Array.isArray(mapData) && mapData.length > 0) {
+        mapData.forEach(rule => {
+          const num = Number(rule.num) || 0
+          const price = Number(rule.price) || 0
+          const distance = Number(rule.distance) || 0
+          this.deliveryPolicyList.push("满" + num + "扎" + price + "元," + distance + "公里内免费配送")
+        })
+      }
+      if (this.deliveryPolicyList.length === 0) {
+        uni.showToast({ title: "暂无详细配送政策", icon: "none" })
+        return
+      }
+      this.$refs.deliveryPolicyPopup.open()
+    },
+    closeDeliveryPolicy() {
+      this.$refs.deliveryPolicyPopup.close()
+    },
+    bindDateChange(e) {
+      this.sendTimeWant = e.detail.value
+    },
+    /** 提交前校验,失败时提示并返回 false */
+    validateBeforeSubmit() {
+      if (Number(this.localForm.sendType) === 3 && this.$util.isEmpty(this.localForm.wlName)) {
+        this.$msg("请填选物流")
+        return false
+      }
+      const method = this.currentShMethod
+      if (method && Number(method.unMeet) === 1 && this.checkShMethodBelowMinimum(method)) {
+        this.$msg("未达到最低消费,不能下单")
+        return false
+      }
+      return true
+    },
+    /** 向父页面同步面板状态 */
+    emitPanelChange() {
+      this.syncPackCostByMethod()
+      this.$emit("change", {
+        sendType: this.localForm.sendType,
+        wlName: this.localForm.wlName,
+        wlId: this.localForm.wlId,
+        sendCost: this.localForm.sendCost,
+        packCost: this.localForm.packCost,
+        sendTimeWant: this.sendTimeWant,
+        remark: this.remark,
+        showDistance: this.showDistance,
+        selectedDeliveryData: this.selectedDeliveryData,
+        totalPrice: this.panelTotalPrice,
+        runnerFeeAmount: this.runnerFeeAmount,
+        extraFeeAmount: this.shMethodExtraFeeAmount
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.delivery-quotes-container {
+  width: 100%;
+  margin-top: 20upx;
+}
+
+.delivery-quotes-grid {
+  display: flex;
+  flex-wrap: wrap;
+  padding: 0;
+  width: 100%;
+}
+
+.delivery-quote-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  flex: 0 0 calc(33% - 7upx);
+  width: calc(33% - 7upx);
+  min-height: 140upx;
+  padding: 16upx 10upx;
+  margin-right: 10upx;
+  margin-bottom: 10upx;
+  background: #FFFFFF;
+  border: 2upx solid #DDDDDD;
+  border-radius: 8upx;
+  box-sizing: border-box;
+
+  &:nth-child(3n) {
+    margin-right: 0;
+  }
+
+  &.fixed-width {
+    flex: 0 0 220rpx;
+    width: 220rpx;
+  }
+
+  .platform-name {
+    font-size: 26upx;
+    color: #333;
+    font-weight: bold;
+    margin-bottom: 8upx;
+    text-align: center;
+    line-height: 1.3;
+    max-width: 100%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    line-clamp: 2;
+    -webkit-box-orient: vertical;
+    word-break: break-all;
+  }
+
+  .platform-price {
+    font-size: 30upx;
+    color: #333;
+    font-weight: bold;
+    margin-bottom: 4upx;
+  }
+
+  .platform-type {
+    font-size: 20upx;
+    color: #868686;
+    margin-top: 4upx;
+    text-align: center;
+    line-height: 1.3;
+    max-width: 100%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    line-clamp: 2;
+    -webkit-box-orient: vertical;
+    word-break: break-all;
+  }
+
+  &.active {
+    background: #09C567;
+    border-color: #09C567;
+
+    .platform-name,
+    .platform-price {
+      color: #FFFFFF;
+    }
+
+    .platform-type {
+      color: rgba(255, 255, 255, 0.85);
+    }
+  }
+
+  &.disabled {
+    opacity: 0.5;
+    background: #F5F5F5;
+
+    .platform-name,
+    .platform-price,
+    .platform-type {
+      color: #CCCCCC;
+    }
+  }
+}
+
+.delivery-price-normal {
+  color: #3385FF;
+}
+
+.delivery-price-free {
+  color: #999;
+  text-decoration: line-through;
+}
+
+.free-delivery-tag {
+  color: #3385FF;
+  margin-left: 5upx;
+}
+
+.delivery-policy-link {
+  color: rgb(201, 52, 52);
+  font-size: 30upx;
+  margin-left: 30upx;
+  font-weight: bold;
+  text-decoration: underline;
+}
+
+.policy-popup-content {
+  width: 600upx;
+  background-color: #fff;
+  border-radius: 20upx;
+  padding: 30upx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.policy-title {
+  font-size: 34upx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 30upx;
+}
+.policy-scroll {
+  width: 100%;
+  max-height: 50vh;
+  margin-bottom: 30upx;
+}
+.policy-list {
+  width: 100%;
+}
+.policy-item {
+  font-size: 30upx;
+  color: #666;
+  margin-bottom: 20upx;
+  text-align: left;
+  line-height: 1.5;
+  display: list-item;
+  list-style-type: disc;
+  margin-left: 40upx;
+}
+.policy-btn {
+  width: 50%;
+  height: 80upx;
+  line-height: 80upx;
+  text-align: center;
+  background-color: #3385ff;
+  color: #fff;
+  font-size: 32upx;
+  border-radius: 40upx;
+}
+</style>
+
+<style lang="scss">
+/* 36523 配送方式按钮:二字 60upx,三到四字 32upx(与 affirmGhs.vue 一致) */
+.sh-method-btn {
+  width: 115upx;
+  height: 100upx;
+  margin-right: 15upx;
+}
+.sh-method-btn:last-child {
+  margin-right: 0;
+}
+.sh-method-btn--short {
+  line-height: 60upx;
+}
+.sh-method-btn--long {
+  line-height: 32upx;
+}
+</style>

+ 4 - 0
hdApp/src/api/purchase/index.js

@@ -1,5 +1,9 @@
 import https from "@/plugins/luch-request_0.0.7/request";
 
+export const getShMethod = data => {
+	return https.get("/sh-method/get-all-method", data);
+};
+
 export const cancelOrder = data => {
 	return https.get("/purchase/cancel", data);
 };