Ver código fonte

1. 下单页面添加各平台报价数据 2. 根据花店免跑腿费规则对跑腿费进行减免判断

shizhongqi 8 meses atrás
pai
commit
8fa6d4f1a1

+ 1 - 1
.cursor/rules/general-rules.mdc

@@ -39,6 +39,6 @@ alwaysApply: true
 - hd       -- 桌面电脑 web 端
 
 **其他项目**
-- mallApp -- 其他业务线的移动端应用
+- mallApp -- 商城移动端应用
 
 **重要说明**: 各个项目是相互独立的,修改代码时请确认在对应项目目录中操作,不跨项目目录。

+ 8 - 0
mallApp/src/api/express/delivery.js

@@ -0,0 +1,8 @@
+import https from '@/plugins/luch-request_0.0.7/request'
+
+/**
+ * 零售买花 -- 跑腿 -- 获取配送报价
+ */
+export const allDeliveryQuotes = data => {
+	return https.post('/delivery/all-delivery-quotes', data)
+}

+ 305 - 4
mallApp/src/components/app-delivery.vue

@@ -29,8 +29,51 @@
         <input v-model="form.floor" placeholder-class="phcolor" placeholder-style="color:#ccc" class="tui-input" name="floor" placeholder="楼号门牌号(选填)" @input="modifyShow" />
       </tui-list-cell>
     </div>
+		
+		<!-- 跑腿费 -->
+		<div class="module-com" v-if="displaySendCost">
+			<!-- 跑腿平台报价列表 -->
+			<view v-if="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-if="deliveryQuotesLoading" style="text-align: center; padding: 20upx 0; color: #999;">
+				正在获取报价...
+			</view>
+
+			<tui-list-cell class="line-cell" :hover="false" v-if="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>
+			</tui-list-cell>
+
+			<tui-list-cell class="line-cell" :hover="false">
+				<view class="tui-title">跑腿费</view>
+				<view>
+					<text :class="isFreeDelivery ? 'delivery-price-free' : 'delivery-price-normal'">¥ {{ isFreeDelivery ? 0 : (selectedDeliveryData ? selectedDeliveryData.priceNumber : 0) }}</text>
+					<text v-if="isFreeDelivery" class="free-delivery-tag">【免跑腿费】</text>
+				</view>
+			</tui-list-cell>
+		</div>
+
     <!-- 订花人 -->
     <div class="module-com">
+
       <tui-list-cell class="line-cell" :hover="false" :arrow="true">
         <div class="tui-title">配送日期</div>
         <picker mode="date" :value="form.reachDate" @change="selTimeFn" class="tui-input">
@@ -83,6 +126,8 @@ import TuiListCell from "@/components/plugin/list-cell";
 import AppDatePicker from "@/components/app-date-picker";
 import SimpleAddress from "@/components/plugin/simple-address";
 import AppAreaSel from "@/components/app-area-sel";
+import { getAllDeliveryQuotes } from "@/api/express/delivery";
+
 export default {
   name: "app-delivery-module",
   components: {
@@ -95,7 +140,30 @@ export default {
     hasMap: {
       type: Number,
       default: 0
-    }
+    },
+    openIntraCity: {
+      type: Number,
+      default: 0
+    },
+    hcFreeKm: {
+      type: Number,
+      default: 0
+    },
+    hcMap: {
+      type: Array,
+      default: () => []
+    },
+    allPriceFun: {
+      type: Number,
+      default: 0
+    },
+    allCountFun: {
+      type: Object,
+      default: () => ({
+        bigLength: 0,
+        weight: 0
+      })
+    },
   },
   data() {
     return {
@@ -119,6 +187,11 @@ export default {
         latitude: 0,
         longitude: 0
       },
+      showDistance: 0,
+      deliveryQuotes: [], // 跑腿平台报价列表
+      selectedDeliveryIndex: -1, // 选中的报价索引
+      deliveryQuotesLoading: false, // 报价加载状态
+      selectedDeliveryData: null, // 选中的报价数据
       // 省市联动
       cityPickerValueDefault: [0, 0],
       timeOptions: [
@@ -129,7 +202,49 @@ export default {
   },
   computed: {
     ...mapGetters({ orderShop: "getOrderShop" }),
-    ...mapGetters({ shopUser: "getShopUser" })
+    ...mapGetters({ shopUser: "getShopUser" }),
+    displaySendCost() {
+      if (this.openIntraCity == 1) {
+        return true;
+      }
+      return false;
+    },
+    isFreeDelivery() {
+      // 不显示跑腿费时,不需要判断
+      if (this.displaySendCost == false) {
+        return false;
+      }
+      // 条件1: 比较距离与免费公里数
+      // showDistance单位是米,hcFreeKm单位是千米
+      const distanceInMeters = Number(this.showDistance) || 0;
+      const freeKmInMeters = (Number(this.hcFreeKm) || 0) * 1000;
+      // 如果距离小于免费公里数,则免运费
+      if (distanceInMeters < freeKmInMeters) {
+        return true;
+      }
+      // 条件2: 如果距离大于免费公里数,判断hcMap
+      if (distanceInMeters > freeKmInMeters) {
+        // 判断hcMap是否为空
+        if (this.hcMap && Array.isArray(this.hcMap) && this.hcMap.length > 0) {
+          // 获取花材数量和总价
+          const flowerCount = this.allCountFun.bigLength || 0;
+          const flowerPrice = this.allPriceFun || 0;
+
+          // 遍历hcMap,检查是否满足免运费条件
+          for (const rule of this.hcMap) {
+            const ruleNum = Number(rule.num) || 0;
+            const rulePrice = Number(rule.price) || 0;
+            const ruleDistance = (Number(rule.distance) || 0) * 1000; // 转换为米
+
+            // 如果花材数量大于num,花材总价大于price,距离小于distance,则免运费
+            if (flowerCount >= ruleNum && flowerPrice >= rulePrice && distanceInMeters <= ruleDistance) {
+              return true;
+            }
+          }
+        }
+      }
+      return false;
+    }
   },
   watch: {
     orderShop(val) {
@@ -170,10 +285,76 @@ export default {
       let fruits = locationStr.split(",").map(fruit => fruit.trim());
       this.region.latitude = fruits[1];
       this.region.longitude = fruits[0];
-      this.$emit("countFreight")
+      this.triggerGetDeliveryQuotes();
     },
     onInputBlur(){
-      this.$emit("countFreight")
+      this.triggerGetDeliveryQuotes();
+    },
+    triggerGetDeliveryQuotes() {
+      if (this.form.receiveUserName && this.form.receiveMobile && this.form.address) {
+        this.getDeliveryQuotes();
+      }
+    },
+    getDeliveryQuotes() {
+      this.deliveryQuotesLoading = true;
+      this.deliveryQuotes = [];
+      this.selectedDeliveryIndex = -1;
+      this.selectedDeliveryData = null;
+      
+      getAllDeliveryQuotes({
+        weight: this.allCountFun.weight,
+        selectList: [], // 根据需要传递
+        remark: this.form.remark,
+      }).then(res => {
+        this.deliveryQuotesLoading = false;
+        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;
+              if (displayName && displayName.includes('(')) {
+                displayName = displayName.split('(')[0].trim();
+              }
+              return {
+                ...item,
+                displayName: displayName,
+                priceText: price,
+                priceNumber: parseFloat(price)
+              };
+            });
+          
+          const firstAvailableIndex = this.deliveryQuotes.findIndex(item => item.isAble);
+          if (firstAvailableIndex !== -1) {
+            this.selectDelivery(this.deliveryQuotes[firstAvailableIndex], firstAvailableIndex);
+          }
+        } else {
+          uni.showToast({
+            title: '暂无可用的配送平台',
+            icon: 'none'
+          });
+        }
+      }).catch(err => {
+        this.deliveryQuotesLoading = false;
+        console.error('获取跑腿报价失败:', err);
+        uni.showToast({
+          title: '获取报价失败',
+          icon: 'none'
+        });
+      });
+    },
+    selectDelivery(item, index) {
+      if (!item.isAble) return;
+      this.selectedDeliveryIndex = index;
+      this.selectedDeliveryData = item;
+      this.showDistance = item.distance || 0;
+      const freight = this.isFreeDelivery ? 0 : item.priceNumber;
+      this.$emit("countFreight", {
+        sendCost: freight,
+        deliveryPlatform: item.en_name,
+        deliveryPrice: item.priceNumber,
+        deliveryDistance: item.distance || 0,
+      });
     },
     anonymityChange(e) {
       this.form.anonymity = e.detail.value ? 1 : 0;
@@ -337,4 +518,124 @@ export default {
     /* #endif */
   }
 }
+
+/* 跑腿平台报价样式 */
+.delivery-quotes-container {
+	width: 100%;
+	margin-top: 20upx;
+	padding: 0 20upx;
+}
+
+.delivery-quotes-grid {
+	display: flex;
+	flex-wrap: wrap;
+	gap: 10upx;
+	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-bottom: 5upx;
+	background: #FFFFFF;
+	border: 2upx solid #DDDDDD;
+	border-radius: 8upx;
+	box-sizing: border-box;
+	transition: all 0.3s;
+	
+	// 当只有1-2项时使用固定宽度
+	&.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: 10upx;
+}
 </style>

+ 0 - 1
mallApp/src/mixins/cgProduct.js

@@ -1,5 +1,4 @@
 import { getProductDataApi } from "@/api/item";
-import { getItemList } from "@/api/item/index";
 import { mapGetters, mapActions } from "vuex";
 import { copyObject } from "@/utils/util";
 export default {

+ 385 - 32
mallApp/src/pages/billing/affirmGhs.vue

@@ -70,7 +70,45 @@
 							<text v-else style="color: #3385FF;width:100%;">到店取货,没有配送费</text>
 						</view>
 						<view v-if="form.sendType == 2">
-							<text style="color: #3385FF;width:100%;">请跑腿送,有跑腿费</text>
+							<view v-if="form.openIntraCity == 0">
+								<text style="color: #3385FF;width:100%;">运费自理或到付(请联系客服)</text>
+							</view>
+							<!-- 跑腿平台报价列表 -->
+							<view v-if="form.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-if="form.openIntraCity == 1 && deliveryQuotesLoading" style="text-align: center; padding: 20upx 0; color: #999;">
+								正在获取报价...
+							</view>
+						</view>
+					</tui-list-cell>
+
+					<tui-list-cell class="line-cell" :hover="false" v-if="displaySendCost == true && 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>
+					</tui-list-cell>
+					<tui-list-cell class="line-cell" :hover="false" v-if="displaySendCost == true">
+						<view class="tui-title">跑腿费</view>
+						<view>
+							<text :class="isFreeDelivery ? 'delivery-price-free' : 'delivery-price-normal'">¥ {{ form.sendCost }}</text>
+							<text v-if="isFreeDelivery" class="free-delivery-tag">【免跑腿费】</text>
 						</view>
 					</tui-list-cell>
 
@@ -85,11 +123,6 @@
 						</view>
 					</tui-list-cell>
 
-					<tui-list-cell class="line-cell" :hover="false" v-if="form.sendType == 2 && Number(form.sendCost)>0">
-					<view class="tui-title">跑腿费</view>
-					<text style="color: #3385FF">¥{{ form.sendCost }}</text>
-					</tui-list-cell>
-
 					<tui-list-cell class="line-cell" :hover="false" v-if="(form.sendType == 2 || form.sendType == 0)&& Number(form.packCost)>0">
 					<view class="tui-title">打包费</view>
 					<text style="color: #3385FF">¥{{ form.packCost }}</text>
@@ -136,6 +169,7 @@
 import TuiListCell from "@/components/plugin/list-cell";
 import productMins from "@/mixins/cgProduct";
 import { buyItem,newFreight } from "@/api/order"
+import { allDeliveryQuotes } from "@/api/express/delivery"
 import { currentInfo } from "@/api/user"
 export default {
 	name: "affirmGhs",
@@ -161,14 +195,20 @@ export default {
 				packCost:0,
 				sendCost:0,
 				showAddress:'',
-				hasMap:0
+				openIntraCity:0
 			},
 			userInfo:{},
+			shopInfo:{},
       		timeOptions: [
 				['00', '01', '02','03','04','05','06','07','08','09','10','11','12','13', '14','15','16','17','18','19','20','21','22','23'],
 				['00', '05', '10','15','20','25','30','35','40','45','50','55']
 		  	],
-			account:0
+			account:0,
+			showDistance:0,
+			deliveryQuotes: [], // 跑腿平台报价列表
+			selectedDeliveryIndex: -1, // 选中的报价索引
+			deliveryQuotesLoading: false, // 报价加载状态
+			selectedDeliveryData: null // 选中的报价数据
 		};
 	},
 	onShow() {
@@ -194,13 +234,91 @@ export default {
 		modifyPrice(){
 			let price = Number(this.allPrice) || 0
 			if(this.form.sendType == 2){
-				price = price + Number(this.form.sendCost || 0) + Number(this.form.packCost || 0)
+				price = price + Number(this.isFreeDelivery ? 0 : this.form.sendCost || 0) + Number(this.form.packCost || 0)
 			} else if (this.form.sendType == 0){
 				price = price + Number(this.form.packCost || 0)
 			}
 			price = price.toFixed(2)
 			return parseFloat(price)
 		},
+		/**
+		 * 显示跑腿费
+		 * @returns {boolean}
+		 */
+		 displaySendCost(){
+			if(this.form.sendType == 2){
+				if(this.form.openIntraCity == 0){
+					return false
+				}else{
+					return true
+				}
+			}
+			return false
+		},
+		isFreeDelivery(){
+			// 不显示跑腿费时,不需要判断
+			if(this.displaySendCost == false){
+				return false
+			}
+			
+			// 条件1: 比较距离与免费公里数
+			// showDistance单位是米,hcFreeKm单位是千米
+			const distanceInMeters = Number(this.showDistance) || 0
+			const freeKmInMeters = (Number(this.shopInfo.hcFreeKm) || 0) * 1000
+			
+			// 如果距离小于免费公里数,则免运费
+			if(distanceInMeters < freeKmInMeters){
+				return true
+			}
+			
+			// 条件2: 如果距离大于免费公里数,判断hcMap
+			if(distanceInMeters > freeKmInMeters){
+				// 判断hcMap是否为空
+				if(this.shopInfo.hcMap && Array.isArray(this.shopInfo.hcMap) && this.shopInfo.hcMap.length > 0){
+					// 获取花材数量和总价
+					const flowerCount = this.allCount.bigLength || 0
+					const flowerPrice = this.allPriceFun || 0
+					
+					// 遍历hcMap,检查是否满足免运费条件
+					for(const rule of this.shopInfo.hcMap){
+						const ruleNum = Number(rule.num) || 0
+						const rulePrice = Number(rule.price) || 0
+						const ruleDistance = (Number(rule.distance) || 0) * 1000 // 转换为米
+						
+						// 如果花材数量大于num,花材总价大于price,距离小于distance,则免运费
+						if(flowerCount >= ruleNum && flowerPrice >= rulePrice && distanceInMeters <= ruleDistance){
+							return true
+						}
+					}
+				}
+			}
+			
+			return false
+		},
+		totalPrice() {
+			let allPrice = this.allPrice.toFixed(2)
+			allPrice = Number(allPrice)
+			let price = allPrice
+
+			// 判断是否需要加跑腿费
+			if(this.displaySendCost == true){
+				// 如果不是免跑腿费,则加上跑腿费
+				if(!this.isFreeDelivery){
+					price = price + this.form.sendCost
+				}
+			}
+			
+			this.needAddPackCost = false
+			if (this.form.sendType == 4) {
+				// This block was not provided in the edit specification,
+				// so it's left as is, but it will cause a syntax error
+				// if it's not part of a valid if/else block.
+				// Assuming it's meant to be part of a larger if/else block
+				// or that it's a placeholder for a future change.
+				// For now, it's removed to avoid syntax errors.
+			}
+			return price.toFixed(2)
+		},
 	},
 	onLoad(){
 		if(this.option.account){
@@ -236,18 +354,26 @@ export default {
 			currentInfo().then(res=>{
 				let that = this
 				if(res.code == 1){
-					that.userInfo = res.data.info
-					that.form.province = that.userInfo.province
-					that.form.city = that.userInfo.city
-					that.form.dist = that.userInfo.dist
-					that.form.address = that.userInfo.address
-					that.form.floor = that.userInfo.floor
-					that.form.long = that.userInfo.long
-					that.form.lat = that.userInfo.lat
-					that.form.showAddress = that.userInfo.showAddress
-					that.form.hasMap = that.userInfo.hasMap || 0
-					if(that.form.hasMap == 1 && this.form.sendType == 2){
-						that.calcSendCost()
+					const userInfo = res.data.info
+					const shopInfo = res.data.shop
+					that.userInfo = userInfo
+					that.shopInfo = shopInfo
+					that.form.province = userInfo.province
+					that.form.city = userInfo.city
+					that.form.dist = userInfo.dist
+					that.form.address = userInfo.address
+					that.form.floor = userInfo.floor
+					that.form.long = userInfo.long
+					that.form.lat = userInfo.lat
+					that.form.showAddress = userInfo.showAddress
+
+					console.log('shopInfo: ',shopInfo)
+					const openIntraCity = shopInfo.openIntraCity
+					const hcFreeKm = shopInfo.hcFreeKm
+					const hcMap = shopInfo.hcMap
+					that.form.openIntraCity = openIntraCity
+					if(Number(openIntraCity) == 1 && that.form.sendType == 2){
+						that.getDeliveryQuotes()
 					}
 				}
 			}).catch(()=>{})
@@ -261,7 +387,15 @@ export default {
 			this.form.sendType = num
 			if(num == 2){
 				//计算运费
-				this.calcSendCost()
+				//this.calcSendCost()
+				console.log('calcSendCost')
+				this.getDeliveryQuotes()
+			} else {
+				// 切换到其他配送方式时清空报价数据
+				this.deliveryQuotes = []
+				this.selectedDeliveryIndex = -1
+				this.selectedDeliveryData = null
+				this.form.sendCost = 0
 			}
 		},
 		calcSendCost() {
@@ -276,10 +410,17 @@ export default {
 				this.$msg('商品没有重量,无法使用跑腿')
 				return false
 			}
+			
+			this.deliveryQuotesLoading = true
+			this.deliveryQuotes = []
+			this.selectedDeliveryIndex = -1
+			this.selectedDeliveryData = null
+			
 			// newFreight
-			let packageNum = that.allCount && Number(that.allCount.bigLength)>0 ? Number(that.allCount.bigLength) : that.allCount.smallLength
-			let cargoName = '花材'
-			let newFreightParams = {
+            let packageNum = that.allCount && Number(that.allCount.bigLength)>0 ? Number(that.allCount.bigLength) : 
+            that.allCount.smallLength
+            let cargoName = '花材'
+        	let newFreightParams = {
 				cargo_name: cargoName,
 				price: that.allPriceFun,
 				weight: that.allCount.weight,
@@ -288,13 +429,97 @@ export default {
 				user_lat: that.userInfo.lat,
 				user_name: that.userInfo.name || that.userInfo.realName || '',
 				user_phone: that.userInfo.mobile || that.userInfo.phone || '',
-				user_address: that.userInfo.province + that.userInfo.city + that.userInfo.dist + that.userInfo.address + that.userInfo.floor
+				user_address: that.userInfo.province + that.userInfo.city + that.userInfo.dist + that.userInfo.address + 
+				that.userInfo.floor
+            }
+            newFreight(newFreightParams).then(res => {
+            	if(res.code == 1){
+					console.log('res: ',res)
+                 	that.form.sendCost = res.data.est_fee?parseFloat(Number(res.data.est_fee/100)):0
+             	}
+            }).catch(()=>{});
+		},
+		getDeliveryQuotes() {
+			let that = this
+			// 无地址或重量为 0 时,设为 0 并返回
+			if (this.$util.isEmpty(that.userInfo.lat) || this.$util.isEmpty(that.userInfo.long)) {
+				that.form.sendCost = 0
+				this.$msg('请填写地址')
+				return
 			}
-			newFreight(newFreightParams).then(res => {
-				if(res.code == 1){
-					that.form.sendCost = res.data.est_fee?parseFloat(Number(res.data.est_fee/100)):0
+			if(Number(that.allCount.weight) <= 0){
+				this.$msg('商品没有重量,无法使用跑腿')
+				return false
+			}
+			this.deliveryQuotesLoading = true
+			this.deliveryQuotes = []
+			this.selectedDeliveryIndex = -1
+			this.selectedDeliveryData = null
+			
+			allDeliveryQuotes({
+				buyType: 'huaCai',//购买类别: 花材、花束
+				totalPrice: this.allPriceFun,
+				weight: 5, // 固定重量5kg
+				selectList: this.selectList,
+				remark: this.form.remark,
+			}).then(res => {
+				this.deliveryQuotesLoading = false
+				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(')', '')
+							}
+							
+							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.form.sendCost = 0
+					}
+				} else {
+					uni.showToast({
+						title: res.msg || '暂无可用的配送平台',
+						icon: 'none'
+					})
 				}
-			}).catch(()=>{});
+			}).catch(err => {
+				this.deliveryQuotesLoading = false
+				console.error('获取跑腿报价失败:', err)
+				uni.showToast({
+					title: '获取报价失败',
+					icon: 'none'
+				})
+			})
+		},
+		// 选择跑腿平台
+		selectDelivery(item, index) {
+			if (!item.isAble) {
+				return;
+			}
+			this.selectedDeliveryIndex = index
+			this.selectedDeliveryData = item
+			this.form.sendCost = item.priceNumber
+			this.showDistance = item.distance || 0
 		},
 		confirmForm() {
 			const product = this.selectList.map(ele => {
@@ -307,7 +532,19 @@ export default {
 				return { productId: ele.id, num: num, itemId: ele.itemId, unitType: unitType, property: 1 };
 			});
 			let hdId = this.option.hdId ? this.option.hdId : 0
-			let params = { product: JSON.stringify(product),...this.form,hdId:hdId }
+			let params = {
+				product: JSON.stringify(product),
+				...this.form,
+				hdId:hdId,
+				isFreeDelivery:this.isFreeDelivery
+			}
+			// 如果选择了跑腿配送并且有选中的平台数据,则添加配送平台信息
+			if (this.form.sendType == 2 && this.selectedDeliveryData) {
+				params.deliveryPlatform = this.selectedDeliveryData.en_name // 平台名称
+				params.deliveryPrice = this.selectedDeliveryData.priceNumber // 报价金额
+				params.deliveryDistance = this.selectedDeliveryData.distance || 0 // 配送距离
+				params.deliveryBracketContent = this.selectedDeliveryData.bracketContent // 配送平台括号里的内容
+			}
 			if(this.form.sendType == 2 || this.form.sendType == 0){
 				if(this.$util.isEmpty(this.userInfo.lat) || this.$util.isEmpty(this.userInfo.long)){
 					this.$msg('请填写地址')
@@ -315,7 +552,7 @@ export default {
 				}
 			}
 			if(this.form.sendType == 2) {
-				if(this.form.hasMap == 0){
+				if(this.form.openIntraCity == 0){
 					this.$msg('跑腿送已停用,请换其它方式')
 					return false
 				}
@@ -475,4 +712,120 @@ export default {
 		}
 	}
 }
+/* 跑腿平台报价样式 */
+.delivery-quotes-container {
+	width: 100%;
+	margin-top: 10upx;
+}
+
+.delivery-quotes-grid {
+	display: flex;
+	flex-wrap: wrap;
+	gap: 10upx;
+	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-bottom: 5upx;
+	background: #FFFFFF;
+	border: 2upx solid #DDDDDD;
+	border-radius: 8upx;
+	box-sizing: border-box;
+	transition: all 0.3s;
+	
+	// 当只有1-2项时使用固定宽度
+	&.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: 10upx;
+}
 </style>

+ 114 - 66
mallApp/src/pages/order/buy.vue

@@ -20,20 +20,20 @@
         </tui-list-cell>
       </view>
       <block v-if="form.sendType == 2">
-        <app-delivery-module ref="appDelivery" @countFreight="countFreight" :hasMap="hasMap" />
+        <app-delivery-module 
+          ref="appDelivery"
+          @countFreight="countFreight" 
+          :hasMap="hasMap"
+          :showDistance="showDistance"
+          :isFreight="isFreight"
+          :openIntraCity="openIntraCity"
+          :hcFreeKm="hcFreeKm"
+          :hcMap="hcMap"
+          :totalPrice="goodsTotalPrice"
+          :allCountFun="{ bigLength: goodsInfo.buyNum, weight: goodsInfo.weight * goodsInfo.buyNum }"
+        />
       </block>
 
-      <view class="module-com input-line-wrap" v-if="form.sendType == 2">
-        <tui-list-cell class="line-cell" :hover="false">
-            <view class="tui-title">距离</view>
-            <view class="tui-input">{{ showDistance }}公里</view>
-        </tui-list-cell>
-        <tui-list-cell class="line-cell" :hover="false">
-            <view class="tui-title">跑腿费</view>
-            <view class="tui-input" v-if="isFreight">¥{{ freightPrice?parseFloat(freightPrice):0 }}</view>
-            <view class="tui-input" v-else>免跑腿费</view>
-        </tui-list-cell>
-      </view>
       <view style="height:90upx;"> </view>
 
       <view class="page-btn app-footer">
@@ -52,7 +52,7 @@
 </template>
 <script>
 import TuiListCell from "@/components/plugin/list-cell";
-import AppDeliveryModule from "@/components/app-delivery";
+import AppDeliveryModule from "./components/app-delivery";
 import AppListModule from "./components/app-order-list2";
 //import { getShopUser } from "@/utils/auth";
 import { getOrderShop } from "@/utils/config";
@@ -86,18 +86,74 @@ export default {
       hasMap: 0, // 是否具备定位功能 0不具备 1具备
       // 是否收配送费
       isFreight: false,
+      openIntraCity: 0,
+      // 免费公里数
+      hcFreeKm: 0,
+      // 免费公里数规则
+      hcMap: [],
+      deliveryPlatform: '', // 跑腿平台
+      deliveryPrice: 0, // 跑腿报价
+      deliveryDistance: 0, // 跑腿距离
+      deliveryBracketContent: '' // 跑腿平台括号里的内容
     };
   },
   onLoad(option) {
     this.option = option || {};
-    //this.init();
+    this.init();
   },
   computed: {
+    /**
+     * 是否免跑腿费
+     * @returns {boolean}
+     */
+    isFreeDelivery(){
+      // 不是跑腿送或不收配送费时,不需要判断
+      if(this.form.sendType != 2 || !this.isFreight){
+        return false
+      }
+      
+      // 条件1: 比较距离与免费公里数
+      // showDistance单位是公里,hcFreeKm单位也是公里
+      const distanceInKm = Number(this.showDistance) || 0
+      const freeKm = Number(this.hcFreeKm) || 0
+      
+      // 如果距离小于免费公里数,则免运费
+      if(distanceInKm < freeKm){
+        return true
+      }
+      
+      // 条件2: 如果距离大于免费公里数,判断hcMap
+      if(distanceInKm > freeKm){
+        // 判断hcMap是否为空
+        if(this.hcMap && Array.isArray(this.hcMap) && this.hcMap.length > 0){
+          // 获取花束数量和总价
+          const flowerCount = Number(this.goodsInfo.buyNum) || 0
+          const flowerPrice = this.goodsTotalPrice || 0
+          
+          // 遍历hcMap,检查是否满足免运费条件
+          for(const rule of this.hcMap){
+            const ruleNum = Number(rule.num) || 0
+            const rulePrice = Number(rule.price) || 0
+            const ruleDistance = Number(rule.distance) || 0 // 单位:公里
+            
+            // 如果花束数量大于等于num,花束总价大于等于price,距离小于等于distance,则免运费
+            if(flowerCount >= ruleNum && flowerPrice >= rulePrice && distanceInKm <= ruleDistance){
+              return true
+            }
+          }
+        }
+      }
+      
+      return false
+    },
     mayPayAmount(){
       let price = (this.goodsInfo.price || 0) * (this.goodsInfo.buyNum || 0)
       // 收配送费时,商品价格加上配送费
       if(this.isFreight){
-        price = Number(this.freightPrice)+Number(price)
+        // 如果不是免跑腿费,则加上跑腿费
+        if(!this.isFreeDelivery){
+          price = Number(this.freightPrice)+Number(price)
+        }
       }
       return parseFloat(price.toFixed(2))
     }
@@ -121,18 +177,37 @@ export default {
       this.goodsTotalPrice = parseFloat(total.toFixed(2))
 
       getHasMap().then(res => {
-        this.hasMap = (res.data && res.data.hasMap) ? res.data.hasMap : 0
+        this.hasMap = (res.data && res.data.hasMap) ? res.data.hasMap : 0;
+
+        const shopData = res.data.shop || {};
+        this.openIntraCity = shopData.openIntraCity || 0;
+        this.hcFreeKm = shopData.hcFreeKm || 0;
+        this.hcMap = shopData.hcMap || [];
       })
     },
 		changeSendType(num){
 			this.form.sendType = num
 			if(num == 2){
 				this.$nextTick(()=>{
-					this.countFreight()
+					this.$refs.appDelivery.triggerGetDeliveryQuotes();
 				})
-			}
+			} else {
+        this.freightPrice = 0;
+        this.showDistance = 0;
+      }
 		},
-    countFreight() {
+    countFreight(data = {}) {
+      if(this.isFreight){
+        this.freightPrice = data.sendCost || 0
+      } else {
+        this.freightPrice = 0
+      }
+      this.showDistance = data.distance ? parseFloat((data.distance / 1000).toFixed(2)) : 0;
+      this.deliveryPlatform = data.deliveryPlatform || '';
+      this.deliveryPrice = data.deliveryPrice || 0;
+      this.deliveryDistance = data.deliveryDistance || 0;
+      this.deliveryBracketContent = data.deliveryBracketContent || '';
+
       const ref = this.$refs.appDelivery
       if(!ref || !ref.region) return
       let lat = ref.region.latitude;
@@ -140,54 +215,15 @@ export default {
       this.form.lat = lat
       this.form.long = long
 
-      if(this.isFreight == false){
-        //免跑腿费不需要查询
-        return
-      }
-
-      try {
-        const deliveryForm = (ref && ref.form) ? ref.form : {};
-        const goodsInfo = this.goodsInfo || {};
-        
-        const cargoName = goodsInfo.goodsName || goodsInfo.name || '商品';
-        const packageNum = Number(goodsInfo.buyNum || this.form.goodsNum || 0);
-        // 商品总重量 = 单件重量(kg,缺省视为 1) * 数量
-        const unitWeight = goodsInfo.weight ? Number(goodsInfo.weight) : 1; // 单件重量,缺省视为 1 公斤
-        const totalWeight = Number((unitWeight * packageNum).toFixed(2));
-        const userName = deliveryForm.receiveUserName || '';
-        const userPhone = deliveryForm.receiveMobile || '';
-
-        // 仅当必要数据都具备时才调用 newFreight
-        const hasLocation = lat && long;
-        const hasValidNum = packageNum > 0;
-        const hasValidWeight = totalWeight > 0;
-        const hasContact = !!userName && !!userPhone;
-        if (!(hasLocation && hasValidNum && hasValidWeight && hasContact)) {
-          return;
-        }
-        const params = {
-          cargo_name: cargoName,
-          price: this.goodsTotalPrice,
-          weight: totalWeight,
-          package_num: packageNum,
-          user_lng: long,
-          user_lat: lat,
-          user_name: userName,
-          user_phone: userPhone,
-          user_address: deliveryForm.address + deliveryForm.floor+'('+deliveryForm.showAddress+')'
-        };
-        newFreight(params).then(res => {
-          if (res && res.code == 1) {
-            const data = res.data || {};
-            this.freightPrice = data.est_fee ? parseFloat(Number(data.est_fee/100)) : 0;
-            this.showDistance = parseFloat((data.distance/1000 || 0).toFixed(2))
-            this.form.sendDistance = parseFloat((data.distance/1000 || 0).toFixed(2))
-            this.form.sendCost = this.isFreight ? this.freightPrice : 0
-          }
-        }).catch(() => {});
-      } catch (e) {}
+      this.form.sendDistance = this.showDistance
+      this.form.sendCost = this.freightPrice
     },
     formSubmit() {
+      if(this.form.sendType == 2 && this.openIntraCity == 0){
+        this.$msg('跑腿暂不可用,请选它方式下单')
+        return
+      }
+      
       this.createOrderFn()
     },
     createOrderFn() {
@@ -222,7 +258,19 @@ export default {
       uni.showLoading({mask:true})
       let hdId = this.option.hdId?this.option.hdId:0
       let account = this.option.account?this.option.account:0
-      createOrder({...form, ...this.form,hdId:hdId}).then(res => {
+      
+      const params = {
+        ...form,
+        ...this.form,
+        hdId: hdId,
+        deliveryPlatform: this.deliveryPlatform,
+        deliveryPrice: this.deliveryPrice,
+        deliveryDistance: this.deliveryDistance,
+        deliveryBracketContent: this.deliveryBracketContent,
+        isFreeDelivery: this.isFreeDelivery
+      };
+      
+      createOrder(params).then(res => {
         uni.hideLoading()
         if(res.code == 1){
           uni.removeStorageSync("buyGoodsDetil")

+ 684 - 0
mallApp/src/pages/order/components/app-delivery.vue

@@ -0,0 +1,684 @@
+<template>
+  <div class="app-delivery-module">
+    <!-- 收花人 -->
+    <div class="module-com">
+      <tui-list-cell class="line-cell" :hover="false">
+        <div class="tui-title">收花人姓名</div>
+        <input v-model="form.receiveUserName" placeholder-class="phcolor" placeholder-style="color:#ccc" @blur="onInputBlur" class="tui-input" name="receiveUserName" placeholder="请填写姓名" />
+      </tui-list-cell>
+      <tui-list-cell class="line-cell" :hover="false">
+        <div class="tui-title">收花人电话</div>
+        <input v-model="form.receiveMobile" placeholder-class="phcolor" placeholder-style="color:#ccc" @blur="onInputBlur" class="tui-input" name="receiveMobile" placeholder="请填写电话" type="number" />
+      </tui-list-cell>
+      <tui-list-cell class="line-cell" :hover="false" :arrow="true" @click="openAddres">
+        <div class="tui-title">所在城市</div>
+        <div class="tui-input" v-if="form.province || form.city" >{{ form.province + '-' + form.city }}</div>
+        <div class="tui-placeholder" v-else>请选择</div>
+      </tui-list-cell>
+      <tui-list-cell v-if="hasMap == 0" class="line-cell" :hover="false">
+        <view class="tui-title">详细地址</view>
+        <input v-model="form.address" placeholder-class="phcolor" class="tui-input" name="address" @input="modifyShow" placeholder="请填写地址" />
+      </tui-list-cell>
+			<tui-list-cell v-else class="line-cell" :hover="false" :arrow="true" @click="selRegionFn">
+        <div class="tui-title">详细地址</div>
+        <div class="tui-input" v-if="form.address">{{ form.address }}</div>
+        <div class="tui-placeholder" v-else>请选择地址</div>
+      </tui-list-cell>
+      <tui-list-cell class="line-cell" :hover="false">
+        <div class="tui-title">门牌号</div>
+        <input v-model="form.floor" placeholder-class="phcolor" placeholder-style="color:#ccc" class="tui-input" name="floor" placeholder="楼号门牌号(选填)" @input="modifyShow" />
+      </tui-list-cell>
+    </div>
+    <!-- 订花人 -->
+    <div class="module-com">
+      <tui-list-cell class="line-cell" :hover="false" :arrow="true">
+        <div class="tui-title">配送日期</div>
+        <picker mode="date" :value="form.reachDate" @change="selTimeFn" class="tui-input">
+          <div v-if="form.reachDate">{{ form.reachDate }}</div>
+          <div v-else>今天</div>
+        </picker>
+      </tui-list-cell>
+      
+      <tui-list-cell class="line-cell" :hover="false" :arrow="false">
+        <div class="tui-title">配送时间</div>
+					<picker mode="multiSelector" :value="form.reachPeriod" :range="timeOptions" @change="bindTimeChange">
+						<view class="uni-input" style="width:450upx;">{{form.reachPeriod}}<text style="margin-left:10upx;">前</text></view>
+					</picker>
+      </tui-list-cell>
+
+      <block v-if="openIntraCity == 0">
+        <tui-list-cell class="line-cell" :hover="false" :arrow="false">
+          <view class="delivery-unavailable-notice">
+            <view class="notice-icon">
+              <text class="icon-text">!</text>
+            </view>
+            <view class="notice-content">
+              <view class="notice-title">跑腿配送暂不可用</view>
+              <view class="notice-desc">请选择其他配送方式下单</view>
+            </view>
+          </view>
+        </tui-list-cell>
+      </block>
+      <block v-else>
+        <tui-list-cell class="line-cell" :hover="false">
+          <view class="tui-title">距离</view>
+          <view class="tui-input" v-if="showDistance > 0">{{ showDistance }}公里</view>
+          <view class="tui-input" v-else>0公里</view>
+        </tui-list-cell>
+        <tui-list-cell class="line-cell" :hover="false">
+            <view class="tui-title">跑腿费</view>
+            <view class="tui-input">
+              <text :class="isFreeDelivery ? 'delivery-price-free' : 'delivery-price-normal'">¥ {{ form.sendCost }}</text>
+              <text v-if="isFreeDelivery" class="free-delivery-tag">【免跑腿费】</text>
+            </view>
+        </tui-list-cell>
+        
+        <!-- 跑腿平台报价列表 -->
+        <tui-list-cell class="line-cell" :hover="false" :arrow="false" v-if="deliveryQuotes.length > 0">
+          <view 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>
+        </tui-list-cell>
+        <tui-list-cell class="line-cell" :hover="false" :arrow="false" v-else-if="deliveryQuotesLoading">
+          <view style="text-align: center; padding: 20rpx 0; color: #999;">正在获取报价...</view>
+        </tui-list-cell>
+      </block>
+      
+    </div>
+
+    <div class="module-com">
+
+      <tui-list-cell class="line-cell" :hover="false">
+        <div class="tui-title">贺卡内容</div>
+        <textarea v-model="form.cardInfo" placeholder-class="phcolor" placeholder-style="color:#ccc" class="tui-input textarea-input" name="cardInfo" placeholder="请填写" :adjust-position="false" :cursor-spacing="0" :auto-height="false" :disable-default-padding="true" :show-confirm-bar="false" />
+
+      </tui-list-cell>
+
+      <tui-list-cell class="line-cell" :hover="false">
+        <div class="tui-title">派送方式</div>
+        <div class="delivery-mode-wrapper">
+          <switch :checked="form.anonymity == 1" @change="anonymityChange" color="#ff6b35" />
+          <span v-if="form.anonymity == 0" class="mode-text normal">不匿名</span>
+          <span v-else class="mode-text anonymous">要匿名</span>
+        </div>
+      </tui-list-cell>
+
+      <tui-list-cell class="line-cell" :hover="false">
+        <div class="tui-title">备注内容</div>
+        <textarea v-model="form.remark" placeholder-class="phcolor" placeholder-style="color:#ccc" class="tui-input textarea-input" name="remark" placeholder="选填,不要贺卡这里备注" :adjust-position="false" :cursor-spacing="0" :auto-height="false" :disable-default-padding="true" :show-confirm-bar="false" />
+      </tui-list-cell>
+    </div>
+    <!-- 省市联动 -->
+    <simple-address ref="simpleAddress" :region="orderShop.region" :pickerValueDefault="cityPickerValueDefault" @onConfirm="onCityConfirm" >
+    </simple-address>
+    <!-- 选择地区 -->
+    <app-area-sel :isFocus="false" :show.sync="showRegion" :city="form.city" @change="changeAreaFn" />
+  </div>
+</template>
+
+<script>
+import { mapGetters } from "vuex";
+import TuiListCell from "@/components/plugin/list-cell";
+import AppDatePicker from "@/components/app-date-picker";
+import SimpleAddress from "@/components/plugin/simple-address";
+import AppAreaSel from "@/components/app-area-sel";
+import { allDeliveryQuotes } from "@/api/express/delivery";
+
+export default {
+  name: "app-delivery-module",
+  components: {
+    AppAreaSel,
+    TuiListCell,
+    AppDatePicker,
+    SimpleAddress
+  },
+  props: {
+    // 是否开启同城配送
+    openIntraCity: {
+      type: Number,
+      default: 0
+    },
+    hasMap: {
+      type: Number,
+      default: 0
+    },
+    showDistance: {
+      type: Number,
+      default: 0
+    },
+    isFreight: {
+      type: Boolean,
+      default: false
+    },
+    hcFreeKm: {
+      type: Number,
+      default: 0
+    },
+    hcMap: {
+      type: Array,
+      default: () => []
+    },
+    totalPrice: {
+      type: Number,
+      default: 0
+    }
+  },
+  data() {
+    return {
+      showRegion: false,
+      form: {
+        receiveUserName: "", // 收花人姓名;到店自提的姓名
+        receiveMobile: "", // 收花人手机;到店自提的电话;
+        province: "", // 收花人省份
+        city: "", // 收花人城市
+        address: "", // 收花人城市
+        floor: "", // 收花人门楼号和牌号
+        showAddress: "", // 收花人完整地址,从腾讯地图接口获取
+        reachDate: "", // 配送时间
+        reachPeriod: "12:00", // 配送时段,0上午1下午2晚上
+        cardInfo: "", // 贺卡信息
+        remark: "", // 备注
+        anonymity:0,
+        sendCost: 0,
+        deliveryPlatform: '',
+        deliveryPrice: 0,
+        deliveryDistance: 0
+      },
+      region: {
+        latitude: 0,
+        longitude: 0
+      },
+      // 省市联动
+      cityPickerValueDefault: [0, 0],
+      timeOptions: [
+			['00', '01', '02','03','04','05','06','07','08','09','10','11','12','13', '14','15','16','17','18','19','20','21','22','23'],
+			['00', '05', '10','15','20','25','30','35','40','45','50','55']
+		  ],
+      deliveryQuotes: [], // 跑腿平台报价列表
+      selectedDeliveryIndex: -1, // 选中的报价索引
+      deliveryQuotesLoading: false, // 报价加载状态
+      selectedDeliveryData: null, // 选中的报价数据
+      goodsInfo: {}
+    };
+  },
+  computed: {
+    ...mapGetters({ orderShop: "getOrderShop" }),
+    ...mapGetters({ shopUser: "getShopUser" }),
+    isFreeDelivery(){
+      // 如果父组件没传isFreight,或者isFreight为false,则直接判定为免运费
+      if(!this.isFreight) return true;
+      
+      const distanceInMeters = (Number(this.showDistance) || 0) * 1000;
+      const freeKmInMeters = (Number(this.hcFreeKm) || 0) * 1000;
+      
+      if (distanceInMeters > 0 && distanceInMeters < freeKmInMeters) {
+        return true;
+      }
+      
+      if(distanceInMeters > freeKmInMeters){
+        if (this.hcMap && Array.isArray(this.hcMap) && this.hcMap.length > 0) {
+          const goodsInfo = uni.getStorageSync("buyGoodsDetil") || {};
+          const flowerCount = Number(goodsInfo.buyNum || 0);
+          const flowerPrice = (Number(goodsInfo.price) || 0) * flowerCount;
+          
+          for (const rule of this.hcMap) {
+            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;
+    }
+  },
+  watch: {
+    orderShop(val) {
+      console.log("val", val);
+      this.form.province = val.shop.province ? val.shop.province : "";
+      this.form.city = val.shop.city ? val.shop.city : "";
+    }
+  },
+  mounted() {
+    if (!this.$util.isEmpty(this.orderShop)) {
+      this.form.province = this.orderShop.shop.province ? this.orderShop.shop.province : ""
+      this.form.city = this.orderShop.shop.city ? this.orderShop.shop.city : ""
+    }
+  },
+  methods: {
+    bindTimeChange(event){
+      let val = event.detail.value
+      let before = Number(val[0])
+      let after = Number(val[1])
+      let timeOptions = this.timeOptions
+      this.form.reachPeriod = timeOptions[0][before]+':'+timeOptions[1][after]
+    },
+    selTimeFn(e) {
+      this.form.reachDate = e.detail.value;
+    },
+    // 选择地址
+    selRegionFn() {
+      if (!this.form.city) {
+        this.$msg("请先选择地址!");
+        return false;
+      }
+      this.showRegion = true;
+    },
+    changeAreaFn(e) {
+      this.form.address = e.name;
+      this.form.showAddress = e.address;
+      let locationStr = e.location
+      let fruits = locationStr.split(",").map(fruit => fruit.trim());
+      this.region.latitude = fruits[1];
+      this.region.longitude = fruits[0];
+      this.triggerGetDeliveryQuotes();
+    },
+    onInputBlur(){
+      this.triggerGetDeliveryQuotes()
+    },
+    anonymityChange(e) {
+      this.form.anonymity = e.detail.value ? 1 : 0;
+    },
+    // 省市联动
+    openAddres() {
+      //城市暂时不可修改 shish 2020.5.11
+      this.$refs.simpleAddress.open();
+    },
+    onCityConfirm(e) {
+      this.form.address = e.label;
+      this.form.province = e.provinceName;
+      this.form.city = e.cityName;
+    },
+    modifyShow(){
+			this.form.showAddress = this.form.province+this.form.city+this.form.address+this.form.floor
+		},
+    triggerGetDeliveryQuotes() {
+      if (this.form.receiveUserName && this.form.receiveMobile && this.form.address) {
+        this.getDeliveryQuotes();
+      }
+    },
+    getDeliveryQuotes() {
+      this.goodsInfo = uni.getStorageSync("buyGoodsDetil") || {}
+      const weight = (this.goodsInfo.weight || 1) * (this.goodsInfo.buyNum || 1);
+
+			this.deliveryQuotesLoading = true
+			this.deliveryQuotes = []
+			this.selectedDeliveryIndex = -1
+			this.selectedDeliveryData = null
+			
+			allDeliveryQuotes({
+        buyType: 'huaShu',//购买类别: 花材、花束
+        totalPrice: this.totalPrice,
+        weight: weight,
+        selectList: [this.goodsInfo],
+				...this.form,
+        region: this.region
+			}).then(res => {
+				this.deliveryQuotesLoading = false
+				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('(')) {
+								//displayName = displayName.split('(')[0].trim()
+                bracketContent = displayName.split('(')[1].replace(')', '')
+							}
+							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 {
+					uni.showToast({
+						title: '暂无可用的配送平台',
+						icon: 'none'
+					})
+				}
+			}).catch(err => {
+				this.deliveryQuotesLoading = false
+			})
+    },
+    selectDelivery(item, index) {
+			if (!item.isAble) return;
+			this.selectedDeliveryIndex = index
+			this.selectedDeliveryData = item
+      this.form.sendCost = this.isFreeDelivery ? 0 : item.priceNumber;
+			this.form.deliveryPlatform = item.en_name;
+      this.form.deliveryPrice = item.priceNumber;
+      this.form.deliveryDistance = item.distance || 0;
+      this.form.deliveryBracketContent = item.bracketContent || '';
+			this.$emit("countFreight", {
+        sendCost: this.form.sendCost,
+        distance: item.distance,
+        deliveryPlatform: item.en_name,
+        deliveryPrice: item.priceNumber,
+        deliveryBracketContent: item.bracketContent
+      })
+		},
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.app-delivery-module {
+  .module-com {
+    margin-bottom: 20upx;
+    background-color: #fff;
+    .module-tit {
+      padding: 20upx 18upx;
+      font-size: 28upx;
+      font-weight: 600;
+      border-bottom: 1upx solid $borderColor;
+    }
+    .module-det {
+      padding: 0 30upx;
+    }
+  }
+  // 公共
+  .line-cell {
+    .tui-title {
+      width: 210upx;
+      color: $fontColor2;
+    }
+    .tui-input {
+      width: calc(100% - 210upx);
+      font-size: 28upx;
+    }
+    .tui-placeholder {
+      color: #ccc;
+    }
+    .phcolor {
+      color: green;
+    }
+  }
+  // 今天时间选择
+  .delivery-time {
+    @include disFlex(center, center);
+    .button-com {
+      padding-top: 14upx;
+      padding-bottom: 10upx;
+      margin: 0 10upx;
+    }
+  }
+  // 贺卡
+  .gift-wrap {
+    .gift-content {
+      width: 100%;
+    }
+    .need-gift {
+      margin-left: 14upx;
+      color: $fontColor2;
+    }
+    .gitf-textarea {
+      margin-top: 40upx;
+    }
+  }
+
+  // 派送方式选择器样式
+  .delivery-mode-wrapper {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    
+    .mode-text {
+      font-size: 40upx;
+      margin-left: 10upx;
+      transition: color 0.3s ease;
+      &.normal {
+        // 不匿名 - 使用页面常规颜色
+        color: #666666;
+      }
+      
+      &.anonymous {
+        // 匿名 - 使用显眼颜色
+        color: #ff6b35;
+        font-weight: bold;
+      }
+    }
+    
+    switch {
+      transform: scale(0.8);
+    }
+  }
+
+  // textarea样式 - 彻底解决input文字浮动问题
+  .textarea-input {
+    /* 重置textarea默认样式,使其表现像input */
+    resize: none !important;
+    overflow: hidden !important;
+    border: none !important;
+    outline: none !important;
+    background: transparent !important;
+    padding: 0 !important;
+    margin: 0 !important;
+    
+    /* 固定高度和行高,防止浮动 */
+    height: 44upx !important;
+    max-height: 44upx !important;
+    min-height: 44upx !important;
+    line-height: 44upx !important;
+    
+    /* 字体样式保持一致 */
+    font-size: 28upx !important;
+    font-family: inherit !important;
+    color: inherit !important;
+    
+    /* 文字单行显示,模拟input行为 */
+    white-space: nowrap !important;
+    text-overflow: ellipsis !important;
+    word-wrap: normal !important;
+    word-break: keep-all !important;
+    
+    /* 垂直对齐 */
+    vertical-align: middle !important;
+    text-align: left !important;
+    
+    /* 微信小程序特殊处理 */
+    /* #ifdef MP-WEIXIN */
+    /* 禁用textarea的多行特性 */
+    overflow-y: hidden !important;
+    scrollbar-width: none !important;
+    /* 固定位置,防止键盘影响 */
+    position: relative !important;
+    z-index: 1 !important;
+    /* 禁用文字大小自动调整 */
+    -webkit-text-size-adjust: none !important;
+    text-size-adjust: none !important;
+    /* 强制使用系统字体 */
+    font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, sans-serif !important;
+    /* #endif */
+    
+    /* H5环境优化 */
+    /* #ifdef H5 */
+    box-sizing: border-box !important;
+    -webkit-appearance: none !important;
+    appearance: none !important;
+    /* #endif */
+    
+    /* APP环境优化 */
+    /* #ifdef APP-PLUS */
+    box-sizing: border-box !important;
+    /* #endif */
+  }
+}
+
+/* 跑腿平台报价样式 */
+.delivery-quotes-container {
+	width: 100%;
+	margin-top: 20rpx;
+}
+
+.delivery-quotes-grid {
+	display: flex;
+	flex-wrap: wrap;
+	gap: 10rpx;
+	padding: 0;
+	width: 100%;
+}
+
+.delivery-quote-item {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	flex: 0 0 calc(33% - 7rpx);
+	width: calc(33% - 7rpx);
+	min-height: 140rpx;
+	padding: 16rpx 10rpx;
+	margin-bottom: 5rpx;
+	background: #FFFFFF;
+	border: 2rpx solid #DDDDDD;
+	border-radius: 8rpx;
+	box-sizing: border-box;
+	transition: all 0.3s;
+	
+	&.fixed-width {
+		flex: 0 0 220rpx;
+		width: 220rpx;
+	}
+	
+	.platform-name {
+		font-size: 26rpx;
+		color: #333;
+		font-weight: bold;
+		margin-bottom: 8rpx;
+		text-align: center;
+		line-height: 1.3;
+		max-width: 100%;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		display: -webkit-box;
+		-webkit-line-clamp: 2;
+		-webkit-box-orient: vertical;
+		word-break: break-all;
+	}
+	
+	.platform-price {
+		font-size: 30rpx;
+		color: #333;
+		font-weight: bold;
+		margin-bottom: 4rpx;
+	}
+	
+	.platform-type {
+		font-size: 20rpx;
+		color: #868686;
+		margin-top: 4rpx;
+	}
+	
+	&.active {
+		background: #09C567;
+		border-color: #09C567;
+		
+		.platform-name,
+		.platform-price,
+    .platform-type {
+			color: #FFFFFF;
+		}
+	}
+	
+	&.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: 10rpx;
+}
+
+/* 跑腿不可用提示样式 */
+.delivery-unavailable-notice {
+	display: flex;
+	align-items: center;
+	width: 100%;
+	padding: 20rpx;
+	background: linear-gradient(135deg, #FFF5E6 0%, #FFF9F0 100%);
+	border-radius: 12rpx;
+	border-left: 6rpx solid #FF9800;
+	
+	.notice-icon {
+		flex-shrink: 0;
+		width: 60rpx;
+		height: 60rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		background: #FF9800;
+		border-radius: 50%;
+		margin-right: 20rpx;
+		
+		.icon-text {
+			font-size: 40rpx;
+			font-weight: bold;
+			color: #FFFFFF;
+			line-height: 1;
+		}
+	}
+	
+	.notice-content {
+		flex: 1;
+		
+		.notice-title {
+			font-size: 30rpx;
+			font-weight: bold;
+			color: #FF9800;
+			margin-bottom: 8rpx;
+			line-height: 1.4;
+		}
+		
+		.notice-desc {
+			font-size: 26rpx;
+			color: #F57C00;
+			line-height: 1.4;
+		}
+	}
+}
+</style>