Procházet zdrojové kódy

销花宝、花掌柜、收银端下的快捷下单功能实现跑腿能查询各平台跑腿报价,并根据免配送规则进行免配送费判断:
1. 根据跑腿发送时间 -- 晚点呼叫是后端不创建跑腿
2. 根据跑腿发送时间 -- 现在呼叫是后端会自动创建跑腿单并发送

shizhongqi před 7 měsíci
rodič
revize
4632602752

+ 418 - 129
ghsApp/src/admin/billing/affirm.vue

@@ -283,10 +283,75 @@
                 <input type="text" placeholder="请填写物流" v-model="form.wlName" @focus="form.wlName = ''" placeholder-class="tui-placeholder" />
               </tui-list-cell>
 
-              <tui-list-cell class="line-cell" :hover="false" :arrow="false" v-show="displaySendCost == true">
-                <view class="tui-title">运费</view>
-                <input type="digit" placeholder="请填写运费" v-model="form.sendCost" placeholder-class="tui-placeholder" />
-              </tui-list-cell>
+              <view v-show="displaySendCost == true">
+                <view v-if="openIntraCity == 1" class="delivery-section">
+                  <tui-list-cell v-if="openIntraCity == 1" class="line-cell" :hover="false">
+                    <!-- 跑腿平台报价列表 -->
+                    <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 class="empty-quotes-view">
+                      <text>正在获取运费...</text>
+                      <!-- <text class="error-tips">上面地址缺失或有问题,请修改</text> -->
+                    </view>
+                  </tui-list-cell>
+                  <tui-list-cell class="line-cell" :hover="false">
+                    <view class="tui-title">距离</view>
+                    <view class="distance-box">
+                      <text v-if="showDistance > 100" class="blue-text">{{ (showDistance/1000).toFixed(1) }}公里</text>
+                      <text v-else class="blue-text">{{ showDistance }} 米</text>
+                      <!-- <input type="digit" :value="showDistance/1000" placeholder-class="tui-placeholder" style="text-align:left;width:70upx;margin-right:10upx;"/>公里 -->
+                    </view>
+                  </tui-list-cell>
+                  <tui-list-cell class="line-cell" :hover="false" :arrow="false">
+                    <view class="tui-title title-reduction">满减规则</view>
+                    <button @tap="form.reductionRule = 0" class="admin-button-com mini-btn btn-reduction" :class="form.reductionRule == 0 ? 'active' : 'default'">没有满减</button>
+                    <button @tap="form.reductionRule = 1" class="admin-button-com mini-btn btn-reduction" :class="form.reductionRule == 1 ? 'active' : 'default'">用花材</button>
+                  </tui-list-cell>
+                  <tui-list-cell class="line-cell" :hover="false" :arrow="false">
+                    <view class="tui-title">运费</view>
+                    <text v-if="form.reductionRule == 0" class="blue-text">¥{{ form.sendCost }}</text>
+                    <text v-if="form.reductionRule == 1" class="free-delivery-tag">
+                      <text v-if="isHcMapRule()"><text class="strike-through">¥{{ form.sendCost }}</text>【花材免跑腿费】</text>
+                      <text v-else>¥{{ form.sendCost }}</text>
+                    </text>
+                  </tui-list-cell>
+                  <tui-list-cell class="line-cell" :hover="false" :arrow="false">
+                    <view class="tui-title">呼叫跑腿</view>
+                    <button @tap="form.callErrand = 0" class="admin-button-com middle btn-call" :class="form.callErrand == 0? 'active' : 'default'">晚点呼叫</button>
+                    <button @tap="form.callErrand = 1" class="admin-button-com middle btn-call" :class="form.callErrand == 1 ? 'active' : 'default'">现在呼叫</button>
+                  </tui-list-cell>
+                  <tui-list-cell v-if="form.callErrand == 1" class="line-cell" :hover="false" :arrow="false">
+                    <view class="tui-title">取件时间</view>
+                    <view class="time-action-view" @click.stop="openTimePicker">
+                      <text>{{ pickupTime.label || '立即取件' }}</text>
+                      <text class="iconfont iconxiangyou icon-arrow-right"></text>
+                    </view>
+                  </tui-list-cell>
+                </view>
+                <view v-else class="delivery-section">
+                  <tui-list-cell class="line-cell" :hover="false" :arrow="false">
+                    <view class="tui-title">运费</view>
+                    <input type="digit" placeholder="请填写运费" v-model="form.sendCost" placeholder-class="tui-placeholder"/>
+                  </tui-list-cell>
+                </view>
+              </view>
 
               <tui-list-cell class="line-cell" :hover="false" :arrow="false">
                 <view class="tui-title">打印小票</view>
@@ -491,45 +556,47 @@
         @confirm="confirmPicker"
         @cancel="showPicker = false"
       />
+
+      <!-- 取件时间选择器 -->
+      <TimePicker ref="timePicker" @confirm="handleTimeConfirm" />
     </view>
   </view>
 </template>
 
 <script>
 import TuiListCell from '@/components/plugin/list-cell';
-import AppAvatarModule from '@/components/module/app-avatar';
-import SimpleAddress from '@/components/plugin/simple-address';
-import AppAreaSel from '@/components/app-area-sel';
 import productMins from '@/mixins/product';
-import appFormRadioBtn from '@/components/app-formRadio-btn';
-import appAddressGroup from '@/components/app-address-group';
-import appSendTime from '@/components/app-send-time';
-import appFormSend from '@/components/app-form-send';
 const formUtil = require('@/utils/formValidation.js');
+import MxDatePicker from '@/components/mx-datepicker/mx-datepicker.vue';
+import ModalModule from '@/components/plugin/modal';
+import TimePicker from '@/components/plugin/TimePicker';
 import { createOrder, updateOrder } from '@/api/order/index';
 import { getAllStaff } from '@/api/shop-admin';
 import { mapActions, mapGetters } from 'vuex';
 import { getUserDet } from '@/api/member';
-import ModalModule from '@/components/plugin/modal';
 import { getStaffInfo } from '@/api/staff';
-import MxDatePicker from '@/components/mx-datepicker/mx-datepicker.vue';
+import { quickOrderAllDeliveryQuotes } from '@/api/express/delivery';
+import dayjs from 'dayjs';
+
 export default {
   name: 'affirm',
   components: {
     TuiListCell,
-    AppAvatarModule,
-    SimpleAddress,
-    AppAreaSel,
-    appFormRadioBtn,
-    appAddressGroup,
-    appSendTime,
-    appFormSend,
     ModalModule,
-    MxDatePicker
+    MxDatePicker,
+    TimePicker
   },
   mixins: [productMins],
   data() {
     return {
+      deliveryQuotes: [], // 跑腿平台报价列表
+      selectedDeliveryIndex: -1,
+      selectedDeliveryData: null,
+      showDistance: 0,
+      pickupTime: {
+        label: '立即取件',
+        value: dayjs().format('YYYY-MM-DD HH:mm')
+      },
       defaultPickerDate: '',
       showPicker: false,
       pickerType: '', // 'history' 或 'sendDate'
@@ -546,7 +613,6 @@ export default {
         { name: '不打折', value: 1 }
       ],
       discountValue: 0,
-      autoLoad: false,
       staffList: [],
       payWayList: [
         { name: '线下微信(收款码)', id: 0 },
@@ -558,16 +624,9 @@ export default {
       form: {
         shopId: '',
         shopName: '',
-        receiveProvince: '',
-        receiveCity: '',
-        receiveFloor: '',
-        receiveAddress: '',
-        receiveLong: '',
-        receiveLat: '',
         sendType: 0,
         transType: 0,
-        sendSide: '2',
-        sendDate: '',
+        sendDate: '', // 保留以防 API 需要
         sendTimeWant: '',
         historyDate: '',
         sendCost: '',
@@ -581,20 +640,22 @@ export default {
         getStaffId: 0,
         getStaffName: '',
         dealPrice: 0,
-        wlName: ''
+        wlName: '',
+        reductionRule: 0,
+        callErrand: 0 //现在呼叫 0 晚点呼叫 1
       },
       showCustomer: false,
       customInfo: { discount: 1 },
       customData: { bigCount: 0, smallCount: 0, userPrice: 0 },
       isAddModel: false,
-      checkStock: true,
-      xjData: {},
-      xjDataInfo: {},
       modifyPrice: '',
       isScanEnv: false,
       calc: 'add',
       diffPriceList: [],
       currentShop: { default: 0 },
+      openIntraCity: 0,
+      hcFreeKm: 0,  //跑腿免费公里数
+      hcMap: [],    //花材满减规则
       book: 0,
       selectStaff: { id: '', name: '' },
       showFinance: 0,
@@ -602,12 +663,20 @@ export default {
     };
   },
   onLoad(option) {
-    this.book = this.option.book ? this.option.book : 0;
+    this.option = option;
+    this.book = option.book ? option.book : 0;
     this.kdType = option.kdType;
     let that = this;
     this.initMerchantInfo().then((data) => {
       uni.setNavigationBarTitle({ title: data.name });
       that.currentShop = data.shopInfo;
+      that.openIntraCity = data.openIntraCity;
+      that.hcFreeKm = data.hcFreeKm;
+      try {
+        that.hcMap = typeof data.hcMap === 'string' ? JSON.parse(data.hcMap) : data.hcMap;
+      } catch (e) {
+        that.hcMap = [];
+      }
     });
 
     //默认送货
@@ -669,6 +738,11 @@ export default {
       this.selectStaff.id = this.form.getStaffId;
       this.selectStaff.name = this.form.getStaffName;
     }
+
+    //获取跑腿平台报价
+    if (this.openIntraCity == 1 && this.form.sendType == 2) {
+      this.getDeliveryQuotes();
+    }
   },
   onUnload() {
     uni.removeStorageSync('newClient');
@@ -711,9 +785,26 @@ export default {
     finalPrice() {
       let totalPrice = this.allPrice.toFixed(2);
       let finalPrice = Number(totalPrice);
-      if (this.displaySendCost == true) {
+      
+      // 跑腿费逻辑
+      if (this.form.sendType == 2) {
+        const sendCost = Number(this.form.sendCost);
+        if (this.form.reductionRule == 0) {
+          finalPrice = Number(finalPrice) + sendCost;
+        } else if (this.form.reductionRule == 1) {
+          // 花材满减
+          if (this.isHcMapRule()) {
+             // 满足条件免运费,不加 sendCost
+             finalPrice = Number(finalPrice);
+          } else {
+             finalPrice = Number(finalPrice) + sendCost;
+          }
+        }
+      } else if (this.displaySendCost == true) {
+        // 其他配送方式
         finalPrice = Number(finalPrice) + Number(this.form.sendCost);
       }
+
       if (this.displayPackCost == true) {
         finalPrice = Number(finalPrice) + Number(this.form.packCost);
       }
@@ -724,6 +815,7 @@ export default {
     }
   },
   methods: {
+    init() {},
     ...mapActions(['initMerchantInfo']),
     getMyFinance() {
       this.showFinance = this.showFinance == 1 ? 0 : 1;
@@ -755,12 +847,10 @@ export default {
       this.$refs.discountRef.close();
     },
     beginScan() {
-      console.log('beginScan');
-      let that = this;
       uni.scanCode({
         scanType: ['barCode'],
-        success: function (res) {
-          that.takeItem(res.result);
+        success: (res) => {
+          this.takeItem(res.result);
         }
       });
     },
@@ -783,24 +873,11 @@ export default {
       this.kdType = 'HIT';
       this.replaceItemFn(item, 0, 'add');
     },
-    init() {},
-    kdChange(e) {
-      let index = e.detail.value;
-      if (!this.$util.isEmpty(this.staffList)) {
-        if (this.staffList[index]) {
-          this.form.getStaffName = this.staffList[index].name;
-          this.form.getStaffId = this.staffList[index].id;
-        }
-      }
-    },
     payWayChange(e) {
       let index = e.detail.value;
       this.payWayindex = index;
       this.form.payWay = this.payWayList[index].id;
     },
-    setPayWay(payWay) {
-      this.form.payWay = payWay;
-    },
     setTransType(type) {
       this.form.transType = type;
     },
@@ -808,6 +885,13 @@ export default {
       this.form.sendType = type;
       if (type !== 2) {
         this.form.sendCost = '';
+        this.deliveryQuotes = [];
+        this.selectedDeliveryIndex = -1;
+        this.selectedDeliveryData = null;
+      } else {
+        if (this.openIntraCity == 1 && this.deliveryQuotes.length == 0) {
+          this.getDeliveryQuotes();
+        }
       }
     },
     diffPriceConfirm() {
@@ -875,9 +959,21 @@ export default {
       }
 
       let Fn = createOrder;
-      let book = this.option.book ? this.option.book : 0;
+      let book = this.book;
       let params = { ...formData, modifyPrice: this.modifyPrice, newVersion: 1, book: book };
-      if (this.option.orderId && this.option.orderId > 0) {
+
+      // 如果选择了跑腿配送并且有选中的平台数据,则添加配送平台信息
+      if (this.form.sendType == 2 && this.selectedDeliveryData) {
+        params.deliveryPlatform = this.selectedDeliveryData.en_name; // 平台名称
+        params.deliveryPrice = this.selectedDeliveryData.priceNumber; // 报价金额
+        params.deliveryDistance = this.showDistance; // 配送距离
+        params.deliveryBracketContent = this.selectedDeliveryData.bracketContent; // 配送平台括号里的内容
+        if (this.form.callErrand == 1) {
+          params.pickupTime = this.pickupTime.value;
+        }
+      }
+      
+      if (this.option && this.option.orderId && this.option.orderId > 0) {
         Fn = updateOrder;
         params = { ...params, id: this.option.orderId };
       }
@@ -894,7 +990,10 @@ export default {
       }
 
       //多颜色
-      let xj = uni.getStorageSync('xj' + this.option.customId);
+      let xj = null;
+      if (this.option && this.option.customId) {
+        xj = uni.getStorageSync('xj' + this.option.customId);
+      }
       params = { ...params, xj: JSON.stringify(xj) };
 
       uni.showLoading({ mask: true });
@@ -928,14 +1027,11 @@ export default {
       this.$refs.diffPriceRef.close();
     },
     gobackEvent() {
-      let book = this.option.book ? this.option.book : 0;
-      let customId = this.option.customId ? this.option.customId : 0;
-      let customName = this.option.customName ? this.option.customName : '';
+      let book = this.option && this.option.book ? this.option.book : 0;
+      let customId = this.option && this.option.customId ? this.option.customId : 0;
+      let customName = this.option && this.option.customName ? this.option.customName : '';
       this.$util.pageTo({ url: '/admin/billing/index2?customId=' + customId + '&customName=' + customName + '&book=' + book, type: 2 });
     },
-    pageToItemList() {
-      this.$util.pageTo({ url: '/admin/billing/index2?customId=' + this.option.customId, type: 2 });
-    },
     changeStaff() {
       this.$refs.staffRef.open('center');
     },
@@ -952,6 +1048,106 @@ export default {
       this.selectStaff.id = '';
       this.selectStaff.name = '';
       this.$refs.staffRef.close();
+    },
+    checkMapRule(type) {
+      const distanceInMeters = Number(this.showDistance) || 0;
+      const freeKm = type === 'hc' ? this.hcFreeKm : 0; // 只处理花材
+      const freeKmInMeters = (Number(freeKm) || 0) * 1000;
+      if (distanceInMeters < freeKmInMeters) {
+        return true;
+      }
+      let map = type === 'hc' ? this.hcMap : [];
+      if (map && Array.isArray(map) && map.length > 0) {
+        const flowerCount = this.totalItemNum.big || 0; // 使用 big 扎数
+        const flowerPrice = this.allPrice || 0;
+        for (const rule of map) {
+          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;
+    },
+    isHcMapRule() {
+      return this.checkMapRule('hc');
+    },
+    getDeliveryQuotes() {
+      this.deliveryQuotes = [];
+      this.selectedDeliveryIndex = -1;
+      this.selectedDeliveryData = null;
+
+      quickOrderAllDeliveryQuotes({
+        totalPrice: this.allPrice,
+        weight: 1, //this.allCount.weight
+        customId: this.option.customId,
+        pickupTime: this.pickupTime.value
+      })
+        .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.indexOf('(') !== -1) {
+                  const parts = displayName.split('(');
+                  if (parts.length > 1) {
+                    displayName = parts[0].trim();
+                    bracketContent = parts[1].replace(')', '');
+                  }
+                }
+
+                //滴滴特殊处理,括号里的内容作为类型
+                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 {
+            uni.showToast({ title: '暂无跑腿可用', icon: 'none' });
+          }
+        })
+        .catch((err) => {
+          this.$msg('获取跑腿平台报价失败');
+          console.log('获取跑腿平台报价失败:', err);
+          this.deliveryQuotes = [];
+        });
+    },
+    selectDelivery(item, index) {
+      this.selectedDeliveryIndex = index;
+      this.selectedDeliveryData = item;
+      this.form.sendCost = item.priceNumber;
+      this.showDistance = item.distance || 0;
+    },
+    openTimePicker() {
+      this.$refs.timePicker.open();
+    },
+    handleTimeConfirm(time) {
+      this.pickupTime = time;
+      if (this.form.sendType == 2) {
+        this.getDeliveryQuotes();
+      }
     }
   }
 };
@@ -1019,14 +1215,6 @@ export default {
                   font-weight: bold;
                 }
               }
-              .item-type {
-                color: #999;
-                font-size: 24upx;
-              }
-              .item-count {
-                color: #666;
-                font-size: 24upx;
-              }
             }
 
             & .open-bx {
@@ -1036,18 +1224,6 @@ export default {
                 font-size: 50upx;
               }
 
-              .position {
-                display: block;
-                width: 70upx;
-                text-align: center;
-              }
-              .iconcachu {
-                margin: 0 6upx 0 20upx;
-                color: #ccc;
-                &.edit {
-                  color: #3385ff;
-                }
-              }
               & > .num {
                 display: inline-block;
                 width: 120upx;
@@ -1060,25 +1236,6 @@ export default {
                 padding: 3upx 0;
                 font-size: 25upx;
               }
-              .change-input {
-                width: 164upx;
-                height: 60upx;
-                line-height: 60upx;
-                text-align: center;
-                background: #ffffff;
-                border: 1upx solid #dddddd;
-                border-radius: 4upx;
-                font-size: 25upx;
-              }
-              .btn-box {
-                width: 100upx;
-                height: 60upx;
-                line-height: 60upx;
-                color: #ffffff;
-                background: #3385ff;
-                text-align: center;
-                border-radius: 10upx;
-              }
             }
           }
         }
@@ -1120,34 +1277,6 @@ export default {
       margin-bottom: 20upx;
       border-radius: 10upx;
       overflow: hidden;
-      .tab-box {
-        padding: 10upx 30upx 30upx;
-        .tab {
-          width: 49%;
-          height: 70upx;
-          color: #a2a2a2;
-          border: 1upx solid #dcdcdc;
-          border-radius: 10upx;
-          font-size: 28upx;
-          font-weight: 700;
-          &.active {
-            color: #ff4d4d;
-            border: 1upx solid #ff4d4d;
-          }
-        }
-      }
-      .member-wrap {
-        .member-det {
-          font-size: 24upx;
-          margin-left: 20upx;
-        }
-      }
-      .remark-wrap {
-        align-items: flex-start;
-        .remark {
-          height: 240upx;
-        }
-      }
     }
   }
   .under-bar {
@@ -1276,4 +1405,164 @@ export default {
     opacity: 0;
   }
 }
+
+/* 跑腿平台报价样式 */
+.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: #3385ff;
+    border-color: #3385ff;
+    
+    .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-section {
+    border: 1upx solid #eee;
+    margin: 8upx 20upx;
+    border-radius: 8upx;
+}
+.empty-quotes-view {
+    text-align: center;
+    padding: 20upx 0;
+}
+.distance-box {
+    display: flex;
+    align-items: center;
+}
+.blue-text {
+    color: #3385FF;
+}
+.title-reduction {
+    width: 150upx;
+}
+.btn-reduction {
+    margin-right: 12upx;
+    height: 70upx;
+    line-height: 46upx;
+    &.active {
+       background-color: #3385ff !important;
+       color: #fff !important;
+       border: 1upx solid #3385ff !important;
+    }
+}
+.strike-through {
+    text-decoration: line-through;
+}
+.free-delivery-tag {
+    color: #3385FF;
+    margin-left: 5upx;
+}
+.btn-call {
+    width: 170upx;
+    &.active {
+       background-color: #3385ff !important;
+       color: #fff !important;
+       border: 1upx solid #3385ff !important;
+    }
+}
+.time-action-view {
+    display: flex;
+    align-items: center;
+    color: #3385ff;
+    font-size: 28upx;
+}
+.icon-arrow-right {
+    font-size: 24upx;
+    color: #3385ff;
+    margin-left: 5upx;
+}
 </style>

+ 7 - 0
ghsApp/src/api/express/delivery.js

@@ -14,6 +14,13 @@ export const getDeliveryQuotes = data => {
 	return https.post('/delivery/all-delivery-quotes', data) //?XDEBUG_SESSION_START=PHPSTORM
 }
 
+/**
+ * 批发快捷开单 -- 获取跑腿配送报价
+ */
+export const quickOrderAllDeliveryQuotes = data => {
+	return https.post('/delivery/quick-order-delivery-quotes', data)
+}
+
 /**
  * 创建配送单
  */

+ 295 - 0
ghsApp/src/components/plugin/TimePicker.vue

@@ -0,0 +1,295 @@
+<template>
+  <uni-popup ref="popup" type="bottom" :safe-area="false">
+    <view class="time-picker-container">
+      <view class="picker-header">
+        <text class="title">期望取件时间</text>
+        <view class="close-btn" @click="close">
+          <zui-svg-icon icon="general-close" :width="18" :height="18" color="#666" />
+        </view>
+      </view>
+
+      <picker-view v-if="visible" class="picker-view" :indicator-style="indicatorStyle" :value="pickerValue" @change="handleChange" :immediate-change="true">
+        <picker-view-column>
+          <view class="picker-item" v-for="(item, index) in days" :key="index">{{ item.label }}</view>
+        </picker-view-column>
+        <picker-view-column>
+          <view class="picker-item" v-for="(item, index) in hours" :key="index">{{ item.label }}</view>
+        </picker-view-column>
+        <picker-view-column>
+          <view class="picker-item" v-for="(item, index) in minutes" :key="index">{{ item.label }}</view>
+        </picker-view-column>
+      </picker-view>
+
+      <view class="confirm-button-wrapper">
+        <button class="confirm-btn" @click="handleConfirm">确认</button>
+      </view>
+    </view>
+  </uni-popup>
+</template>
+
+<script>
+import dayjs from 'dayjs';
+
+const minutes = [0, 10, 20, 30, 40, 50];
+
+export default {
+  name: 'TimePicker',
+  data() {
+    return {
+      visible: false, // 控制picker-view的渲染
+      pickerValue: [0, 0, 0],
+      tempPickerValue: [0, 0, 0], // 临时存储滚动过程中的值
+      debounceTimer: null,
+      days: [],
+      hours: [],
+      minutes: [],
+      indicatorStyle: `height: 80rpx;`,
+    };
+  },
+  created() {
+    this.initTime();
+  },
+  methods: {
+    open() {
+      this.visible = true;
+      this.initTime();
+      this.$refs.popup.open();
+    },
+
+    close() {
+      this.visible = false;
+      this.$refs.popup.close();
+    },
+
+    initTime() {
+      const now = dayjs(); // Use current time
+      this.days = this.generateDays();
+
+      const isToday = true;
+      let availableHoursToday = this.generateHours(now, isToday);
+
+      // If no hours available today, switch to tomorrow
+      if (availableHoursToday.length <= 1) { // Only "立即"
+        const tomorrow = dayjs().add(1, 'day').startOf('day');
+        this.days = this.generateDays(); // Regenerate days if needed
+        this.hours = this.generateHours(tomorrow, false);
+        this.minutes = this.generateMinutes(tomorrow, false, this.hours[0].value);
+        this.pickerValue = [1, 0, 0]; // Select "Tomorrow", first hour, first minute
+        this.tempPickerValue = [1, 0, 0];
+        return;
+      }
+
+      this.hours = availableHoursToday;
+
+      // Default to "立即"
+      const initialHourIndex = 0;
+      const selectedHour = this.hours[initialHourIndex].value;
+      this.minutes = this.generateMinutes(now, isToday, selectedHour);
+      const initialMinuteIndex = 0;
+      
+      this.pickerValue = [0, initialHourIndex, initialMinuteIndex];
+      this.tempPickerValue = [0, initialHourIndex, initialMinuteIndex];
+    },
+
+    generateDays() {
+      return [
+        { label: '今天', value: dayjs().format('YYYY-MM-DD') },
+        { label: '明天', value: dayjs().add(1, 'day').format('YYYY-MM-DD') },
+      ];
+    },
+
+    generateHours(now, isToday) {
+      const hours = [];
+      if (isToday) {
+        hours.push({ label: '立即', value: -1 });
+
+        let startHour = now.hour() + 1; // 当前小时的下一个小时
+        // Find if any minutes are available in the current hour
+        const firstAvailableMinute = minutes.find(m => m > now.minute());
+        
+        // If no minutes available in current hour, or if the last minute has passed, start from the next hour
+        if (firstAvailableMinute === undefined) {
+          startHour++;
+        }
+        
+        for (let i = startHour; i <= 23; i++) {
+          hours.push({ label: `${i}时`, value: i });
+        }
+      } else {
+        for (let i = 0; i <= 23; i++) {
+          hours.push({ label: `${i}时`, value: i });
+        }
+      }
+      return hours;
+    },
+
+    generateMinutes(now, isToday, selectedHour) {
+      if (selectedHour === -1) {
+        return [{ label: '', value: -1 }];
+      }
+      
+      let availableMinutes = minutes;
+      
+      if (isToday && selectedHour === now.hour()) {
+        const currentMinute = now.minute();
+        availableMinutes = minutes.filter(m => m > currentMinute);
+      }
+      
+      return availableMinutes.map(m => ({ label: `${m}分`, value: m }));
+    },
+
+    handleChange(e) {
+      const val = e.detail.value;
+      this.tempPickerValue = val;
+      
+      if (this.debounceTimer) {
+        clearTimeout(this.debounceTimer);
+      }
+      
+      this.debounceTimer = setTimeout(() => {
+        this.updatePickers(val);
+      }, 100);
+    },
+
+    updatePickers(val, isSync = false) {
+      const [dayIndex, hourIndex, minuteIndex] = val;
+      const oldPickerValue = this.pickerValue;
+      
+      const now = dayjs();
+      const isToday = dayIndex === 0;
+
+      let newHours = this.hours;
+      let newMinutes = this.minutes;
+      
+      let finalHourIndex = hourIndex;
+      let finalMinuteIndex = minuteIndex;
+
+      // Day changed
+      if (oldPickerValue[0] !== dayIndex) {
+        newHours = this.generateHours(now, isToday);
+        // 修正越界,尽量保持用户的选择,而不是强制归零
+        if (finalHourIndex >= newHours.length) {
+            finalHourIndex = 0;
+        }
+      }
+      
+      // Calculate selected hour based on current indices
+      const selectedHour = newHours[finalHourIndex] ? newHours[finalHourIndex].value : -1;
+
+      // Always regenerate minutes to ensure correctness (e.g. today/tomorrow switch, or hour switch)
+      // Optimization: we could check if generation parameters actually changed, but it's fast enough.
+      newMinutes = this.generateMinutes(now, isToday, selectedHour);
+      
+      // 修正越界
+      if (finalMinuteIndex >= newMinutes.length) {
+          finalMinuteIndex = 0;
+      }
+      
+      this.hours = newHours;
+      this.minutes = newMinutes;
+      this.debounceTimer = null;
+
+      const newValue = [dayIndex, finalHourIndex, finalMinuteIndex];
+      
+      if (isSync) {
+        this.pickerValue = newValue;
+      } else {
+        // Use setTimeout to avoid visual glitch on picker reset
+        setTimeout(() => {
+            this.pickerValue = newValue;
+        }, 0);
+      }
+    },
+
+    handleConfirm() {
+      if (this.debounceTimer) {
+        clearTimeout(this.debounceTimer);
+        this.updatePickers(this.tempPickerValue, true);
+      }
+      
+      const [dayIndex, hourIndex, minuteIndex] = this.pickerValue;
+      const selectedDay = this.days[dayIndex];
+      const selectedHour = this.hours[hourIndex];
+      const selectedMinute = this.minutes[minuteIndex];
+
+      let result = {};
+      if (selectedHour && selectedHour.value === -1) {
+        result = {
+          label: '立即取件',
+          value: dayjs().format('YYYY-MM-DD HH:mm'),
+        };
+      } else {
+        const date = selectedDay.value;
+        const hour = selectedHour?.value ?? 0;
+        const minute = selectedMinute?.value ?? 0;
+        const finalDate = dayjs(`${date} ${hour}:${minute}`).toDate();
+        result = {
+          label: dayjs(finalDate).format('YYYY-MM-DD HH:mm'),
+          value: dayjs(finalDate).format('YYYY-MM-DD HH:mm'),
+        };
+      }
+
+      this.$emit('confirm', result);
+      this.close();
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.time-picker-container {
+  background-color: #fff;
+  border-top-left-radius: 24rpx;
+  border-top-right-radius: 24rpx;
+  height: 45vh;
+  display: flex;
+  flex-direction: column;
+}
+.picker-header {
+  position: relative;
+  text-align: center;
+  padding: 30rpx;
+  flex-shrink: 0;
+
+  .title {
+    font-size: 44rpx;
+    font-weight: 500;
+  }
+
+  .close-btn {
+    position: absolute;
+    right: 30rpx;
+    top: 50%;
+    transform: translateY(-50%);
+    padding: 10rpx;
+  }
+}
+.picker-view {
+  width: 100%;
+  height: 100%;
+}
+.picker-item {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 42rpx;
+}
+.confirm-button-wrapper {
+  padding: 20rpx 30rpx;
+  padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
+  padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
+  flex-shrink: 0;
+}
+.confirm-btn {
+  background: linear-gradient(to right, #409eff, #67c2ff);
+  color: white;
+  border-radius: 50rpx;
+  font-size: 42rpx;
+  height: 88rpx;
+  line-height: 88rpx;
+  border: none;
+  &::after {
+    border: none;
+  }
+}
+</style>

+ 8 - 0
ghsPad/src/api/delivery/index.js

@@ -0,0 +1,8 @@
+import https from '@/plugins/luch-request_0.0.7/request'
+
+/**
+ * 收银快捷开单 -- 获取跑腿配送报价
+ */
+export const quickOrderAllDeliveryQuotes = data => {
+	return https.post('/delivery/quick-order-delivery-quotes', data)
+}

+ 3 - 0
ghsPad/src/api/merchant/index.js

@@ -0,0 +1,3 @@
+import https from "@/plugins/luch-request_0.0.7/request";
+// 商家信息
+export const merchantDetail = param => https.get("/merchant/detail", param);

+ 295 - 0
ghsPad/src/components/TimePicker.vue

@@ -0,0 +1,295 @@
+<template>
+  <uni-popup ref="popup" type="center" :safe-area="false">
+    <view class="time-picker-container">
+      <view class="picker-header">
+        <text class="title">期望取件时间</text>
+        <view class="close-btn" @click="close">
+          <zui-svg-icon icon="general-close" :width="18" :height="18" color="#666" />
+        </view>
+      </view>
+
+      <picker-view v-if="visible" class="picker-view" :indicator-style="indicatorStyle" :value="pickerValue" @change="handleChange" :immediate-change="true">
+        <picker-view-column>
+          <view class="picker-item" v-for="(item, index) in days" :key="index">{{ item.label }}</view>
+        </picker-view-column>
+        <picker-view-column>
+          <view class="picker-item" v-for="(item, index) in hours" :key="index">{{ item.label }}</view>
+        </picker-view-column>
+        <picker-view-column>
+          <view class="picker-item" v-for="(item, index) in minutes" :key="index">{{ item.label }}</view>
+        </picker-view-column>
+      </picker-view>
+
+      <view class="confirm-button-wrapper">
+        <button class="confirm-btn" @click="handleConfirm">确认</button>
+      </view>
+    </view>
+  </uni-popup>
+</template>
+
+<script>
+import dayjs from 'dayjs';
+
+const minutes = [0, 10, 20, 30, 40, 50];
+
+export default {
+  name: 'TimePicker',
+  data() {
+    return {
+      visible: false, // 控制picker-view的渲染
+      pickerValue: [0, 0, 0],
+      tempPickerValue: [0, 0, 0], // 临时存储滚动过程中的值
+      debounceTimer: null,
+      days: [],
+      hours: [],
+      minutes: [],
+      indicatorStyle: `height: 70rpx;`,
+    };
+  },
+  created() {
+    this.initTime();
+  },
+  methods: {
+    open() {
+      this.visible = true;
+      this.initTime();
+      this.$refs.popup.open();
+    },
+
+    close() {
+      this.visible = false;
+      this.$refs.popup.close();
+    },
+
+    initTime() {
+      const now = dayjs(); // Use current time
+      this.days = this.generateDays();
+
+      const isToday = true;
+      let availableHoursToday = this.generateHours(now, isToday);
+
+      // If no hours available today, switch to tomorrow
+      if (availableHoursToday.length <= 1) { // Only "立即"
+        const tomorrow = dayjs().add(1, 'day').startOf('day');
+        this.days = this.generateDays(); // Regenerate days if needed
+        this.hours = this.generateHours(tomorrow, false);
+        this.minutes = this.generateMinutes(tomorrow, false, this.hours[0].value);
+        this.pickerValue = [1, 0, 0]; // Select "Tomorrow", first hour, first minute
+        this.tempPickerValue = [1, 0, 0];
+        return;
+      }
+
+      this.hours = availableHoursToday;
+
+      // Default to "立即"
+      const initialHourIndex = 0;
+      const selectedHour = this.hours[initialHourIndex].value;
+      this.minutes = this.generateMinutes(now, isToday, selectedHour);
+      const initialMinuteIndex = 0;
+      
+      this.pickerValue = [0, initialHourIndex, initialMinuteIndex];
+      this.tempPickerValue = [0, initialHourIndex, initialMinuteIndex];
+    },
+
+    generateDays() {
+      return [
+        { label: '今天', value: dayjs().format('YYYY-MM-DD') },
+        { label: '明天', value: dayjs().add(1, 'day').format('YYYY-MM-DD') },
+      ];
+    },
+
+    generateHours(now, isToday) {
+      const hours = [];
+      if (isToday) {
+        hours.push({ label: '立即', value: -1 });
+
+        let startHour = now.hour() + 1; // 当前小时的下一个小时
+        // Find if any minutes are available in the current hour
+        const firstAvailableMinute = minutes.find(m => m > now.minute());
+        
+        // If no minutes available in current hour, or if the last minute has passed, start from the next hour
+        if (firstAvailableMinute === undefined) {
+          startHour++;
+        }
+        
+        for (let i = startHour; i <= 23; i++) {
+          hours.push({ label: `${i}时`, value: i });
+        }
+      } else {
+        for (let i = 0; i <= 23; i++) {
+          hours.push({ label: `${i}时`, value: i });
+        }
+      }
+      return hours;
+    },
+
+    generateMinutes(now, isToday, selectedHour) {
+      if (selectedHour === -1) {
+        return [{ label: '', value: -1 }];
+      }
+      
+      let availableMinutes = minutes;
+      
+      if (isToday && selectedHour === now.hour()) {
+        const currentMinute = now.minute();
+        availableMinutes = minutes.filter(m => m > currentMinute);
+      }
+      
+      return availableMinutes.map(m => ({ label: `${m}分`, value: m }));
+    },
+
+    handleChange(e) {
+      const val = e.detail.value;
+      this.tempPickerValue = val;
+      
+      if (this.debounceTimer) {
+        clearTimeout(this.debounceTimer);
+      }
+      
+      this.debounceTimer = setTimeout(() => {
+        this.updatePickers(val);
+      }, 100);
+    },
+
+    updatePickers(val, isSync = false) {
+      const [dayIndex, hourIndex, minuteIndex] = val;
+      const oldPickerValue = this.pickerValue;
+      
+      const now = dayjs();
+      const isToday = dayIndex === 0;
+
+      let newHours = this.hours;
+      let newMinutes = this.minutes;
+      
+      let finalHourIndex = hourIndex;
+      let finalMinuteIndex = minuteIndex;
+
+      // Day changed
+      if (oldPickerValue[0] !== dayIndex) {
+        newHours = this.generateHours(now, isToday);
+        // 修正越界,尽量保持用户的选择,而不是强制归零
+        if (finalHourIndex >= newHours.length) {
+            finalHourIndex = 0;
+        }
+      }
+      
+      // Calculate selected hour based on current indices
+      const selectedHour = newHours[finalHourIndex] ? newHours[finalHourIndex].value : -1;
+
+      // Always regenerate minutes to ensure correctness (e.g. today/tomorrow switch, or hour switch)
+      // Optimization: we could check if generation parameters actually changed, but it's fast enough.
+      newMinutes = this.generateMinutes(now, isToday, selectedHour);
+      
+      // 修正越界
+      if (finalMinuteIndex >= newMinutes.length) {
+          finalMinuteIndex = 0;
+      }
+      
+      this.hours = newHours;
+      this.minutes = newMinutes;
+      this.debounceTimer = null;
+
+      const newValue = [dayIndex, finalHourIndex, finalMinuteIndex];
+      
+      if (isSync) {
+        this.pickerValue = newValue;
+      } else {
+        // Use setTimeout to avoid visual glitch on picker reset
+        setTimeout(() => {
+            this.pickerValue = newValue;
+        }, 0);
+      }
+    },
+
+    handleConfirm() {
+      if (this.debounceTimer) {
+        clearTimeout(this.debounceTimer);
+        this.updatePickers(this.tempPickerValue, true);
+      }
+      
+      const [dayIndex, hourIndex, minuteIndex] = this.pickerValue;
+      const selectedDay = this.days[dayIndex];
+      const selectedHour = this.hours[hourIndex];
+      const selectedMinute = this.minutes[minuteIndex];
+
+      let result = {};
+      if (selectedHour && selectedHour.value === -1) {
+        result = {
+          label: '立即取件',
+          value: dayjs().format('YYYY-MM-DD HH:mm'),
+        };
+      } else {
+        const date = selectedDay.value;
+        const hour = selectedHour?.value ?? 0;
+        const minute = selectedMinute?.value ?? 0;
+        const finalDate = dayjs(`${date} ${hour}:${minute}`).toDate();
+        result = {
+          label: dayjs(finalDate).format('YYYY-MM-DD HH:mm'),
+          value: dayjs(finalDate).format('YYYY-MM-DD HH:mm'),
+        };
+      }
+
+      this.$emit('confirm', result);
+      this.close();
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.time-picker-container {
+  background-color: #fff;
+  border-radius: 20rpx;
+  width: 300rpx;
+  height: 400rpx;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+.picker-header {
+  position: relative;
+  text-align: center;
+  padding: 20rpx;
+  flex-shrink: 0;
+
+  .title {
+    font-size: 26rpx;
+    font-weight: 350;
+  }
+
+  .close-btn {
+    position: absolute;
+    right: 24rpx;
+    top: 50%;
+    transform: translateY(-50%);
+    padding: 10rpx;
+  }
+}
+.picker-view {
+  width: 100%;
+  height: 100%;
+}
+.picker-item {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 22rpx;
+}
+.confirm-button-wrapper {
+  padding: 15rpx 20rpx;
+  padding-bottom: 25rpx;
+  flex-shrink: 0;
+}
+.confirm-btn {
+  background: linear-gradient(to right, #409eff, #67c2ff);
+  color: white;
+  border-radius: 50rpx;
+  font-size: 24rpx;
+  height: 50rpx;
+  line-height: 50rpx;
+  border: none;
+  &::after {
+    border: none;
+  }
+}
+</style>

+ 12 - 3
ghsPad/src/pages/home/components/console.vue

@@ -27,8 +27,8 @@
     </uni-popup>
 
     <!-- 结算弹框 -->
-    <uni-popup ref="settlePopRef" background-color="#fff" type="center" :animation="false">
-        <settlePop @settleCommit="settleCommit" @cancelSettle="cancelSettle" ref="settlePop" :settleOpenStatus.sync="settleOpenStatus"></settlePop>
+    <uni-popup ref="settlePopRef" background-color="#fff" type="center" :animation="false" @change="settlePopChange">
+        <settlePop @settleCommit="settleCommit" @cancelSettle="cancelSettle" ref="settlePop" :settleOpenStatus.sync="settleOpenStatus" :customId.sync="customId"></settlePop>
     </uni-popup>
 
     <!-- 扫码付款 -->
@@ -378,7 +378,16 @@ export default {
 		},
 		delSelectItem(){
 			this.$util.hitVoice()
-		}
+		},
+		/**
+		 * 结算弹框变化事件
+		 * @param e 结算弹框变化事件
+		 */
+        settlePopChange(e){
+            if(!e.show){
+                this.settleOpenStatus = 0
+            }
+        }
     }
 }
 </script>

+ 390 - 4
ghsPad/src/pages/home/components/settlePop.vue

@@ -16,6 +16,85 @@
                 </view>
             </view>
 
+            <view class="select-item_box" v-if="form.sendType == 2">
+                <view v-if="openIntraCity == 1" class="delivery-section">
+                    
+                    <!-- 跑腿平台报价列表 -->
+                    <view class="select-item_box" v-if="deliveryQuotes.length > 0">
+                        <!-- <view class="title">选择跑腿</view> -->
+                        <scroll-view scroll-y class="delivery-quotes-scroll">
+                            <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>
+                        </scroll-view>
+                    </view>
+                    <view v-else class="empty-quotes-view">
+                      <text style="font-size:12upx;">正在获取运费...</text>
+                    </view>
+
+                    <view class="select-item_box">
+                        <view class="title">
+                             <text>运费:</text>
+                             <text v-if="form.reductionRule == 0" class="blue-text">¥{{ form.sendCost }}</text>
+                             <text v-if="form.reductionRule == 1" class="free-delivery-tag">
+                               <text v-if="isHcMapRule()"><text class="strike-through">¥{{ form.sendCost }}</text>【花材免跑腿费】</text>
+                               <text v-else>¥{{ form.sendCost }}</text>
+                             </text>
+                             <text style="margin-left: 40upx;">距离:</text>
+                             <text v-if="showDistance > 100" class="blue-text">{{ (showDistance/1000).toFixed(1) }}公里</text>
+                             <text v-else class="blue-text">{{ showDistance }} 米</text>
+                        </view>
+                    </view>
+
+                    <view class="select-item_box">
+                        <view class="title">满减规则</view>
+                        <view class="item-list_box">
+                            <view @tap="form.reductionRule = 0" :class="[form.reductionRule == 0 ? 'active' : '']">没有满减</view>
+                            <view @tap="form.reductionRule = 1" :class="[form.reductionRule == 1 ? 'active' : '']">用花材</view>
+                        </view>
+                    </view>
+
+                    
+
+                    <view class="select-item_box">
+                        <view class="title">呼叫跑腿</view>
+                        <view class="item-list_box">
+                            <view @tap="form.callErrand = 0" :class="[form.callErrand == 0 ? 'active' : '']">晚点呼叫</view>
+                            <view @tap="form.callErrand = 1" :class="[form.callErrand == 1 ? 'active' : '']">现在呼叫</view>
+                        </view>
+                    </view>
+
+                    <view class="select-item_box" v-if="form.callErrand == 1">
+                        <view class="title" @click.stop="openTimePicker">
+                            取件时间:<text class="blue-text">{{ pickupTime.label || '立即取件' }}</text>
+                        </view>
+                    </view>
+
+                </view>
+                <view v-else class="delivery-section">
+                    <view class="select-item_box">
+                        <view class="title">运费</view>
+                        <view class="inpup_box">
+                            <view><input type="digit" placeholder="请填写运费" v-model="form.sendCost" placeholder-class="tui-placeholder"/></view>
+                        </view>
+                    </view>
+                </view>
+            </view>
+
             <view class="select-item_box" v-if="form.hasPay == 1">
                 <view class="title">请选择</view>
                 <view class="item-list_box">
@@ -122,13 +201,19 @@
             </view>
         </uni-popup>
 
+        <TimePicker ref="timePicker" @confirm="handleTimeConfirm" />
+
     </view>
 </template>
 <script>
 import VKeyboard from '@/components/vKeyboard/VKeyboard.vue'
+import TimePicker from '@/components/TimePicker';
 import productMins from "@/mixins/product"
 import { getAllStaff} from '@/api/shop-admin'
+import { quickOrderAllDeliveryQuotes } from '@/api/delivery'
+import { merchantDetail } from '@/api/merchant'
 import { mapGetters } from "vuex"
+import dayjs from 'dayjs';
 export default {
     name:'selectCustomer',
     data() {
@@ -153,7 +238,9 @@ export default {
                 shopAdminId:0,
                 shopAdminName:'',
                 cash:0,
-                dealPrice:0
+                dealPrice:0,
+                reductionRule: 0,
+                callErrand: 0 //现在呼叫 0 晚点呼叫 1
             },
             proceeds: '0',
             sendTypeList:[{name:'自取',id:1},{name:'出车',id:0},{name:'跑腿',id:2}],
@@ -167,7 +254,18 @@ export default {
             diffData:[],
             discountList:[{name:'1折',value:0.1},{name:'2折',value:0.2},{name:'3折',value:0.3},{name:'4折',value:0.4},{name:'5折',value:0.5},{name:'6折',value:0.6},{name:'7折',value:0.7},{name:'8折',value:0.8},{name:'9折',value:0.9},{name:'不打折',value:1}],
             discountValue:0,
-            showDiscount:false
+            showDiscount:false,
+            deliveryQuotes: [], // 跑腿平台报价列表
+            selectedDeliveryIndex: -1,
+            selectedDeliveryData: null,
+            showDistance: 0,
+            pickupTime: {
+                label: '立即取件',
+                value: ''
+            },
+            openIntraCity: 0,
+            hcFreeKm: 0,
+            hcMap: [],
         };
     },
     props:{
@@ -182,9 +280,13 @@ export default {
         settleOpenStatus:{
             type:Number,
             default:0
+        },
+        customId:{
+            type:Number,
+            default:0
         }
     },
-    components:{ VKeyboard },
+    components:{ VKeyboard, TimePicker },
 	mixins: [productMins],
     computed:{
         ...mapGetters(["getLoginInfo"]),
@@ -214,6 +316,14 @@ export default {
         }
     },
     watch:{
+        totalShop: {
+            handler(val) {
+                if (val) {
+                }
+            },
+            immediate: true,
+            deep: true
+        },
         settleOpenStatus:{
             handler(val){
                 if(Number(val) == 1){
@@ -227,6 +337,24 @@ export default {
                         this.form.needPrint = 2
                     }
 
+                    // 重置商户配置,避免显示旧数据
+                    this.openIntraCity = 0;
+                    this.hcFreeKm = 0;
+                    this.hcMap = [];
+
+                    merchantDetail().then(res => {
+                        if(res.code == 1){
+                            // this.currentShop = res.data.shopInfo;
+                            this.openIntraCity = res.data.openIntraCity;
+                            this.hcFreeKm = res.data.hcFreeKm;
+                            try {
+                                this.hcMap = typeof res.data.hcMap === 'string' ? JSON.parse(res.data.hcMap) : res.data.hcMap;
+                            } catch (e) {
+                                this.hcMap = [];
+                            }
+                        }
+                    });
+
                     this.form.groupName = ''
                     this.form.remark = ''
                     this.form.sendCost = 0
@@ -237,6 +365,9 @@ export default {
         }
     },
     mounted() {
+        // Initialize pickupTime
+        this.pickupTime.value = dayjs().format('YYYY-MM-DD HH:mm');
+
         this.activeKeyboard()
 
         getAllStaff().then(res=>{
@@ -267,7 +398,6 @@ export default {
                 }
             }
         })
-
     },
     methods: {
         viewDiscount(){
@@ -315,6 +445,15 @@ export default {
         },
         changeSendType(val){
             this.form.sendType = val
+            if (val !== 2) {
+                this.deliveryQuotes = [];
+                this.selectedDeliveryIndex = -1;
+                this.selectedDeliveryData = null;
+            } else {
+                if (this.openIntraCity == 1) {
+                    this.getDeliveryQuotes();
+                }
+            }
         },
         changeHasPay(val){
             this.$util.hitVoice()
@@ -418,8 +557,131 @@ export default {
                     return false
                 }
             }
+
+            // 跑腿信息
+            if (this.form.sendType == 2 && this.selectedDeliveryData) {
+                this.form.deliveryPlatform = this.selectedDeliveryData.en_name;
+                this.form.deliveryPrice = this.selectedDeliveryData.priceNumber;
+                this.form.deliveryDistance = this.showDistance;
+                this.form.deliveryBracketContent = this.selectedDeliveryData.bracketContent;
+                if (this.form.callErrand == 1) {
+                    this.form.pickupTime = this.pickupTime.value;
+                }
+            }
+
             this.$emit('settleCommit',this.form)
         },
+        checkMapRule(type) {
+            const distanceInMeters = Number(this.showDistance) || 0;
+            const freeKm = type === 'hc' ? this.hcFreeKm : 0; // 只处理花材
+            const freeKmInMeters = (Number(freeKm) || 0) * 1000;
+            if (distanceInMeters < freeKmInMeters) {
+                return true;
+            }
+            let map = type === 'hc' ? this.hcMap : [];
+            if (map && Array.isArray(map) && map.length > 0) {
+                let big = 0;
+                if (!this.$util.isEmpty(this.selectList)) {
+                    for (const ele of this.selectList) {
+                        if (Number(ele.bigCount) > 0) {
+                            big = Number(big) + Number(ele.bigCount);
+                        }
+                    }
+                }
+                const flowerCount = big || 0; 
+                const flowerPrice = this.allPrice || 0;
+                for (const rule of map) {
+                    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;
+        },
+        isHcMapRule() {
+            return this.checkMapRule('hc');
+        },
+        getDeliveryQuotes() {
+            console.log('getDeliveryQuotes', Number(this.customId));
+            if(Number(this.customId) === 0){
+                this.$msg('请先选择客户');
+                return false;
+            }
+            this.deliveryQuotes = [];
+            this.selectedDeliveryIndex = -1;
+            this.selectedDeliveryData = null;
+
+            quickOrderAllDeliveryQuotes({
+                totalPrice: this.allPrice,
+                weight: 1,
+                customId: this.customId, 
+                pickupTime: this.pickupTime.value
+            })
+            .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.indexOf('(') !== -1) {
+                                const parts = displayName.split('(');
+                                if (parts.length > 1) {
+                                    displayName = parts[0].trim();
+                                    bracketContent = parts[1].replace(')', '');
+                                }
+                            }
+                            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 {
+                    uni.showToast({ title: res.msg || '暂无跑腿可用', icon: 'none' });
+                    this.form.sendType = 1; // 切换回自取
+                    this.deliveryQuotes = [];
+                }
+            }).catch((err) => {
+                console.log('获取跑腿平台报价失败:', err);
+                if(err.code == 0){
+                    uni.showToast({ title: err.msg || '获取跑腿平台报价失败', icon: 'none', duration: 2500});
+                } else {
+                    uni.showToast({ title: '获取跑腿平台报价失败', icon: 'none' });
+                }
+                this.form.sendType = 1; // 切换回自取
+                this.deliveryQuotes = [];
+            });
+        },
+        selectDelivery(item, index) {
+            this.selectedDeliveryIndex = index;
+            this.selectedDeliveryData = item;
+            this.form.sendCost = item.priceNumber;
+            this.showDistance = item.distance || 0;
+        },
+        openTimePicker() {
+            this.$refs.timePicker.open();
+        },
+        handleTimeConfirm(time) {
+            this.pickupTime = time;
+            if (this.form.sendType == 2) {
+                this.getDeliveryQuotes();
+            }
+        },
         cancel(){
             this.$emit('cancelSettle')
         }
@@ -531,4 +793,128 @@ export default {
         z-index:99;
     }
 }
+
+/* 跑腿平台报价样式 */
+.delivery-quotes-container {
+  width: 100%;
+  margin-top: 5upx;
+}
+.delivery-quotes-scroll {
+  width: 100%;
+  max-height: 210upx;
+}
+.delivery-quotes-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4upx;
+  padding: 0;
+  width: 100%;
+}
+.delivery-quote-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  flex: 0 0 calc(33% - 4upx);
+  width: calc(33% - 4upx);
+  min-height: 50upx;
+  padding: 4upx;
+  margin-bottom: 2upx;
+  background: #FFFFFF;
+  border: 1px solid #DDDDDD;
+  border-radius: 4upx;
+  box-sizing: border-box;
+  transition: all 0.3s;
+  
+  &.fixed-width {
+    flex: 0 0 100upx;
+    width: 100upx;
+  }
+  
+  .platform-name {
+    font-size: 11upx;
+    color: #333;
+    font-weight: bold;
+    margin-bottom: 2upx;
+    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: 13upx;
+    color: #333;
+    font-weight: bold;
+    margin-bottom: 1upx;
+  }
+  
+  .platform-type {
+    font-size: 9upx;
+    color: #868686;
+    margin-top: 1upx;
+    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: #3385ff;
+    border-color: #3385ff;
+    
+    .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-section {
+    border: 1px solid #eee;
+    margin: 5upx 0;
+    border-radius: 4upx;
+    padding: 5upx;
+}
+.empty-quotes-view {
+    text-align: center;
+    padding: 10upx 0;
+}
+.blue-text {
+    color: #3385FF;
+}
+.strike-through {
+    text-decoration: line-through;
+}
+.free-delivery-tag {
+    color: #3385FF;
+    margin-left: 5upx;
+}
 </style>

+ 519 - 48
hdApp/src/admin/billing/affirm.vue

@@ -155,10 +155,80 @@
             <button @click="form.getGoods = 0" class="admin-button-com middle" style="width:140upx;" :class="form.getGoods == 0 ? 'blue' : 'default'">未取货</button>
           </tui-list-cell>
 
-          <tui-list-cell class="line-cell" :hover="false" :arrow="false" v-if="form.sendType == 2">
-            <view class="tui-title">运费</view>
-            <input type="digit" placeholder="请填写运费" v-model="form.sendCost" placeholder-class="tui-placeholder"/>
-          </tui-list-cell>
+          <view v-if="form.sendType == 2">
+            <view v-if="openIntraCity == 1" class="delivery-section">
+              <tui-list-cell v-if="openIntraCity == 1" class="line-cell" :hover="false">
+                <!-- 跑腿平台报价列表 -->
+                <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 class="empty-quotes-view">
+                  <text>正在获取运费...</text>
+                  <!-- <text class="error-tips">上面地址缺失或有问题,请修改</text> -->
+                </view>
+              </tui-list-cell>
+              <tui-list-cell class="line-cell" :hover="false">
+                <view class="tui-title">距离</view>
+                <view class="distance-box">
+                  <text v-if="showDistance > 100" class="blue-text">{{ (showDistance/1000).toFixed(1) }}公里</text>
+						      <text v-else class="blue-text">{{ showDistance }} 米</text>
+                  <!-- <input type="digit" :value="showDistance/1000" placeholder-class="tui-placeholder" style="text-align:left;width:70upx;margin-right:10upx;"/>公里 -->
+                </view>
+              </tui-list-cell>
+              <tui-list-cell class="line-cell" :hover="false" :arrow="false">
+                <view class="tui-title title-reduction" style="width: 150upx;">满减规则</view>
+                <button @tap="form.reductionRule = 0" class="admin-button-com mini-btn btn-reduction" :class="form.reductionRule == 0 ? 'active' : 'default'">没有满减</button>
+                <button @tap="form.reductionRule = 1" class="admin-button-com mini-btn btn-reduction" :class="form.reductionRule == 1 ? 'active' : 'default'">用花材</button>
+                <button @tap="form.reductionRule = 2" class="admin-button-com mini-btn btn-reduction-last" :class="form.reductionRule == 2 ? 'active' : 'default'">用花束</button>
+              </tui-list-cell>
+              <tui-list-cell class="line-cell" :hover="false" :arrow="false">
+                <view class="tui-title">运费</view>
+                <text v-if="form.reductionRule == 0" class="blue-text">¥{{ form.sendCost }}</text>
+                <text v-if="form.reductionRule == 1" class="free-delivery-tag">
+                  <text v-if="isHcMapRule()"><text class="strike-through">¥{{ form.sendCost }}</text>【花材免跑腿费】</text>
+                  <text v-else>¥{{ form.sendCost }}</text>
+                </text>
+                <text v-if="form.reductionRule == 2" class="free-delivery-tag">
+                  <text v-if="isHsMapRule()"><text class="strike-through">¥{{ form.sendCost }}</text>【花束免跑腿费】</text>
+                  <text v-else>¥{{ form.sendCost }}</text>
+                </text>
+              </tui-list-cell>
+              <tui-list-cell class="line-cell" :hover="false" :arrow="false">
+                <view class="tui-title">呼叫跑腿</view>
+                <button @tap="form.callErrand = 0" class="admin-button-com middle btn-call" :class="form.callErrand == 0? 'active' : 'default'">晚点呼叫</button>
+                <button @tap="form.callErrand = 1" class="admin-button-com middle btn-call" :class="form.callErrand == 1 ? 'active' : 'default'">现在呼叫</button>
+              </tui-list-cell>
+              <tui-list-cell v-if="form.callErrand == 1" class="line-cell" :hover="false" :arrow="false">
+                <view class="tui-title">取件时间</view>
+                <view class="time-action-view" @click.stop="openTimePicker">
+                  <text>{{ pickupTime.label || '立即取件' }}</text>
+                  <text class="iconfont iconxiangyou icon-arrow-right"></text>
+                </view>
+              </tui-list-cell>
+            </view>
+            <view v-else class="delivery-section">
+              <tui-list-cell class="line-cell" :hover="false" :arrow="false">
+                <view class="tui-title">运费</view>
+                <input type="digit" placeholder="请填写运费" v-model="form.sendCost" placeholder-class="tui-placeholder"/>
+              </tui-list-cell>
+            </view>
+          </view>
 
           <tui-list-cell class="line-cell" :hover="false" :arrow="false">
             <view class="tui-title">打印小票</view>
@@ -170,7 +240,7 @@
             <view class="tui-title">
               <text>合计金额</text>  
             </view>
-            <text style="color: #3385FF;width:170upx;">¥{{parseFloat(finalPrice)}}</text>
+            <text style="color: #3385FF;width:170upx;">¥{{parseFloat(systemPrice)}}</text>
             <button @tap="showDiscount()" class="admin-button-com middle default" style="width:140upx;">打折</button>
             <text v-if="discountValue>0 && discountValue<1">{{ Number(discountValue)*10 }}折</text>
           </tui-list-cell>
@@ -180,8 +250,8 @@
             <view v-else-if="form.hasPay == 3" class="tui-title">余额扣减</view>
             <view v-else class="tui-title">{{form.forward==0?'实收':'实付'}}金额</view>
             <input style="border:1upx solid #eee;color:#3385FF;width:150upx;height:70upx;" type="digit" @focus="modifyPrice=''" v-model="modifyPrice" placeholder-class="tui-placeholder"/>
-            <text v-if="modifyPrice > 0 && modifyPrice > finalPrice" style="font-size:25upx;margin-left:20upx;color:red;">人工包装费 ¥{{parseFloat((modifyPrice-finalPrice).toFixed(2))}}</text>
-            <text v-if="modifyPrice > 0 && modifyPrice < finalPrice" style="font-size:25upx;margin-left:20upx;color:green;">优惠 ¥{{parseFloat((finalPrice-modifyPrice).toFixed(2))}}</text>
+            <text v-if="modifyPrice > 0 && modifyPrice > systemPrice" style="font-size:25upx;margin-left:20upx;color:red;">人工包装费 ¥{{parseFloat((modifyPrice-systemPrice).toFixed(2))}}</text>
+            <text v-if="modifyPrice > 0 && modifyPrice < systemPrice" style="font-size:25upx;margin-left:20upx;color:green;">优惠 ¥{{parseFloat((systemPrice-modifyPrice).toFixed(2))}}</text>
           </tui-list-cell>
 
 				<tui-list-cell class="line-cell" :hover="false" :arrow="true">
@@ -395,40 +465,32 @@
 
     <mx-date-picker :show="reachDatePickerShow" format="yyyy-mm-dd" type="date" :value="reachDatePickerValue" :show-tips="true" @confirm="confirmReachDatePicker" @cancel="reachDatePickerShow = false" />
 
+    <!-- 取件时间选择器 -->
+    <TimePicker ref="timePicker" @confirm="handleTimeConfirm" />
+
     </view>
   </view>
 </template>
 
 <script>
 import TuiListCell from "@/components/plugin/list-cell";
-import AppAvatarModule from "@/components/module/app-avatar";
-import SimpleAddress from "@/components/plugin/simple-address";
-import AppAreaSel from "@/components/app-area-sel";
 import productMins from "@/mixins/product";
-import appFormRadioBtn from "@/components/app-formRadio-btn";
-import appAddressGroup from "@/components/app-address-group";
-import appSendTime from "@/components/app-send-time";
-import appFormSend from "@/components/app-form-send";
-const formUtil = require("@/utils/formValidation.js");
 import { createOrder } from "@/api/order/index";
 import { getAllStaff } from "@/api/shop-admin"
 import { mapActions, mapGetters } from "vuex";
 import { getCustomInfo} from "@/api/custom"
+import { quickOrderAllDeliveryQuotes } from '@/api/express/delivery'
 import ModalModule from "@/components/plugin/modal"
 import MxDatePicker from '@/components/mx-datepicker/mx-datepicker.vue';
+import TimePicker from '@/components/plugin/TimePicker';
+import dayjs from 'dayjs';
 export default {
   name: "affirm",
   components: {
     TuiListCell,
-    AppAvatarModule,
-    SimpleAddress,
-    AppAreaSel,
-    appFormRadioBtn,
-    appAddressGroup,
-    appSendTime,
-    appFormSend,
     ModalModule,
-    MxDatePicker
+    MxDatePicker,
+    TimePicker
   },
   mixins: [productMins],
   data () {
@@ -472,7 +534,9 @@ export default {
         forward:0,
         needWork:0,
         forwardStock:1,
-        fromType:1//门店来的订单
+        fromType:1,//门店来的订单
+        reductionRule: 0,
+        callErrand: 0 //现在呼叫 0 晚点呼叫 1
       },
       showCustomer: false,
       customInfo:{discount:1,balance:0},
@@ -486,22 +550,47 @@ export default {
       calc:'add',
       diffPriceList:[],
       currentShop:{default:0},
+      openIntraCity: 0,
+      hcFreeKm: 0,  //跑腿免费公里数
+      hcMap: [],    //花材满减规则
+      hsFreeKm: 0,  //花束免费公里数
+      hsMap: [],    //花束满减规则
+      deliveryQuotes: [], // 跑腿平台报价列表
+      selectedDeliveryIndex: -1,
+      selectedDeliveryData: null,
+      showDistance: 0,
       forwardPayWay:0,//不影响原有的form.payWay方式,重新定义一个
       reduceStock: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']],
       selectedHour: '12', // 选中的小时
       selectedMinute: '00', // 选中的分钟
+      pickupTime: {
+          label: '立即取件',
+          value: dayjs().format('YYYY-MM-DD HH:mm'),
+      },
     };
   },
   onLoad (option) {
-
-
+    this.option = option
     this.kdType = option.kdType
 
     let that = this
     this.initMerchantInfo().then((data) => {
         uni.setNavigationBarTitle({ title: data.name })
         that.currentShop = data.shopInfo
+        that.openIntraCity = data.openIntraCity
+        that.hcFreeKm = data.hcFreeKm
+        try {
+           that.hcMap = typeof data.hcMap === 'string' ? JSON.parse(data.hcMap) : data.hcMap
+        } catch (e) {
+           that.hcMap = []
+        }
+        that.hsFreeKm = data.hsFreeKm
+        try {
+           that.hsMap = typeof data.hsMap === 'string' ? JSON.parse(data.hsMap) : data.hsMap
+        } catch (e) {
+           that.hsMap = []
+        }
     })
 
     if(Number(option.customId) > 0){
@@ -516,7 +605,6 @@ export default {
       //用于混入product的已选中花材!!!!!
       this.selectJobId = option.customId
       this.selectJobType = 'bill'
-
     }
 
     //默认欠款是首选项
@@ -538,7 +626,6 @@ export default {
 
   },
 	onShow(){
-
 		if(!this.$util.isEmpty(uni.getStorageSync("newClient"))){
 			this.showCustomer = true;
 		}
@@ -550,6 +637,10 @@ export default {
       }
     })
 
+    //获取跑腿平台报价
+    if(this.form.sendType == 2){
+      this.getDeliveryQuotes()
+    }
 	},
 	onUnload(){
 			uni.removeStorageSync('newClient')
@@ -557,32 +648,92 @@ export default {
 	},
   computed: {
     ...mapGetters(["getMerchantInfo","getMyShopInfo", "getLoginInfo"]),
-    finalPrice(){
+    systemPrice(){
       let totalPrice = this.allPrice.toFixed(2)
       let finalPrice = Number(totalPrice)
       if(this.form.sendType == 2){
-        finalPrice = Number(finalPrice) + Number(this.form.sendCost)
+        const sendCost = Number(this.form.sendCost)
+        
+        if(this.form.reductionRule == 0){
+          finalPrice = Number(finalPrice) + sendCost
+        }else if(this.form.reductionRule == 1){
+          if(this.isHcMapRule()){
+            finalPrice = Number(finalPrice)
+          }else{
+            finalPrice = Number(finalPrice) + sendCost
+          }
+        }else if(this.form.reductionRule == 2){
+          if(this.isHsMapRule()){
+            finalPrice = Number(finalPrice)
+          }else{
+            finalPrice = Number(finalPrice) + sendCost
+          }
+        }
       }
-      finalPrice = Number(finalPrice).toFixed(2)
-      let currentPrice = parseFloat(finalPrice)
-      this.modifyPrice = currentPrice 
-      return currentPrice
+      return parseFloat(Number(finalPrice).toFixed(2))
     },
     labourCost(){
       let cost = 0
-      if(Number(this.modifyPrice)>Number(this.finalPrice)){
-        cost = Number(this.modifyPrice) - Number(this.finalPrice)
+      if(Number(this.modifyPrice)>Number(this.systemPrice)){
+        cost = Number(this.modifyPrice) - Number(this.systemPrice)
         cost = Number(cost).toFixed(2)
       }
       return cost
     }
   },
+  watch: {
+    systemPrice: {
+      handler(val) {
+        this.modifyPrice = val
+      },
+      immediate: true
+    }
+  },
   methods: {
+    init () {},
     ...mapActions(["initMerchantInfo"]),
+    checkMapRule(type){
+      const distanceInMeters = Number(this.showDistance) || 0
+      const freeKm = type === 'hc' ? this.hcFreeKm : this.hsFreeKm
+      const freeKmInMeters = (Number(freeKm) || 0) * 1000
+      if(distanceInMeters < freeKmInMeters){
+        return true
+      }
+      let map = type === 'hc' ? this.hcMap : this.hsMap
+      if(map && Array.isArray(map) && map.length > 0){
+          const flowerCount = this.allCountFun.bigLength || 0
+          const flowerPrice = this.allPriceFun || 0
+          for(const rule of map){
+            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
+    },
+    isHcMapRule(){
+      return this.checkMapRule('hc')
+    },
+    isHsMapRule(){
+      return this.checkMapRule('hs')
+    },
     setSendType (type) {
       this.form.sendType = type
       if (type !== 2) {
         this.form.sendCost = ""
+        // 切换到其他配送方式时清空报价数据
+        this.deliveryQuotes = []
+        this.selectedDeliveryIndex = -1
+        this.selectedDeliveryData = null
+      } else {
+        // 如果切换回跑腿,且之前有获取过但被清空了,可能需要重新获取,或者保持现状等待用户操作
+        // 这里根据 affirmGhs 的逻辑,如果在此处重新获取,可能需要在 onShow 或其他地方触发
+        if(this.openIntraCity == 1 && type == 2){
+          this.getDeliveryQuotes()
+        }
       }
     },
     bindHistoryDateChange(){
@@ -632,7 +783,7 @@ export default {
     },
     setDiscount(num){
       this.discountValue = Number(num)
-      let price = Number(this.finalPrice)*Number(num)
+      let price = Number(this.systemPrice)*Number(num)
       price = parseFloat(price.toFixed(2))
       this.modifyPrice = price
       this.$refs.discountRef.close()
@@ -666,9 +817,6 @@ export default {
       this.kdType = 'HIT';
 			this.replaceItemFn(item,0,'add')
 		},
-    init () {
-
-    },
     kdChange(e){
 			let index = e.detail.value
       if(!this.$util.isEmpty(this.staffList)){
@@ -744,7 +892,16 @@ export default {
         self.formSubmit()
       })
     },
+    validateForm() {
+      if(Number(this.modifyPrice) <= 0){
+        this.$msg('收款金额必须大于0');
+        return false
+      }
+      return true
+    },
     async formSubmit () {
+      if(!this.validateForm()) return;
+
       let that = this
       let formData = this.form;
       const product = this.selectList.map((ele) => {
@@ -753,19 +910,27 @@ export default {
         let unitPrice = ele.userPrice ? ele.userPrice : 0
         return {productId: ele.id, bigNum: ele.bigCount, smallNum: ele.smallCount, classId: ele.classId,
           itemId: ele.itemId, unitPrice:unitPrice,remark:ele.remark,property:ele.property,unitType:unitType,num:num}
-      });
+        }
+      );
       formData.product = JSON.stringify(product);
       if (!this.getMerchantInfo) {
         await this.initMerchantInfo();
       }
       formData.shopId = this.getMerchantInfo.id;
-      if(Number(this.modifyPrice) <= 0){
-        this.$msg('收款金额必须大于0');
-        return false
-      }
 
       let params = {...formData,modifyPrice: this.modifyPrice,labourCost:this.labourCost};
 
+      // 如果选择了跑腿配送并且有选中的平台数据,则添加配送平台信息
+      if (this.form.sendType == 2 && this.selectedDeliveryData) {
+        params.deliveryPlatform = this.selectedDeliveryData.en_name // 平台名称
+        params.deliveryPrice = this.selectedDeliveryData.priceNumber // 报价金额
+        params.deliveryDistance = this.showDistance // 配送距离
+        params.deliveryBracketContent = this.selectedDeliveryData.bracketContent // 配送平台括号里的内容
+        if(this.form.callErrand == 1){
+          params.pickupTime = this.pickupTime.value
+        }
+      }
+
       //多颜色
       let xj = uni.getStorageSync("xj"+this.option.customId)
       //orderType 开单类型,1商品开单 2花材开单
@@ -776,7 +941,7 @@ export default {
 			let book = this.option.book ? this.option.book : 0
 			let reduceStock = this.option.reduceStock?this.option.reduceStock:0
       params = {...params,orderId:orderId,book:book,reduceStock:reduceStock,version:3}
-console.log(params)
+      //console.log(params)
       uni.showLoading({mask:true})
       createOrder(params).then((res) => {
         uni.hideLoading()
@@ -803,7 +968,6 @@ console.log(params)
           }else{
             this.$msg('提交失败')
           }
-
         }
       })
     },
@@ -823,7 +987,81 @@ console.log(params)
         this.$util.pageTo({url: '/admin/billing/hs?customId='+customId+'&customName='+customName+'&orderId='+orderId+'&book='+book+'&reduceStock='+reduceStock,type:2})
       }
       
-    }
+    },
+    getDeliveryQuotes(){
+      this.deliveryQuotes = []
+      this.selectedDeliveryIndex = -1
+      this.selectedDeliveryData = null
+
+      quickOrderAllDeliveryQuotes({
+        totalPrice: this.allPrice,
+        weight: 1, //this.allCount.weight
+        customId: this.option.customId,
+        pickupTime: this.pickupTime.value,
+      }).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.indexOf('(') !== -1) {
+                const parts = displayName.split('(');
+                if(parts.length > 1) {
+                   displayName = parts[0].trim();
+                   bracketContent = parts[1].replace(')', '');
+                }
+              }
+
+              //滴滴特殊处理,括号里的内容作为类型
+              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{
+           uni.showToast({ title: '暂无跑腿可用', icon: 'none' })
+        }
+      }).catch(err=>{
+        this.$msg('获取跑腿平台报价失败')
+        console.log('获取跑腿平台报价失败:', err)
+        this.deliveryQuotes = []
+      })
+    },
+    // 选择跑腿平台
+    selectDelivery(item, index) {
+      this.selectedDeliveryIndex = index
+      this.selectedDeliveryData = item
+      this.form.sendCost = item.priceNumber
+      this.showDistance = item.distance || 0
+    },
+    openTimePicker() {
+      this.$refs.timePicker.open();
+    },
+    handleTimeConfirm(time) {
+      this.pickupTime = time;
+      if (this.form.sendType == 2) {
+          this.getDeliveryQuotes();
+      }
+    },
   }
 };
 </script>
@@ -831,6 +1069,11 @@ console.log(params)
 <style lang="scss" scoped>
 .admin-button-com {
   margin-right: 30upx;
+  &.active {
+      background-color: #09C567 !important;
+      color: #fff !important;
+      border: 1upx solid #09C567 !important;
+  }
 }
 
 
@@ -1315,4 +1558,232 @@ console.log(params)
 		}
 	}
 }
+	/* 跑腿平台报价样式 */
+.delivery-quotes-container {
+	width: 100%;
+	margin-top: 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: 5upx;
+}
+
+.delivery-policy-link {
+	color: rgb(201, 52, 52);
+	font-size: 30upx;
+	margin-left: 30upx;
+	font-weight:bold;
+	text-decoration: underline;
+}
+
+.policy-icon {
+	font-size: 28upx;
+	margin-left: 4upx;
+}
+
+.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;
+}
+
+.delivery-section {
+    border: 1upx solid #eee;
+    margin: 8upx 20upx;
+    border-radius: 8upx;
+}
+.empty-quotes-view {
+    text-align: center;
+    padding: 20upx 0;
+}
+.error-tips {
+    color: red;
+    font-weight: bold;
+    font-size: 32upx;
+}
+.distance-box {
+    display: flex;
+    align-items: center;
+}
+.blue-text {
+    color: #3385FF;
+}
+.title-reduction {
+    width: 150upx;
+}
+.btn-reduction {
+    margin-right: 12upx;
+    height: 70upx;
+    line-height: 46upx;
+}
+.btn-reduction-last {
+    margin-right: 0upx;
+    height: 70upx;
+    line-height: 46upx;
+}
+.strike-through {
+    text-decoration: line-through;
+}
+.btn-call {
+    width: 170upx;
+}
+.time-action-view {
+    display: flex;
+    align-items: center;
+    color: #049E2C;
+    font-size: 28upx;
+}
+.icon-arrow-right {
+    font-size: 24upx;
+    color: #049E2C;
+    margin-left: 5upx;
+}
+
 </style>

+ 16 - 1
hdApp/src/admin/billing/affirmGhs.vue

@@ -224,7 +224,7 @@
 						</view>
 					</tui-list-cell>
 
-					<tui-list-cell v-if="form.sendType == 2 && ghsInfo.openIntraCity == 1" class="line-cell" :hover="false" :arrow="false">
+					<tui-list-cell v-if="form.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">
@@ -510,6 +510,7 @@ export default {
 				if(this.ghsInfo.hcMap && typeof this.ghsInfo.hcMap == 'string'){
 					this.ghsInfo.hcMap = JSON.parse(this.ghsInfo.hcMap)
 				}
+
 				// 判断hcMap是否为空
 				if(this.ghsInfo.hcMap && Array.isArray(this.ghsInfo.hcMap) && this.ghsInfo.hcMap.length > 0){
 					// 获取花材数量和总价
@@ -524,9 +525,18 @@ export default {
 						
 						// 如果花材数量大于num,花材总价大于price,距离小于distance,则免运费
 						if(flowerCount >= ruleNum && flowerPrice >= rulePrice && distanceInMeters <= ruleDistance){
+							console.log('满足免运费条件:',flowerCount,ruleNum,flowerPrice,rulePrice,distanceInMeters,ruleDistance)
 							return true
 						}
 					}
+				}else{
+					if(this.ghsInfo.hcMap && Array.isArray(this.ghsInfo.hcMap) && this.ghsInfo.hcMap.length == 0){
+						console.log('hcMap数据为空')
+						return false
+					}
+					
+					console.log('this.ghsInfo.hcMap: ',this.ghsInfo.hcMap)
+					console.log('hcMap数据格式出问题了')
 				}
 			}
 			
@@ -749,6 +759,11 @@ export default {
 								bracketContent = displayName.split('(')[1].replace(')', '')
 								displayName = displayName.split('(')[0].trim()
 							}
+
+							//滴滴特殊处理,括号里的内容作为类型
+							if(item.en_name == 'didi' && bracketContent != ''){
+								item.type = bracketContent
+							}
 							
 							return {
 								...item,

+ 7 - 0
hdApp/src/api/express/delivery.js

@@ -35,6 +35,13 @@ export const hdAllDeliveryQuotes = data => {
 	return https.post('/delivery/hd-all-delivery-quotes', data)
 }
 
+/**
+ * 零售快捷开单 -- 跑腿 -- 获取配送报价
+ */
+export const quickOrderAllDeliveryQuotes = data => {
+	return https.post('/delivery/quick-order-delivery-quotes', data)
+}
+
 /**
  * 发货与收货地址
  */

+ 295 - 0
hdApp/src/components/plugin/TimePicker.vue

@@ -0,0 +1,295 @@
+<template>
+  <uni-popup ref="popup" type="bottom" :safe-area="false">
+    <view class="time-picker-container">
+      <view class="picker-header">
+        <text class="title">期望取件时间</text>
+        <view class="close-btn" @click="close">
+          <zui-svg-icon icon="general-close" :width="18" :height="18" color="#666" />
+        </view>
+      </view>
+
+      <picker-view v-if="visible" class="picker-view" :indicator-style="indicatorStyle" :value="pickerValue" @change="handleChange" :immediate-change="true">
+        <picker-view-column>
+          <view class="picker-item" v-for="(item, index) in days" :key="index">{{ item.label }}</view>
+        </picker-view-column>
+        <picker-view-column>
+          <view class="picker-item" v-for="(item, index) in hours" :key="index">{{ item.label }}</view>
+        </picker-view-column>
+        <picker-view-column>
+          <view class="picker-item" v-for="(item, index) in minutes" :key="index">{{ item.label }}</view>
+        </picker-view-column>
+      </picker-view>
+
+      <view class="confirm-button-wrapper">
+        <button class="confirm-btn" @click="handleConfirm">确认</button>
+      </view>
+    </view>
+  </uni-popup>
+</template>
+
+<script>
+import dayjs from 'dayjs';
+
+const minutes = [0, 10, 20, 30, 40, 50];
+
+export default {
+  name: 'TimePicker',
+  data() {
+    return {
+      visible: false, // 控制picker-view的渲染
+      pickerValue: [0, 0, 0],
+      tempPickerValue: [0, 0, 0], // 临时存储滚动过程中的值
+      debounceTimer: null,
+      days: [],
+      hours: [],
+      minutes: [],
+      indicatorStyle: `height: 80rpx;`,
+    };
+  },
+  created() {
+    this.initTime();
+  },
+  methods: {
+    open() {
+      this.visible = true;
+      this.initTime();
+      this.$refs.popup.open();
+    },
+
+    close() {
+      this.visible = false;
+      this.$refs.popup.close();
+    },
+
+    initTime() {
+      const now = dayjs(); // Use current time
+      this.days = this.generateDays();
+
+      const isToday = true;
+      let availableHoursToday = this.generateHours(now, isToday);
+
+      // If no hours available today, switch to tomorrow
+      if (availableHoursToday.length <= 1) { // Only "立即"
+        const tomorrow = dayjs().add(1, 'day').startOf('day');
+        this.days = this.generateDays(); // Regenerate days if needed
+        this.hours = this.generateHours(tomorrow, false);
+        this.minutes = this.generateMinutes(tomorrow, false, this.hours[0].value);
+        this.pickerValue = [1, 0, 0]; // Select "Tomorrow", first hour, first minute
+        this.tempPickerValue = [1, 0, 0];
+        return;
+      }
+
+      this.hours = availableHoursToday;
+
+      // Default to "立即"
+      const initialHourIndex = 0;
+      const selectedHour = this.hours[initialHourIndex].value;
+      this.minutes = this.generateMinutes(now, isToday, selectedHour);
+      const initialMinuteIndex = 0;
+      
+      this.pickerValue = [0, initialHourIndex, initialMinuteIndex];
+      this.tempPickerValue = [0, initialHourIndex, initialMinuteIndex];
+    },
+
+    generateDays() {
+      return [
+        { label: '今天', value: dayjs().format('YYYY-MM-DD') },
+        { label: '明天', value: dayjs().add(1, 'day').format('YYYY-MM-DD') },
+      ];
+    },
+
+    generateHours(now, isToday) {
+      const hours = [];
+      if (isToday) {
+        hours.push({ label: '立即', value: -1 });
+
+        let startHour = now.hour() + 1; // 当前小时的下一个小时
+        // Find if any minutes are available in the current hour
+        const firstAvailableMinute = minutes.find(m => m > now.minute());
+        
+        // If no minutes available in current hour, or if the last minute has passed, start from the next hour
+        if (firstAvailableMinute === undefined) {
+          startHour++;
+        }
+        
+        for (let i = startHour; i <= 23; i++) {
+          hours.push({ label: `${i}时`, value: i });
+        }
+      } else {
+        for (let i = 0; i <= 23; i++) {
+          hours.push({ label: `${i}时`, value: i });
+        }
+      }
+      return hours;
+    },
+
+    generateMinutes(now, isToday, selectedHour) {
+      if (selectedHour === -1) {
+        return [{ label: '', value: -1 }];
+      }
+      
+      let availableMinutes = minutes;
+      
+      if (isToday && selectedHour === now.hour()) {
+        const currentMinute = now.minute();
+        availableMinutes = minutes.filter(m => m > currentMinute);
+      }
+      
+      return availableMinutes.map(m => ({ label: `${m}分`, value: m }));
+    },
+
+    handleChange(e) {
+      const val = e.detail.value;
+      this.tempPickerValue = val;
+      
+      if (this.debounceTimer) {
+        clearTimeout(this.debounceTimer);
+      }
+      
+      this.debounceTimer = setTimeout(() => {
+        this.updatePickers(val);
+      }, 100);
+    },
+
+    updatePickers(val, isSync = false) {
+      const [dayIndex, hourIndex, minuteIndex] = val;
+      const oldPickerValue = this.pickerValue;
+      
+      const now = dayjs();
+      const isToday = dayIndex === 0;
+
+      let newHours = this.hours;
+      let newMinutes = this.minutes;
+      
+      let finalHourIndex = hourIndex;
+      let finalMinuteIndex = minuteIndex;
+
+      // Day changed
+      if (oldPickerValue[0] !== dayIndex) {
+        newHours = this.generateHours(now, isToday);
+        // 修正越界,尽量保持用户的选择,而不是强制归零
+        if (finalHourIndex >= newHours.length) {
+            finalHourIndex = 0;
+        }
+      }
+      
+      // Calculate selected hour based on current indices
+      const selectedHour = newHours[finalHourIndex] ? newHours[finalHourIndex].value : -1;
+
+      // Always regenerate minutes to ensure correctness (e.g. today/tomorrow switch, or hour switch)
+      // Optimization: we could check if generation parameters actually changed, but it's fast enough.
+      newMinutes = this.generateMinutes(now, isToday, selectedHour);
+      
+      // 修正越界
+      if (finalMinuteIndex >= newMinutes.length) {
+          finalMinuteIndex = 0;
+      }
+      
+      this.hours = newHours;
+      this.minutes = newMinutes;
+      this.debounceTimer = null;
+
+      const newValue = [dayIndex, finalHourIndex, finalMinuteIndex];
+      
+      if (isSync) {
+        this.pickerValue = newValue;
+      } else {
+        // Use setTimeout to avoid visual glitch on picker reset
+        setTimeout(() => {
+            this.pickerValue = newValue;
+        }, 0);
+      }
+    },
+
+    handleConfirm() {
+      if (this.debounceTimer) {
+        clearTimeout(this.debounceTimer);
+        this.updatePickers(this.tempPickerValue, true);
+      }
+      
+      const [dayIndex, hourIndex, minuteIndex] = this.pickerValue;
+      const selectedDay = this.days[dayIndex];
+      const selectedHour = this.hours[hourIndex];
+      const selectedMinute = this.minutes[minuteIndex];
+
+      let result = {};
+      if (selectedHour && selectedHour.value === -1) {
+        result = {
+          label: '立即取件',
+          value: dayjs().format('YYYY-MM-DD HH:mm'),
+        };
+      } else {
+        const date = selectedDay.value;
+        const hour = selectedHour?.value ?? 0;
+        const minute = selectedMinute?.value ?? 0;
+        const finalDate = dayjs(`${date} ${hour}:${minute}`).toDate();
+        result = {
+          label: dayjs(finalDate).format('YYYY-MM-DD HH:mm'),
+          value: dayjs(finalDate).format('YYYY-MM-DD HH:mm'),
+        };
+      }
+
+      this.$emit('confirm', result);
+      this.close();
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.time-picker-container {
+  background-color: #fff;
+  border-top-left-radius: 24rpx;
+  border-top-right-radius: 24rpx;
+  height: 45vh;
+  display: flex;
+  flex-direction: column;
+}
+.picker-header {
+  position: relative;
+  text-align: center;
+  padding: 30rpx;
+  flex-shrink: 0;
+
+  .title {
+    font-size: 44rpx;
+    font-weight: 500;
+  }
+
+  .close-btn {
+    position: absolute;
+    right: 30rpx;
+    top: 50%;
+    transform: translateY(-50%);
+    padding: 10rpx;
+  }
+}
+.picker-view {
+  width: 100%;
+  height: 100%;
+}
+.picker-item {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 42rpx;
+}
+.confirm-button-wrapper {
+  padding: 20rpx 30rpx;
+  padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
+  padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
+  flex-shrink: 0;
+}
+.confirm-btn {
+  background: linear-gradient(to right, #409eff, #67c2ff);
+  color: white;
+  border-radius: 50rpx;
+  font-size: 42rpx;
+  height: 88rpx;
+  line-height: 88rpx;
+  border: none;
+  &::after {
+    border: none;
+  }
+}
+</style>

+ 20 - 2
mallApp/src/pages/billing/affirmGhs.vue

@@ -117,7 +117,7 @@
 									</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;">上面地址缺失或有问题,请修改</text>
+									<text v-if="validAddress">正在获取配送运费...</text><text v-else style="color:red;font-weight:bold;font-size:32upx;">上面地址缺失或有问题,请修改</text>
 								</view>
 							</view>
 						</tui-list-cell>
@@ -500,7 +500,11 @@ export default {
 			if(distanceInMeters > freeKmInMeters){
 				// 对 this.shopInfo.hcMap 进行类型判断,如果是字符串,则转换为数组
 				if(typeof this.shopInfo.hcMap === 'string'){
-					this.shopInfo.hcMap = JSON.parse(this.shopInfo.hcMap)
+					try {
+						this.shopInfo.hcMap = JSON.parse(this.shopInfo.hcMap)
+					} catch (e) {
+						this.shopInfo.hcMap = []
+					}
 				}
 				
 				// 判断hcMap是否为空
@@ -517,11 +521,19 @@ export default {
 						
 						// 如果花材数量大于num,花材总价大于price,距离小于distance,则免运费
 						if(flowerCount >= ruleNum && flowerPrice >= rulePrice && distanceInMeters <= ruleDistance){
+							console.log('满足免运费条件:',flowerCount,ruleNum,flowerPrice,rulePrice,distanceInMeters,ruleDistance)
 							this.isFreeDelivery = true
 							return true
 						}
 					}
 				}else{
+					if(this.shopInfo.hcMap && Array.isArray(this.shopInfo.hcMap) && this.shopInfo.hcMap.length == 0){
+						console.log('hcMap数据为空')
+						this.isFreeDelivery = false
+						return false
+					}
+
+					console.log('this.shopInfo.hcMap: ',this.shopInfo.hcMap)
 					console.log('hcMap数据格式出问题了')
 				}
 			}
@@ -647,6 +659,12 @@ export default {
 							if (displayName && displayName.includes('(')) {
 								// 取括号里的内容
 								bracketContent = displayName.split('(')[1].replace(')', '')
+								displayName = displayName.split('(')[0].trim()
+							}
+
+							//滴滴特殊处理,括号里的内容作为类型
+							if(item.en_name == 'didi' && bracketContent != ''){
+								item.type = bracketContent
 							}
 							
 							return {

+ 17 - 6
mallApp/src/pages/order/buy.vue

@@ -159,12 +159,23 @@ export default {
       this.goodsTotalPrice = parseFloat(total.toFixed(2))
 
       getHasMap().then(res => {
-        this.hasMap = (res.data && res.data.hasMap) ? res.data.hasMap : 0;
-
-        const shopData = res.data.shop || {};
-        this.openIntraCity = shopData.openIntraCity || 0;
-        this.hsFreeKm = shopData.hsFreeKm || 0;
-        this.hsAddFee = shopData.hsAddFee || 0;
+        if(res.code == 1){
+          this.hasMap = (res.data && res.data.hasMap) ? res.data.hasMap : 0;
+          const shopData = res.data.shop || {};
+          this.openIntraCity = shopData.openIntraCity || 0;
+          this.hsFreeKm = shopData.hsFreeKm || 0;
+          this.hsAddFee = shopData.hsAddFee || 0;
+        }else{
+          this.$msg('获取配送范围与配送规则失败');
+          console.log('获取配送范围与配送规则失败:', res.msg || res)
+          // 返回上一页
+          uni.navigateBack({ delta: 1 });
+        }
+      }).catch(err => {
+        this.$msg('获取配送范围与配送规则失败');
+        console.log('获取配送范围失败:', err)
+        // 返回上一页
+        uni.navigateBack({ delta: 1 });
       })
     },
 		changeSendType(num){

+ 11 - 3
mallApp/src/pages/order/components/app-delivery.vue

@@ -59,7 +59,7 @@
             </view>
           </tui-list-cell>
           <tui-list-cell  v-else-if="deliveryQuotesLoading" class="line-cell" :hover="false" :arrow="false">
-            <view style="text-align: center; padding: 20rpx 0; color: #999;">正在获取报价...</view>
+            <view style="text-align: center; padding: 20rpx 0; color: #999;">正在获取配送报价...</view>
           </tui-list-cell>
 
           <tui-list-cell class="line-cell" :hover="false">
@@ -447,12 +447,20 @@ export default {
 						.filter(item => item.isAble !== false)
 						.map(item => {
 							const price = item.price ? (item.price / 100).toFixed(1) : '0.0'
-							let displayName = item.name
+							
+              // 提取平台名称(去掉括号内的内容)
+              let displayName = item.name
               let bracketContent = ''
 							if (displayName && displayName.includes('(')) {
-								//displayName = displayName.split('(')[0].trim()
                 bracketContent = displayName.split('(')[1].replace(')', '')
+                displayName = displayName.split('(')[0].trim()
 							}
+              
+              //滴滴特殊处理,括号里的内容作为类型
+              if(item.en_name == 'didi' && bracketContent != ''){
+                item.type = bracketContent
+              }
+
 							return {
 								...item,
 								displayName: displayName,