| | |
| | | <template> |
| | | <view class="hall-page"> |
| | | <view v-if="showOrderDetail && isStatusDetail" class="order-detail-map-layer" :style="{ top: statusBarHeight + 'px' }"> |
| | | <map |
| | | class="order-detail-map-layer__map" |
| | | :latitude="detailMap.center.latitude" |
| | | :longitude="detailMap.center.longitude" |
| | | :markers="detailMap.markers" |
| | | :polyline="detailMap.polyline" |
| | | :include-points="detailMap.includePoints" |
| | | :scale="detailMap.scale" |
| | | :enable-zoom="true" |
| | | :enable-scroll="true" |
| | | ></map> |
| | | </view> |
| | | |
| | | <view class="hall-page__header" :style="{ paddingTop: statusBarHeight + 'px' }"> |
| | | <view class="hall-page"> |
| | | <view class="hall-page__header" :style="{ paddingTop: statusBarHeight + 'px' }"> |
| | | <view class="hall-page__user-row"> |
| | | <view class="hall-page__user"> |
| | | <image class="hall-page__avatar" src="/static/image/login_bg@2x.png" mode="aspectFill"></image> |
| | | <text class="hall-page__name">汤子新</text> |
| | | <image class="hall-page__avatar" :src="centerUserInfo.fullImgUrl || '/static/image/ic_pic@2x.png'" mode="aspectFill"></image> |
| | | <text class="hall-page__name">{{ centerUserInfo.name }}</text> |
| | | </view> |
| | | <view class="hall-page__status"> |
| | | <view class="hall-page__status-dot"></view> |
| | | <text class="hall-page__status-text">接单中</text> |
| | | <view class="hall-page__status" v-if="userInfo.auditStatus === 3" @click="openStatusPicker"> |
| | | <view class="hall-page__status-dot" :class="{ 'hall-page__status-dot--offline': acceptingStatus === 0 }"></view> |
| | | <text class="hall-page__status-text">{{ acceptingStatus === 1 ? '接单中' : '已下线' }}</text> |
| | | <text class="hall-page__status-arrow">▼</text> |
| | | </view> |
| | | <view class="hall-page__user" style="opacity: 0;"> |
| | | <image class="hall-page__avatar" src="/static/image/login_bg@2x.png" mode="aspectFill"></image> |
| | | <text class="hall-page__name">汤子新</text> |
| | | <image class="hall-page__avatar" :src="centerUserInfo.fullImgUrl || '/static/image/ic_pic@2x.png'" mode="aspectFill"></image> |
| | | <text class="hall-page__name">{{ centerUserInfo.name }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="hall-page__stats"> |
| | | <view v-for="item in stats" :key="item.label" class="hall-page__stat-item"> |
| | | <text class="hall-page__stat-value">{{ item.value }}</text> |
| | | <text class="hall-page__stat-value">{{ userInfo.auditStatus === 3 ? item.value : '-' }}</text> |
| | | <text class="hall-page__stat-label">{{ item.label }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="hall-page__tabs"> |
| | | <view v-for="tab in tabs" :key="tab.value" class="hall-page__tab" :class="{ 'hall-page__tab--active': activeTab === tab.value }" @click="activeTab = tab.value"> |
| | | <view v-for="tab in displayTabs" :key="tab.value" class="hall-page__tab" :class="{ 'hall-page__tab--active': activeTab === tab.value }" @click="activeTab = tab.value"> |
| | | <text class="hall-page__tab-text">{{ tab.label }}</text> |
| | | <text v-if="tab.count" class="hall-page__tab-count">{{ tab.count }}</text> |
| | | <text v-if="tab.count" class="hall-page__tab-count">{{ userInfo.auditStatus === 3 ? tab.count : '' }}</text> |
| | | <view v-if="activeTab === tab.value" class="hall-page__tab-line"></view> |
| | | </view> |
| | | </view> |
| | | <view class="hall-page__filter" @click="toggleFilterPopup(true)"> |
| | | <text class="hall-page__filter-text" :class="{ 'hall-page__filter-text--active': showFilterPopup }">筛选</text> |
| | | <image :src="showFilterPopup ? '/static/image/ic_shaixuan_sel@2x.png' : '/static/image/ic_shaixuan@2x.png'" mode="widthFix" class="hall-page__filter-icon"></image> |
| | |
| | | |
| | | <view v-if="showFilterPopup" class="filter-popup" :style="{ top: headerHeight + 'px', bottom: tabbarHeight + 'px' }" @click="toggleFilterPopup(false)"> |
| | | <view class="filter-popup__panel" @click.stop> |
| | | <view v-for="section in filterSections" :key="section.key" class="filter-popup__section"> |
| | | <text class="filter-popup__title">{{ section.title }}</text> |
| | | <view class="filter-popup__options"> |
| | | <view |
| | | v-for="option in section.options" |
| | | :key="option" |
| | | class="filter-popup__option" |
| | | :class="{ 'filter-popup__option--active': selectedFilters[section.key] === option }" |
| | | @click="selectFilter(section.key, option)" |
| | | > |
| | | <text class="filter-popup__option-text">{{ option }}</text> |
| | | <scroll-view class="filter-popup__content" scroll-y> |
| | | <view v-for="section in filterSections" :key="section.key" class="filter-popup__section"> |
| | | <text class="filter-popup__title">{{ section.title }}</text> |
| | | <view class="filter-popup__options"> |
| | | <view |
| | | v-for="option in section.options" |
| | | :key="option" |
| | | class="filter-popup__option" |
| | | :class="{ 'filter-popup__option--active': selectedFilters[section.key] === option }" |
| | | @click="selectFilter(section.key, option)" |
| | | > |
| | | <text class="filter-popup__option-text">{{ option }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <view class="filter-popup__actions"> |
| | | <button class="filter-popup__button filter-popup__button--reset" hover-class="filter-popup__button--hover" @click="resetFilters">重置</button> |
| | |
| | | </view> |
| | | |
| | | <scroll-view class="hall-page__body" scroll-y :style="bodyStyle"> |
| | | <view v-if="currentOrderList.length" class="hall-page__list"> |
| | | <view v-for="item in currentOrderList" :key="item.id" class="order-card" @click="openDetailPopup(item)"> |
| | | <view class="hall-page__verified" v-if="userInfo.auditStatus !== 3"> |
| | | <image src="/static/image/default_unverified@2x.png" mode="widthFix"></image> |
| | | <button @click="toDriverCertification">去认证</button> |
| | | </view> |
| | | <view v-else-if="userInfo.auditStatus === 3 && currentOrderList.length" class="hall-page__list"> |
| | | <view v-for="(item, index) in currentOrderList" :key="item.id" class="order-card" @click="goToOrderDetail(item, index)"> |
| | | <view class="order-card__head"> |
| | | <view class="order-card__time"> |
| | | <text class="order-card__time-main">{{ item.time }}</text> |
| | | <text class="order-card__time-sub">送达</text> |
| | | <text class="order-card__time-main">{{ item.remainMinutes }}</text> |
| | | <text class="order-card__time-sub">分钟</text> |
| | | </view> |
| | | <view v-if="activeTab === 'hall'" class="order-card__price-wrap"> |
| | | <text v-if="item.serialNo" class="order-card__serial">#{{ item.serialNo }}</text> |
| | | <text class="order-card__price">{{ item.price }}</text> |
| | | <text class="order-card__price">¥{{ (item.driverFee / 100).toFixed(1) }}</text> |
| | | </view> |
| | | <view v-else class="order-card__price-wrap order-card__price-wrap--serial-only"> |
| | | <text v-if="item.serialNo" class="order-card__serial">#{{ item.serialNo }}</text> |
| | | <text v-if="item.code" class="order-card__serial">#{{ index + 1 }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="order-card__meta"> |
| | | <view class="order-card__tags"> |
| | | <view v-for="tag in item.tags" :key="tag.text" class="order-card__tag-wrap"> |
| | | <image |
| | | v-if="getTagImage(tag.text)" |
| | | class="order-card__tag-icon" |
| | | :src="getTagImage(tag.text)" |
| | | mode="widthFix" |
| | | ></image> |
| | | <text v-else class="order-card__tag" :class="tag.type ? 'order-card__tag--' + tag.type : ''">{{ tag.text }}</text> |
| | | </view> |
| | | <image v-if="item.isUrgent === 1" class="order-card__tag-img" src="/static/image/ic_jisuda@2x.png" mode="widthFix"></image> |
| | | <image v-else class="order-card__tag-img" src="/static/image/ic_biaosuda@2x.png" mode="widthFix"></image> |
| | | <text v-if="item.isValuable === true" class="order-card__tag order-card__tag--orange">贵重物品</text> |
| | | <text v-else class="order-card__tag order-card__tag--blue">{{ item.goodLevelName }}</text> |
| | | </view> |
| | | <text v-if="activeTab === 'hall'" class="order-card__extra">含加急¥{{ item.extra }}</text> |
| | | <text v-if="activeTab === 'hall' && item.urgentAmount" class="order-card__extra">含加急¥{{ item.urgentAmount / 100 }}</text> |
| | | </view> |
| | | |
| | | <view class="order-card__route"> |
| | | <view class="order-card__route-side"> |
| | | <text class="order-card__distance-top">{{ item.distanceTop }}</text> |
| | | <text class="order-card__distance-top">{{ item.depositDistance }}</text> |
| | | <view class="order-card__line"></view> |
| | | <text class="order-card__distance-bottom">{{ item.distanceBottom }}</text> |
| | | <text class="order-card__distance-bottom">{{ item.takeDistance }}</text> |
| | | </view> |
| | | <view class="order-card__route-main"> |
| | | <view class="order-card__route-item"> |
| | | <view class="order-card__route-texts"> |
| | | <text class="order-card__route-title">{{ item.fromName }}</text> |
| | | <text class="order-card__route-desc">{{ item.fromAddress }}</text> |
| | | <text class="order-card__route-title">{{ item.depositShopName }}</text> |
| | | <text class="order-card__route-desc">{{ item.depositShopAddress }}</text> |
| | | </view> |
| | | <image src="/static/image/ic_daohang@2x.png" mode="widthFix" class="order-card__nav"></image> |
| | | </view> |
| | | <view class="order-card__route-item order-card__route-item--destination"> |
| | | <view class="order-card__route-texts"> |
| | | <text class="order-card__route-title">{{ item.toName }}</text> |
| | | <text class="order-card__route-desc">{{ item.toAddress }}</text> |
| | | <template v-if="item.takeShopId"> |
| | | <text class="order-card__route-title">{{ item.takeName }}</text> |
| | | <text class="order-card__route-desc">{{ item.takeAddress }}</text> |
| | | </template> |
| | | <text v-else class="order-card__route-title">{{ item.takeAddress }}</text> |
| | | </view> |
| | | <image src="/static/image/ic_daohang@2x.png" mode="widthFix" class="order-card__nav"></image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="order-card__goods"> |
| | | <text class="order-card__goods-text">{{ item.goods }}</text> |
| | | <view class="order-card__goods" v-if="item.items && item.items.length > 0"> |
| | | <text class="order-card__goods-text">{{ item.items ? item.items.map(i => `${i.name}*${i.quantity}`).join('、') : '无' }}</text> |
| | | <text class="order-card__goods-arrow">⌄</text> |
| | | </view> |
| | | |
| | | <view class="order-card__actions" :class="'order-card__actions--' + activeTab"> |
| | | <template v-if="activeTab === 'pickup'"> |
| | | <view class="order-card__icon-actions"> |
| | | <view class="order-card__icon-action" @click.stop="show = true"> |
| | | <view class="order-card__icon-action" @click.stop="handleCancelOrder(item)"> |
| | | <image class="order-card__action-icon" src="/static/image/ic_cancle@2x.png" mode="aspectFit"></image> |
| | | <text class="order-card__action-text">取消</text> |
| | | </view> |
| | | <view class="order-card__icon-action" @click.stop> |
| | | <view class="order-card__icon-action" @click.stop="handleCall(item)"> |
| | | <image class="order-card__action-icon" src="/static/image/ic_call@2x.png" mode="aspectFit"></image> |
| | | <text class="order-card__action-text">联系</text> |
| | | </view> |
| | | </view> |
| | | <button class="order-card__button order-card__button--code" hover-class="order-card__button--hover" @click.stop="show1 = true">取货码</button> |
| | | <button class="order-card__button order-card__button--code" hover-class="order-card__button--hover" @click.stop="handleShowPickupCode(item)">取货码</button> |
| | | </template> |
| | | <template v-else-if="activeTab === 'delivering'"> |
| | | <view class="order-card__icon-actions order-card__icon-actions--single"> |
| | | <view class="order-card__icon-action" @click.stop> |
| | | <view class="order-card__icon-action" @click.stop="handleCall(item)"> |
| | | <image class="order-card__action-icon" src="/static/image/ic_call@2x.png" mode="aspectFit"></image> |
| | | <text class="order-card__action-text">联系</text> |
| | | </view> |
| | | </view> |
| | | <button class="order-card__button order-card__button--code" hover-class="order-card__button--hover" @click.stop>存件码</button> |
| | | <button class="order-card__button order-card__button--code" hover-class="order-card__button--hover" @click.stop="handleShowPickupCode(item)">存件码</button> |
| | | </template> |
| | | <button v-else class="order-card__button" hover-class="order-card__button--hover" @click.stop="openDetailPopup(item)">立即抢单</button> |
| | | <button v-else class="order-card__button" hover-class="order-card__button--hover" @click="handleGrabOrder(item)">立即抢单</button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | @cancel="show = false" |
| | | cancelColor="#666666" |
| | | confirmColor="#0055FF" |
| | | title="取消订单确认"> |
| | | title="取消订单确认" |
| | | @confirm="confirmCancelOrder"> |
| | | <view style="text-align: center;color: #333333;font-size: 28rpx;font-weight: 400;"> |
| | | 您今日还可取消 X 次订单,次数用尽后今日将无法接单,是否确认取消? |
| | | 您今日还可取消 {{ cancelRemain }} 次订单,次数用尽后今日将无法接单,是否确认取消? |
| | | </view> |
| | | </u-modal> |
| | | |
| | | <!-- 立即抢单 --> |
| | | <u-modal |
| | | :show="showGrabModal" |
| | | showCancelButton |
| | | @cancel="showGrabModal = false" |
| | | cancelColor="#666666" |
| | | confirmColor="#0055FF" |
| | | title="温馨提示" |
| | | @confirm="confirmGrabOrder"> |
| | | <view style="text-align: center;color: #333333;font-size: 28rpx;font-weight: 400;"> |
| | | {{ selectedGrabOrder && selectedGrabOrder.hasOversized === 1 ? '本订单有特大件尺寸行李,请确认是否继续抢单?' : '是否确认接单?' }} |
| | | </view> |
| | | </u-modal> |
| | | |
| | | <!-- 取货码 --> |
| | | <!-- 取货码/存件码 --> |
| | | <u-popup :show="show1" round="20" mode="bottom"> |
| | | <view class="qrcode"> |
| | | <view class="qrcode-title"> |
| | | <image src="/static/image/ic_close@2x.png" mode="widthFix" style="opacity: 0;"></image> |
| | | <text>取货码</text> |
| | | <text>{{ activeTab === 'delivering' ? '存件码' : '取货码' }}</text> |
| | | <image src="/static/image/ic_close@2x.png" mode="widthFix" @click="show1 = false"></image> |
| | | </view> |
| | | <view class="qrcode-image"> |
| | | <image src="/static/logo.png" mode="widthFix"></image> |
| | | <image v-if="selectedPickupOrder && selectedPickupOrder.driverVerifyCode" :src="'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=' + selectedPickupOrder.driverVerifyCode" mode="widthFix"></image> |
| | | </view> |
| | | <view class="qrcode-btn" hover-class="qrcode-btn--hover" @click="show1 = false">关闭</view> |
| | | </view> |
| | | </u-popup> |
| | | |
| | | <!-- 订单详情 --> |
| | | <u-popup :show="showOrderDetail" round="20" mode="bottom" :overlayStyle="{ background: 'rgba(0, 0, 0, 0.32)' }" @close="showOrderDetail = false"> |
| | | <view class="order-detail" :style="{ height: 'calc(100vh - ' + statusBarHeight + 'px)' }"> |
| | | <view v-if="isStatusDetail" class="order-detail__map-section"> |
| | | <view class="order-detail__map"> |
| | | <view class="order-detail__map-placeholder"></view> |
| | | <view class="order-detail__map-bubble">{{ detailMap.tips }}</view> |
| | | </view> |
| | | <view class="order-detail__status-bar"> |
| | | <view class="order-detail__status-left"> |
| | | <image class="order-detail__status-icon" mode="aspectFit"></image> |
| | | <text class="order-detail__status-name">{{ detailOrder.statusText }}</text> |
| | | </view> |
| | | <view class="order-detail__status-right"> |
| | | <text v-if="detailOrder.showCancelTag" class="order-detail__cancel-tag">取消订单</text> |
| | | <text class="order-detail__status-no">#{{ detailOrder.serialNo }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <scroll-view class="order-detail__scroll" scroll-y> |
| | | |
| | | <view class="order-detail__content"> |
| | | <view class="order-detail__head"> |
| | | <view class="order-detail__time-wrap"> |
| | | <text class="order-detail__time">{{ detailOrder.time }}</text> |
| | | <text class="order-detail__time-sub">送达</text> |
| | | </view> |
| | | <view class="order-detail__price-wrap"> |
| | | <text class="order-detail__price">{{ detailOrder.price }}</text> |
| | | <text class="order-detail__extra">含加急¥{{ detailOrder.extra }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-if="isStatusDetail" class="order-detail__qrcode-section"> |
| | | <view class="order-detail__qrcode-box"> |
| | | <image class="order-detail__qrcode-image" src="/static/logo.png" mode="aspectFit"></image> |
| | | </view> |
| | | <text class="order-detail__qrcode-value">{{ detailOrder.qrcodeValue }}</text> |
| | | <text class="order-detail__qrcode-label">{{ detailOrder.qrcodeLabel }}</text> |
| | | </view> |
| | | |
| | | <view class="order-detail__tags"> |
| | | <view v-for="tag in detailOrder.tags" :key="tag.text" class="order-detail__tag-wrap"> |
| | | <image |
| | | v-if="getTagImage(tag.text)" |
| | | class="order-detail__tag-icon" |
| | | :src="getTagImage(tag.text)" |
| | | mode="widthFix" |
| | | ></image> |
| | | <text v-else class="order-detail__tag" :class="tag.type ? 'order-detail__tag--' + tag.type : ''">{{ tag.text }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="order-detail__route"> |
| | | <view class="order-detail__route-side"> |
| | | <text class="order-detail__distance-top">{{ detailOrder.distanceTop }}</text> |
| | | <view class="order-detail__line"></view> |
| | | <text class="order-detail__distance-bottom">{{ detailOrder.distanceBottom }}</text> |
| | | </view> |
| | | <view class="order-detail__route-main"> |
| | | <view class="order-detail__route-item"> |
| | | <view class="order-detail__route-texts"> |
| | | <text class="order-detail__route-title">{{ detailOrder.fromName }}</text> |
| | | <text class="order-detail__route-desc">{{ detailOrder.fromAddress }}</text> |
| | | </view> |
| | | <view class="order-detail__route-actions"> |
| | | <image class="order-detail__route-icon" src="/static/image/ic_c1all@2x.png" mode="aspectFit"></image> |
| | | <image class="order-detail__route-icon" src="/static/image/ic_daohang@2x.png" mode="aspectFit"></image> |
| | | </view> |
| | | </view> |
| | | <view class="order-detail__route-item order-detail__route-item--destination"> |
| | | <view class="order-detail__route-texts"> |
| | | <text class="order-detail__route-title">{{ detailOrder.toName }}</text> |
| | | <text class="order-detail__route-desc">{{ detailOrder.toAddress }}</text> |
| | | </view> |
| | | <view class="order-detail__route-actions"> |
| | | <image class="order-detail__route-icon order-detail__route-icon--phone" src="/static/image/ic_c1all@2x.png" mode="aspectFit"></image> |
| | | <image class="order-detail__route-icon" src="/static/image/ic_daohang@2x.png" mode="aspectFit"></image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="order-detail__section"> |
| | | <text class="order-detail__section-title">客户信息</text> |
| | | <view class="order-detail__customer"> |
| | | <text class="order-detail__customer-text">{{ detailOrder.customer.name }}(手机号{{ detailOrder.customer.phone }})</text> |
| | | <image class="order-detail__customer-icon" src="/static/image/ic_c1all@2x.png" mode="aspectFit"></image> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="order-detail__section"> |
| | | <text class="order-detail__section-title">物品清单(共{{ detailOrder.goodsList.length }}件)</text> |
| | | <view class="order-detail__goods-list"> |
| | | <view v-for="goods in detailOrder.goodsList" :key="goods.name" class="order-detail__goods-item"> |
| | | <text class="order-detail__goods-name">{{ goods.name }}</text> |
| | | <text class="order-detail__goods-count">x{{ goods.count }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="order-detail__section order-detail__section--photos"> |
| | | <text class="order-detail__section-title">物品信息</text> |
| | | <text class="order-detail__goods-category">{{ detailOrder.goodsCategory }}</text> |
| | | <view class="order-detail__photos"> |
| | | <view v-for="(photo, index) in detailOrder.photos" :key="index" class="order-detail__photo-item"> |
| | | <image class="order-detail__photo" :src="photo" mode="aspectFill"></image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <view class="order-detail__footer"> |
| | | <view class="order-detail__cancel" @click="showOrderDetail = false"> |
| | | <image class="order-detail__cancel-icon" src="/static/image/ic_close2@2x.png" mode="aspectFit"></image> |
| | | </view> |
| | | <button v-if="!isStatusDetail" class="order-detail__confirm" hover-class="order-detail__confirm--hover">确认抢单</button> |
| | | <button v-else class="order-detail__confirm order-detail__confirm--status" hover-class="order-detail__confirm--hover" @click="handleStatusAction"> |
| | | <image class="order-detail__confirm-icon" src="/static/image/ic_photo@2x.png" mode="aspectFit"></image> |
| | | <text>{{ detailPopupType === 'pickup' ? '拍照取货' : '拍照送达' }}</text> |
| | | </button> |
| | | </view> |
| | | </view> |
| | | </u-popup> |
| | | |
| | |
| | | <button class="photo-deliver__submit" hover-class="photo-deliver__submit--hover" @click="showPhotoDeliverPopup = false">确认送达</button> |
| | | </view> |
| | | </u-popup> |
| | | |
| | | <u-picker |
| | | :show="showStatusPicker" |
| | | confirmColor="#10B2FA" |
| | | keyName="text" |
| | | :columns="[statusOptions]" |
| | | @confirm="confirmStatus" |
| | | @cancel="showStatusPicker = false" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { mapState } from 'vuex' |
| | | export default { |
| | | data() { |
| | | return { |
| | | tts: null, |
| | | show: false, |
| | | show1: false, |
| | | showGrabModal: false, |
| | | selectedGrabOrder: null, |
| | | selectedCancelOrder: null, |
| | | cancelRemain: 0, |
| | | selectedPickupOrder: null, |
| | | showPhotoDeliverPopup: false, |
| | | showOrderDetail: false, |
| | | detailPopupType: 'hall', |
| | | routeInfo: null, |
| | | statusBarHeight: 0, |
| | | headerHeight: 0, |
| | | tabbarHeight: 0, |
| | | scrollHeight: 0, |
| | | showFilterPopup: false, |
| | | acceptingStatus: 0, |
| | | showStatusPicker: false, |
| | | statusOptions: [ |
| | | { text: '上线', value: 1 }, |
| | | { text: '下线', value: 0 } |
| | | ], |
| | | centerUserInfo: {}, |
| | | activeTab: 'hall', |
| | | categoryList: [], |
| | | filterSections: [ |
| | | { key: 'sort', title: '排序', options: ['综合排序', '离我最近'] }, |
| | | { key: 'level', title: '物品等级', options: ['不限', '普通物品', '贵重物品', '大件物品', '特殊物品', '其他物品'] }, |
| | | { key: 'sort', title: '排序', options: ['综合排序', '距离最近'] }, |
| | | { key: 'level', title: '物品等级', options: ['不限'] }, |
| | | { key: 'distance', title: '位置范围', options: ['不限', '500m', '1km', '2km', '3km', '4km', '5km', '6km', '7km'] } |
| | | ], |
| | | selectedFilters: { |
| | |
| | | level: '不限', |
| | | distance: '不限' |
| | | }, |
| | | detailOrder: { |
| | | time: '45分钟内', |
| | | price: '¥20.5', |
| | | extra: '3.0', |
| | | serialNo: 1, |
| | | statusText: '抢单大厅', |
| | | qrcodeValue: '767889', |
| | | qrcodeLabel: '取货码', |
| | | mapTips: '', |
| | | showCancelTag: false, |
| | | startPoint: { |
| | | latitude: 31.8269, |
| | | longitude: 117.2334 |
| | | }, |
| | | endPoint: { |
| | | latitude: 31.8435, |
| | | longitude: 117.2852 |
| | | }, |
| | | tags: [ |
| | | { text: '标速达', type: 'blue' }, |
| | | { text: '贵重物品', type: 'orange' } |
| | | ], |
| | | distanceTop: '349m', |
| | | distanceBottom: '12.5km', |
| | | fromName: '中铁快运南站旗舰店', |
| | | fromAddress: '莲花路200号莲花产业园F栋401', |
| | | toName: '佳苑巴黎都市3期10栋301室', |
| | | toAddress: '洞庭湖路与湖北路交叉口西150米', |
| | | customer: { |
| | | name: '刘先生', |
| | | phone: '2878' |
| | | }, |
| | | goodsList: [ |
| | | { name: '大件行李', count: 1 }, |
| | | { name: '中件行李', count: 2 }, |
| | | { name: '小件行李', count: 3 }, |
| | | { name: '背包', count: 2 } |
| | | ], |
| | | goodsCategory: '文件', |
| | | photos: ['/static/logo.png', '/static/logo.png', '/static/logo.png'] |
| | | }, |
| | | stats: [ |
| | | { value: '4.2', label: '服务分' }, |
| | | { value: '234.3', label: '今日预计佣金' }, |
| | | { value: '13', label: '今日接单' } |
| | | { value: '-', label: '服务分' }, |
| | | { value: '-', label: '今日预计佣金' }, |
| | | { value: '-', label: '今日接单' } |
| | | ], |
| | | tabs: [ |
| | | { label: '抢单大厅', value: 'hall' }, |
| | | { label: '待取货', value: 'pickup', count: 2 }, |
| | | { label: '配送中', value: 'delivering', count: 2 } |
| | | { label: '待取货', value: 'pickup' }, |
| | | { label: '配送中', value: 'delivering' } |
| | | ], |
| | | orderList: [ |
| | | { |
| | | id: 1, |
| | | time: '45分钟内', |
| | | price: '¥20.5', |
| | | extra: '3.0', |
| | | startPoint: { |
| | | latitude: 31.829512, |
| | | longitude: 117.239211 |
| | | }, |
| | | endPoint: { |
| | | latitude: 31.841268, |
| | | longitude: 117.278695 |
| | | }, |
| | | tags: [ |
| | | { text: '极速达', type: 'blue' }, |
| | | { text: '贵重物品', type: 'orange' } |
| | | ], |
| | | distanceTop: '349m', |
| | | distanceBottom: '12.5km', |
| | | fromName: '中铁快运南站旗舰店', |
| | | fromAddress: '莲花路200号莲花产业园F栋401', |
| | | toName: '佳苑巴黎都市3期10栋301室', |
| | | toAddress: '洞庭湖路与湖北路交叉口西150米', |
| | | goods: '大件行李*1、中件行李*2、小件行李*3、背包*1' |
| | | }, |
| | | { |
| | | id: 2, |
| | | time: '45分钟内', |
| | | price: '¥20.5', |
| | | extra: '3.0', |
| | | startPoint: { |
| | | latitude: 31.827106, |
| | | longitude: 117.232884 |
| | | }, |
| | | endPoint: { |
| | | latitude: 31.847331, |
| | | longitude: 117.289762 |
| | | }, |
| | | tags: [ |
| | | { text: '极速达', type: 'red' }, |
| | | { text: '大件物品', type: 'blue-light' } |
| | | ], |
| | | distanceTop: '349m', |
| | | distanceBottom: '12.5km', |
| | | fromName: '中铁快运南站旗舰店(合肥南站北广场', |
| | | fromAddress: '莲花路200号莲花产业园F栋401', |
| | | toName: '佳苑巴黎都市3期10栋301室', |
| | | toAddress: '洞庭湖路与湖北路交叉口西150米', |
| | | goods: '大件行李*1' |
| | | }, |
| | | { |
| | | id: 3, |
| | | time: '45分钟内', |
| | | price: '¥20.5', |
| | | extra: '3.0', |
| | | startPoint: { |
| | | latitude: 31.823761, |
| | | longitude: 117.228947 |
| | | }, |
| | | endPoint: { |
| | | latitude: 31.838473, |
| | | longitude: 117.272513 |
| | | }, |
| | | tags: [ |
| | | { text: '极速达', type: 'red' }, |
| | | { text: '大件物品', type: 'blue-light' } |
| | | ], |
| | | distanceTop: '349m', |
| | | distanceBottom: '12.5km', |
| | | fromName: '中铁快运南站旗舰店(合肥南站北广场', |
| | | fromAddress: '莲花路200号莲花产业园F栋401', |
| | | toName: '佳苑巴黎都市3期10栋301室', |
| | | toAddress: '洞庭湖路与湖北路交叉口西150米', |
| | | goods: '大件行李*1' |
| | | } |
| | | ], |
| | | pickupOrderList: [ |
| | | { |
| | | id: 101, |
| | | serialNo: 1, |
| | | statusText: '待取货', |
| | | qrcodeValue: '767889', |
| | | qrcodeLabel: '取货码', |
| | | mapTips: '', |
| | | showCancelTag: true, |
| | | startPoint: { |
| | | latitude: 31.829512, |
| | | longitude: 117.239211 |
| | | }, |
| | | endPoint: { |
| | | latitude: 31.841268, |
| | | longitude: 117.278695 |
| | | }, |
| | | time: '45分钟内', |
| | | price: '¥20.5', |
| | | extra: '3.0', |
| | | tags: [ |
| | | { text: '标速达', type: 'blue' }, |
| | | { text: '贵重物品', type: 'orange' } |
| | | ], |
| | | distanceTop: '349m', |
| | | distanceBottom: '12.5km', |
| | | fromName: '中铁快运南站旗舰店', |
| | | fromAddress: '莲花路200号莲花产业园F栋401', |
| | | toName: '小铁无忧存', |
| | | toAddress: '洞庭湖路与湖北路交叉口西150米', |
| | | goods: '大件行李*1、中件行李*2、小件行李*3、背包*1' |
| | | } |
| | | ], |
| | | deliveringOrderList: [ |
| | | { |
| | | id: 201, |
| | | serialNo: 1, |
| | | statusText: '配送中', |
| | | qrcodeValue: '767889', |
| | | qrcodeLabel: '存件码', |
| | | mapTips: '', |
| | | showCancelTag: false, |
| | | startPoint: { |
| | | latitude: 31.827106, |
| | | longitude: 117.232884 |
| | | }, |
| | | endPoint: { |
| | | latitude: 31.847331, |
| | | longitude: 117.289762 |
| | | }, |
| | | time: '45分钟内', |
| | | price: '¥20.5', |
| | | extra: '3.0', |
| | | tags: [ |
| | | { text: '标速达', type: 'blue' }, |
| | | { text: '贵重物品', type: 'orange' } |
| | | ], |
| | | distanceTop: '349m', |
| | | distanceBottom: '12.5km', |
| | | fromName: '中铁快运南站旗舰店', |
| | | fromAddress: '莲花路200号莲花产业园F栋401', |
| | | toName: '小铁无忧存', |
| | | toAddress: '洞庭湖路与湖北路交叉口西150米', |
| | | goods: '大件行李*1、中件行李*2、小件行李*3、背包*1' |
| | | } |
| | | ] |
| | | orderList: [], |
| | | pickupOrderList: [], |
| | | deliveringOrderList: [], |
| | | hallPage: 1, |
| | | hallPageSize: 10, |
| | | hallLoading: false, |
| | | hallHasMore: true, |
| | | pickupLoading: false, |
| | | deliveringLoading: false, |
| | | activeOrderCount: null |
| | | } |
| | | }, |
| | | |
| | | watch: { |
| | | activeTab(newVal) { |
| | | this.loadOrdersByTab(newVal) |
| | | } |
| | | }, |
| | | |
| | |
| | | this.headerHeight = this.statusBarHeight + uni.upx2px(308) |
| | | this.tabbarHeight = uni.upx2px(100) + safeBottom |
| | | this.scrollHeight = Math.max(windowHeight - this.headerHeight, 0) |
| | | this.loadOrdersByTab(this.activeTab) |
| | | }, |
| | | |
| | | onShow() { |
| | | this.getCenterInfo() |
| | | this.getCategoryListData() |
| | | this.getActiveOrderCount() |
| | | }, |
| | | |
| | | onReachBottom() { |
| | | if (this.activeTab === 'hall') { |
| | | this.hallPage++ |
| | | this.getHallOrders() |
| | | } |
| | | }, |
| | | |
| | | computed: { |
| | | isStatusDetail() { |
| | | return this.detailPopupType === 'pickup' || this.detailPopupType === 'delivering' |
| | | ...mapState(['userInfo', 'token']), |
| | | |
| | | displayTabs() { |
| | | return this.tabs.map(tab => { |
| | | if (tab.value === 'pickup') { |
| | | return { ...tab, count: this.activeOrderCount?.grabbedCount } |
| | | } else if (tab.value === 'delivering') { |
| | | return { ...tab, count: this.activeOrderCount?.deliveringCount } |
| | | } |
| | | return tab |
| | | }) |
| | | }, |
| | | |
| | | detailMap() { |
| | | const fallbackPoint = { |
| | | latitude: 31.8269, |
| | | longitude: 117.2334 |
| | | } |
| | | const startPoint = this.detailOrder.startPoint || fallbackPoint |
| | | const endPoint = this.detailOrder.endPoint || fallbackPoint |
| | | const routePoints = this.routeInfo && this.routeInfo.points && this.routeInfo.points.length ? this.routeInfo.points : [] |
| | | const center = { |
| | | latitude: (startPoint.latitude + endPoint.latitude) / 2, |
| | | longitude: (startPoint.longitude + endPoint.longitude) / 2 |
| | | } |
| | | const distanceKm = this.routeInfo && this.routeInfo.distanceKm ? this.routeInfo.distanceKm : 0 |
| | | const durationMinutes = this.routeInfo && this.routeInfo.durationMinutes ? this.routeInfo.durationMinutes : 0 |
| | | const tips = this.detailOrder.mapTips || (distanceKm ? `剩余${distanceKm.toFixed(1)}km,约${durationMinutes}分钟` : '路线规划中...') |
| | | |
| | | return { |
| | | center, |
| | | markers: [ |
| | | { |
| | | id: 1, |
| | | latitude: startPoint.latitude, |
| | | longitude: startPoint.longitude, |
| | | iconPath: '/static/image/map_marker_start.svg', |
| | | width: 32, |
| | | height: 38, |
| | | anchor: { |
| | | x: 0.5, |
| | | y: 1 |
| | | } |
| | | }, |
| | | { |
| | | id: 2, |
| | | latitude: endPoint.latitude, |
| | | longitude: endPoint.longitude, |
| | | iconPath: '/static/image/map_marker_end.svg', |
| | | width: 32, |
| | | height: 38, |
| | | anchor: { |
| | | x: 0.5, |
| | | y: 1 |
| | | } |
| | | } |
| | | ], |
| | | polyline: [ |
| | | ...(routePoints.length ? [{ |
| | | points: routePoints, |
| | | color: '#2A7FFF', |
| | | width: 8, |
| | | dottedLine: false, |
| | | arrowLine: true |
| | | }] : []) |
| | | ], |
| | | includePoints: routePoints.length ? routePoints : [startPoint, endPoint], |
| | | scale: 12, |
| | | tips |
| | | } |
| | | }, |
| | | |
| | | |
| | | currentOrderList() { |
| | | const orderMap = { |
| | | hall: this.orderList, |
| | |
| | | }, |
| | | |
| | | methods: { |
| | | calculateDistance(startPoint, endPoint) { |
| | | if (!startPoint || !endPoint) { |
| | | return 0 |
| | | } |
| | | |
| | | const toRad = (value) => value * Math.PI / 180 |
| | | const earthRadius = 6371 |
| | | const deltaLat = toRad(endPoint.latitude - startPoint.latitude) |
| | | const deltaLng = toRad(endPoint.longitude - startPoint.longitude) |
| | | const lat1 = toRad(startPoint.latitude) |
| | | const lat2 = toRad(endPoint.latitude) |
| | | const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2) |
| | | const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) |
| | | |
| | | return earthRadius * c |
| | | getActiveOrderCount() { |
| | | this.$u.api.activeOrderCount().then(res => { |
| | | if (res.code === 200) { |
| | | this.activeOrderCount = res.data |
| | | } |
| | | }).catch((err) => { |
| | | this.activeOrderCount = null |
| | | }) |
| | | }, |
| | | |
| | | estimateDuration(distanceKm) { |
| | | if (!distanceKm) { |
| | | return 1 |
| | | } |
| | | |
| | | const averageSpeedKmPerHour = 35 |
| | | return Math.max(1, Math.round(distanceKm / averageSpeedKmPerHour * 60)) |
| | | handleGrabOrder(item) { |
| | | this.selectedGrabOrder = item |
| | | this.showGrabModal = true |
| | | }, |
| | | |
| | | handleStatusAction() { |
| | | if (this.detailPopupType === 'delivering') { |
| | | this.showOrderDetail = false |
| | | this.showPhotoDeliverPopup = true |
| | | confirmGrabOrder() { |
| | | if (!this.selectedGrabOrder) return |
| | | this.$u.api.grabOrder({ orderId: this.selectedGrabOrder.id }).then(res => { |
| | | this.showGrabModal = false |
| | | if (res.code === 200) { |
| | | uni.showToast({ title: '抢单成功', icon: 'success' }) |
| | | this.hallPage = 1 |
| | | this.hallHasMore = true |
| | | this.orderList = [] |
| | | this.getHallOrders() |
| | | this.getActiveOrderCount() |
| | | this.getCenterInfo() |
| | | } else { |
| | | uni.showToast({ title: res.message || '抢单失败', icon: 'none' }) |
| | | } |
| | | }).catch(() => { |
| | | this.showGrabModal = false |
| | | }) |
| | | }, |
| | | |
| | | handleCancelOrder(item) { |
| | | this.selectedCancelOrder = item |
| | | this.$u.api.cancelLimit().then(res => { |
| | | if (res.code === 200) { |
| | | this.cancelRemain = res.data.remain |
| | | } |
| | | }).finally(() => { |
| | | this.show = true |
| | | }) |
| | | }, |
| | | |
| | | confirmCancelOrder() { |
| | | if (!this.selectedCancelOrder) return |
| | | this.$u.api.cancelOrder({ orderId: this.selectedCancelOrder.id }).then(res => { |
| | | this.show = false |
| | | if (res.code === 200) { |
| | | uni.showToast({ title: '取消成功', icon: 'success' }) |
| | | this.getPickupOrders() |
| | | this.getCenterInfo() |
| | | this.getActiveOrderCount() |
| | | } else { |
| | | uni.showToast({ title: res.message || '取消失败', icon: 'none' }) |
| | | } |
| | | }).catch(() => { |
| | | this.show = false |
| | | }) |
| | | }, |
| | | |
| | | handleCall(item) { |
| | | if (item.contactPhone) { |
| | | uni.makePhoneCall({ |
| | | phoneNumber: item.contactPhone |
| | | }) |
| | | } |
| | | }, |
| | | |
| | | openDetailPopup(item) { |
| | | this.detailPopupType = this.activeTab |
| | | this.routeInfo = null |
| | | this.detailOrder = { |
| | | ...this.detailOrder, |
| | | ...item, |
| | | customer: item.customer || this.detailOrder.customer, |
| | | goodsList: item.goodsList || this.detailOrder.goodsList, |
| | | goodsCategory: item.goodsCategory || this.detailOrder.goodsCategory, |
| | | photos: item.photos || this.detailOrder.photos |
| | | } |
| | | this.showOrderDetail = true |
| | | |
| | | if (this.activeTab === 'pickup' || this.activeTab === 'delivering') { |
| | | this.fetchDrivingRoute(this.detailOrder) |
| | | } |
| | | handleShowPickupCode(item) { |
| | | this.selectedPickupOrder = item |
| | | this.show1 = true |
| | | }, |
| | | |
| | | fetchDrivingRoute(order) { |
| | | if (!order || !order.startPoint || !order.endPoint) { |
| | | return |
| | | } |
| | | goToOrderDetail(item, index) { |
| | | uni.navigateTo({ |
| | | url: `/pages/order-detail/order-detail?id=${item.id}&index=${index + 1}` |
| | | }) |
| | | }, |
| | | |
| | | const origin = `${order.startPoint.longitude},${order.startPoint.latitude}` |
| | | const destination = `${order.endPoint.longitude},${order.endPoint.latitude}` |
| | | |
| | | uni.request({ |
| | | url: 'https://restapi.amap.com/v3/direction/driving', |
| | | method: 'GET', |
| | | data: { |
| | | key: 'e4d46c87adf151dca20060317592b1b6', |
| | | origin, |
| | | destination, |
| | | extensions: 'all', |
| | | strategy: 0, |
| | | output: 'json' |
| | | }, |
| | | success: (response) => { |
| | | const path = response.data && response.data.route && response.data.route.paths && response.data.route.paths[0] |
| | | if (!path) { |
| | | this.routeInfo = this.buildMockRouteInfo(order.startPoint, order.endPoint) |
| | | return |
| | | getCenterInfo() { |
| | | this.$u.api.centerInfo().then(res => { |
| | | if (res.code === 200) { |
| | | this.acceptingStatus = res.data.acceptingStatus |
| | | this.centerUserInfo = { |
| | | fullImgUrl: res.data.fullImgUrl, |
| | | name: res.data.name |
| | | } |
| | | |
| | | const points = this.parseRoutePoints(path.steps || [], order.startPoint, order.endPoint) |
| | | const distanceKm = Number(path.distance || 0) / 1000 |
| | | const durationMinutes = Math.max(1, Math.round(Number(path.duration || 0) / 60)) |
| | | |
| | | this.routeInfo = { |
| | | points, |
| | | distanceKm: distanceKm || this.calculateDistance(order.startPoint, order.endPoint), |
| | | durationMinutes |
| | | } |
| | | }, |
| | | fail: () => { |
| | | this.routeInfo = this.buildMockRouteInfo(order.startPoint, order.endPoint) |
| | | this.stats = [ |
| | | { value: res.data.score, label: '服务分' }, |
| | | { value: res.data.todayCommission, label: '今日预计佣金' }, |
| | | { value: res.data.todayOrderCount, label: '今日接单' } |
| | | ] |
| | | } |
| | | }) |
| | | }, |
| | | |
| | | buildMockRouteInfo(startPoint, endPoint) { |
| | | const middleLatitude = (startPoint.latitude + endPoint.latitude) / 2 |
| | | const middleLongitude = (startPoint.longitude + endPoint.longitude) / 2 |
| | | const points = [ |
| | | startPoint, |
| | | { |
| | | latitude: startPoint.latitude + (middleLatitude - startPoint.latitude) * 0.65, |
| | | longitude: startPoint.longitude + 0.008 |
| | | }, |
| | | { |
| | | latitude: middleLatitude + 0.004, |
| | | longitude: middleLongitude + 0.012 |
| | | }, |
| | | { |
| | | latitude: endPoint.latitude - 0.003, |
| | | longitude: endPoint.longitude - 0.008 |
| | | }, |
| | | endPoint |
| | | ] |
| | | const distanceKm = this.calculatePathDistance(points) |
| | | const durationMinutes = this.estimateDuration(distanceKm) |
| | | |
| | | return { |
| | | points, |
| | | distanceKm, |
| | | durationMinutes |
| | | } |
| | | }, |
| | | |
| | | parseRoutePoints(steps, startPoint, endPoint) { |
| | | const points = [] |
| | | |
| | | steps.forEach((step) => { |
| | | const polyline = step.polyline || '' |
| | | polyline.split(';').forEach((pointText) => { |
| | | const [longitude, latitude] = pointText.split(',').map(Number) |
| | | if (!Number.isNaN(latitude) && !Number.isNaN(longitude)) { |
| | | points.push({ latitude, longitude }) |
| | | } |
| | | }) |
| | | getCategoryListData() { |
| | | this.$u.api.getCategoryList({ type: 3 }).then(res => { |
| | | if (res.code === 200) { |
| | | this.categoryList = res.data || [] |
| | | this.filterSections.forEach(section => { |
| | | if (section.key === 'level') { |
| | | section.options = ['不限', ...this.categoryList.map(item => item.name)] |
| | | } |
| | | }) |
| | | } |
| | | }) |
| | | |
| | | if (!points.length) { |
| | | return [startPoint, endPoint] |
| | | } |
| | | |
| | | return points |
| | | }, |
| | | |
| | | calculatePathDistance(points) { |
| | | if (!points || points.length < 2) { |
| | | return 0 |
| | | loadOrdersByTab(tab) { |
| | | if (tab === 'hall') { |
| | | this.hallPage = 1 |
| | | this.hallHasMore = true |
| | | this.orderList = [] |
| | | this.getHallOrders() |
| | | } else if (tab === 'pickup') { |
| | | this.pickupOrderList = [] |
| | | this.getPickupOrders() |
| | | } else if (tab === 'delivering') { |
| | | this.deliveringOrderList = [] |
| | | this.getDeliveringOrders() |
| | | } |
| | | }, |
| | | |
| | | getHallOrders() { |
| | | if (this.hallLoading || !this.hallHasMore) { |
| | | return |
| | | } |
| | | this.hallLoading = true |
| | | |
| | | let distance = null |
| | | if (this.selectedFilters.distance !== '不限') { |
| | | const distanceText = this.selectedFilters.distance |
| | | if (distanceText.includes('km')) { |
| | | distance = parseInt(distanceText) * 1000 |
| | | } else { |
| | | distance = parseInt(distanceText) |
| | | } |
| | | } |
| | | |
| | | let totalDistance = 0 |
| | | for (let index = 1; index < points.length; index += 1) { |
| | | totalDistance += this.calculateDistance(points[index - 1], points[index]) |
| | | const sortTypeMap = { |
| | | '综合排序': 1, |
| | | '距离最近': 2 |
| | | } |
| | | const sortType = this.selectedFilters.sort !== '不限' ? (sortTypeMap[this.selectedFilters.sort] || null) : null |
| | | |
| | | let gradeId = null |
| | | if (this.selectedFilters.level !== '不限') { |
| | | const selectedCategory = this.categoryList.find(item => item.name === this.selectedFilters.level) |
| | | if (selectedCategory) { |
| | | gradeId = selectedCategory.id |
| | | } |
| | | } |
| | | |
| | | return totalDistance |
| | | console.log('接单大厅:', { distance, gradeId, sortType }) |
| | | this.$u.api.grabOrderHall({ |
| | | capacity: this.hallPageSize, |
| | | page: this.hallPage, |
| | | model: { |
| | | distance: distance, |
| | | gradeId: gradeId, |
| | | sortType: sortType |
| | | } |
| | | }).then(res => { |
| | | console.log('接单大厅', res) |
| | | this.hallLoading = false |
| | | if (res.code === 200) { |
| | | const list = res.data.records || [] |
| | | this.orderList = this.hallPage === 1 ? list : this.orderList.concat(list) |
| | | this.hallHasMore = list.length >= this.hallPageSize |
| | | } |
| | | }).catch((err) => { |
| | | this.hallLoading = false |
| | | }) |
| | | }, |
| | | |
| | | getPickupOrders() { |
| | | if (this.pickupLoading) return |
| | | this.pickupLoading = true |
| | | this.$u.api.activeOrders({ status: 3 }).then(res => { |
| | | console.log('待取货:', res) |
| | | this.pickupLoading = false |
| | | if (res.code === 200) { |
| | | this.pickupOrderList = res.data.records || res.data || [] |
| | | } |
| | | }).catch((err) => { |
| | | this.pickupLoading = false |
| | | }) |
| | | }, |
| | | |
| | | getDeliveringOrders() { |
| | | if (this.deliveringLoading) return |
| | | this.deliveringLoading = true |
| | | this.$u.api.activeOrders({ status: 4 }).then(res => { |
| | | console.log('配送中:', res) |
| | | this.deliveringLoading = false |
| | | if (res.code === 200) { |
| | | this.deliveringOrderList = res.data || [] |
| | | } |
| | | }).catch((err) => { |
| | | this.deliveringLoading = false |
| | | }) |
| | | }, |
| | | |
| | | openStatusPicker() { |
| | | this.showStatusPicker = true |
| | | }, |
| | | |
| | | confirmStatus(e) { |
| | | this.showStatusPicker = false |
| | | const selectedValue = e.value[0] |
| | | this.$u.api.updateAcceptingStatus({ status: selectedValue.value }).then(res => { |
| | | if (res.code === 200) { |
| | | this.getCenterInfo() |
| | | } |
| | | }) |
| | | }, |
| | | |
| | | toDriverCertification() { |
| | | uni.navigateTo({ |
| | | url: '/pages/driver-certification/driver-certification' |
| | | }) |
| | | }, |
| | | |
| | | toggleFilterPopup(show) { |
| | |
| | | level: '不限', |
| | | distance: '不限' |
| | | } |
| | | this.showFilterPopup = false |
| | | if (this.activeTab === 'hall') { |
| | | this.hallPage = 1 |
| | | this.hallHasMore = true |
| | | this.orderList = [] |
| | | this.getHallOrders() |
| | | } |
| | | }, |
| | | |
| | | confirmFilters() { |
| | | this.showFilterPopup = false |
| | | }, |
| | | |
| | | getTagImage(tagText) { |
| | | const tagImageMap = { |
| | | '极速达': '/static/image/ic_jisuda@2x.png', |
| | | '标速达': '/static/image/ic_biaosuda@2x.png' |
| | | if (this.activeTab === 'hall') { |
| | | this.hallPage = 1 |
| | | this.hallHasMore = true |
| | | this.orderList = [] |
| | | this.getHallOrders() |
| | | } |
| | | |
| | | return tagImageMap[tagText] || '' |
| | | }, |
| | | |
| | | initTTS() { |
| | |
| | | background: #f5f6f8; |
| | | overflow: hidden; |
| | | |
| | | .order-detail-map-layer { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | height: 330rpx; |
| | | z-index: 10081; |
| | | overflow: hidden; |
| | | border-top-left-radius: 28rpx; |
| | | border-top-right-radius: 28rpx; |
| | | |
| | | &__map { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | } |
| | | |
| | | .qrcode { |
| | | padding: 36rpx 30rpx; |
| | | box-sizing: border-box; |
| | |
| | | } |
| | | } |
| | | |
| | | .order-detail { |
| | | display: flex; |
| | | flex-direction: column; |
| | | background: #ffffff; |
| | | border-top-left-radius: 28rpx; |
| | | border-top-right-radius: 28rpx; |
| | | overflow: hidden; |
| | | |
| | | &__scroll { |
| | | flex: 1; |
| | | min-height: 0; |
| | | } |
| | | |
| | | &__map-section { |
| | | background: #ffffff; |
| | | } |
| | | |
| | | &__map { |
| | | position: relative; |
| | | height: 330rpx; |
| | | background: #eef5ff; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | &__map-view { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | &__map-placeholder { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | &__map-bubble { |
| | | position: absolute; |
| | | left: 26rpx; |
| | | bottom: 44rpx; |
| | | padding: 12rpx 18rpx; |
| | | border-radius: 12rpx; |
| | | background: rgba(255, 255, 255, 0.96); |
| | | box-shadow: 0 10rpx 20rpx rgba(33, 92, 182, 0.08); |
| | | font-size: 26rpx; |
| | | font-weight: 500; |
| | | color: #2f6ff2; |
| | | } |
| | | |
| | | &__status-bar { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 0 24rpx; |
| | | height: 92rpx; |
| | | background: #d9e8ff; |
| | | } |
| | | |
| | | &__status-left, |
| | | &__status-right { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | &__status-icon { |
| | | width: 28rpx; |
| | | height: 28rpx; |
| | | margin-right: 12rpx; |
| | | border-radius: 6rpx; |
| | | background: #7ea7ef; |
| | | } |
| | | |
| | | &__status-name, |
| | | &__status-no { |
| | | font-size: 34rpx; |
| | | font-weight: 700; |
| | | color: #2b3139; |
| | | } |
| | | |
| | | &__cancel-tag { |
| | | padding: 8rpx 16rpx; |
| | | margin-right: 18rpx; |
| | | border: 2rpx solid #6ea6ff; |
| | | border-radius: 999rpx; |
| | | font-size: 24rpx; |
| | | color: #1d73ff; |
| | | background: rgba(255, 255, 255, 0.72); |
| | | } |
| | | |
| | | &__content { |
| | | padding: 28rpx 24rpx 36rpx; |
| | | } |
| | | |
| | | &__head { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | } |
| | | |
| | | &__time-wrap { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | &__time { |
| | | font-size: 42rpx; |
| | | font-weight: 700; |
| | | color: #ff8d27; |
| | | } |
| | | |
| | | &__time-sub { |
| | | margin-left: 8rpx; |
| | | font-size: 28rpx; |
| | | color: #a4a9b2; |
| | | } |
| | | |
| | | &__price-wrap { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: flex-end; |
| | | } |
| | | |
| | | &__price { |
| | | font-size: 44rpx; |
| | | font-weight: 700; |
| | | color: #ff3b30; |
| | | } |
| | | |
| | | &__extra { |
| | | margin-top: 6rpx; |
| | | font-size: 24rpx; |
| | | color: #a0a5af; |
| | | } |
| | | |
| | | &__tags { |
| | | display: flex; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | gap: 10rpx; |
| | | margin-top: 14rpx; |
| | | } |
| | | |
| | | &__tag-wrap { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | &__tag-icon { |
| | | width: 108rpx; |
| | | height: 40rpx; |
| | | } |
| | | |
| | | &__tag { |
| | | padding: 4rpx 10rpx; |
| | | border-radius: 8rpx; |
| | | font-size: 22rpx; |
| | | line-height: 1.2; |
| | | border: 1rpx solid #2473f5; |
| | | color: #2473f5; |
| | | |
| | | &--orange { |
| | | border-color: #ff9c45; |
| | | background: #ff9c45; |
| | | color: #ffffff; |
| | | } |
| | | } |
| | | |
| | | &__route { |
| | | display: flex; |
| | | margin-top: 20rpx; |
| | | padding-bottom: 26rpx; |
| | | border-bottom: 1rpx solid #edf0f3; |
| | | } |
| | | |
| | | &__route-side { |
| | | width: 64rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | &__distance-top, |
| | | &__distance-bottom { |
| | | font-size: 22rpx; |
| | | font-weight: 600; |
| | | color: #555b66; |
| | | text-align: center; |
| | | } |
| | | |
| | | &__line { |
| | | position: relative; |
| | | width: 4rpx; |
| | | flex: 1; |
| | | min-height: 92rpx; |
| | | margin: 10rpx 0; |
| | | border-radius: 999rpx; |
| | | background: #d8dbe1; |
| | | |
| | | &::before, |
| | | &::after { |
| | | content: ''; |
| | | position: absolute; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | width: 14rpx; |
| | | height: 14rpx; |
| | | border-radius: 50%; |
| | | background: #6a6f79; |
| | | } |
| | | |
| | | &::before { |
| | | top: -4rpx; |
| | | } |
| | | |
| | | &::after { |
| | | bottom: -4rpx; |
| | | } |
| | | } |
| | | |
| | | &__route-main { |
| | | flex: 1; |
| | | } |
| | | |
| | | &__route-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | gap: 18rpx; |
| | | |
| | | &--destination { |
| | | margin-top: 24rpx; |
| | | } |
| | | } |
| | | |
| | | &__route-texts { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | |
| | | &__route-title { |
| | | display: block; |
| | | font-size: 40rpx; |
| | | font-weight: 700; |
| | | color: #2d3139; |
| | | line-height: 1.3; |
| | | } |
| | | |
| | | &__route-desc { |
| | | display: block; |
| | | margin-top: 8rpx; |
| | | font-size: 28rpx; |
| | | color: #9ea4ae; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | &__route-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | &__route-icon, |
| | | &__customer-icon { |
| | | width: 48rpx; |
| | | height: 48rpx; |
| | | border: 2rpx dashed #c9ced6; |
| | | border-radius: 8rpx; |
| | | background: #f7f8fa; |
| | | } |
| | | |
| | | &__cancel-icon { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | } |
| | | |
| | | &__qrcode-section { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | padding: 34rpx 0 8rpx; |
| | | border-bottom: 1rpx solid #edf0f3; |
| | | } |
| | | |
| | | &__qrcode-box { |
| | | width: 260rpx; |
| | | height: 260rpx; |
| | | padding: 16rpx; |
| | | border: 2rpx solid #edf0f3; |
| | | border-radius: 12rpx; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | &__qrcode-image { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | &__qrcode-value { |
| | | margin-top: 18rpx; |
| | | font-size: 44rpx; |
| | | font-weight: 700; |
| | | color: #31363f; |
| | | } |
| | | |
| | | &__qrcode-label { |
| | | margin-top: 8rpx; |
| | | font-size: 26rpx; |
| | | color: #b0b4bc; |
| | | } |
| | | |
| | | &__section { |
| | | padding: 28rpx 0; |
| | | border-bottom: 1rpx solid #edf0f3; |
| | | |
| | | &--photos { |
| | | border-bottom: 0; |
| | | } |
| | | } |
| | | |
| | | &__section-title { |
| | | display: block; |
| | | font-size: 32rpx; |
| | | font-weight: 700; |
| | | color: #2b3139; |
| | | } |
| | | |
| | | &__customer { |
| | | margin-top: 28rpx; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | gap: 20rpx; |
| | | } |
| | | |
| | | &__customer-text { |
| | | flex: 1; |
| | | font-size: 32rpx; |
| | | color: #41464f; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | &__goods-list { |
| | | margin-top: 22rpx; |
| | | } |
| | | |
| | | &__goods-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 10rpx 0; |
| | | } |
| | | |
| | | &__goods-name { |
| | | font-size: 32rpx; |
| | | color: #41464f; |
| | | } |
| | | |
| | | &__goods-count { |
| | | font-size: 30rpx; |
| | | color: #a1a6af; |
| | | } |
| | | |
| | | &__goods-category { |
| | | display: block; |
| | | margin-top: 24rpx; |
| | | font-size: 32rpx; |
| | | color: #41464f; |
| | | } |
| | | |
| | | &__photos { |
| | | display: flex; |
| | | gap: 16rpx; |
| | | margin-top: 22rpx; |
| | | } |
| | | |
| | | &__photo-item { |
| | | width: 120rpx; |
| | | height: 120rpx; |
| | | border-radius: 12rpx; |
| | | overflow: hidden; |
| | | background: #f3f5f8; |
| | | } |
| | | |
| | | &__photo { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | &__footer { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 18rpx; |
| | | padding: 18rpx 24rpx calc(env(safe-area-inset-bottom) + 18rpx); |
| | | background: #ffffff; |
| | | box-shadow: 0 -8rpx 24rpx rgba(16, 27, 49, 0.04); |
| | | } |
| | | |
| | | &__cancel { |
| | | width: 176rpx; |
| | | height: 100rpx; |
| | | border-radius: 50rpx; |
| | | background: #E5E5E5; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | &__confirm { |
| | | flex: 1; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | gap: 14rpx; |
| | | height: 100rpx; |
| | | line-height: 100rpx; |
| | | border-radius: 50rpx; |
| | | background: #106EFA; |
| | | font-size: 36rpx; |
| | | font-weight: 600; |
| | | color: #ffffff; |
| | | border: 0; |
| | | padding: 0; |
| | | |
| | | &::after { |
| | | border: 0; |
| | | } |
| | | |
| | | &--hover { |
| | | opacity: 0.92; |
| | | } |
| | | } |
| | | |
| | | &__confirm-icon { |
| | | width: 44rpx; |
| | | height: 44rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | } |
| | | |
| | | &__header { |
| | | position: fixed; |
| | | left: 0; |
| | |
| | | border-radius: 50%; |
| | | background: #32d74b; |
| | | margin-right: 10rpx; |
| | | |
| | | &--offline { |
| | | background: #ff3b30; |
| | | } |
| | | } |
| | | |
| | | &__status-text, |
| | |
| | | } |
| | | |
| | | &__list { |
| | | padding: 22rpx 30rpx calc(22rpx + env(safe-area-inset-bottom)) 30rpx; |
| | | padding: 30rpx; |
| | | } |
| | | |
| | | &__verified { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | image { |
| | | width: 320rpx; |
| | | height: 320rpx; |
| | | } |
| | | button { |
| | | width: 160rpx; |
| | | height: 64rpx; |
| | | line-height: 64rpx; |
| | | text-align: center; |
| | | padding: 0 !important; |
| | | border: 0 !important; |
| | | background: #106EFA; |
| | | border-radius: 50rpx; |
| | | margin-top: 40rpx; |
| | | font-weight: 400; |
| | | font-size: 28rpx; |
| | | color: #FFFFFF; |
| | | } |
| | | } |
| | | |
| | | &__empty { |
| | |
| | | background: rgba(0, 0, 0, 0.24); |
| | | |
| | | &__panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100%; |
| | | padding: 22rpx 0 26rpx; |
| | | background: #ffffff; |
| | | border-bottom-left-radius: 28rpx; |
| | | border-bottom-right-radius: 28rpx; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | &__content { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | padding: 22rpx 18rpx 0; |
| | | } |
| | | |
| | | &__section { |
| | | padding: 0 18rpx; |
| | | margin-bottom: 28rpx; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | &__actions { |
| | | flex-shrink: 0; |
| | | display: flex; |
| | | gap: 24rpx; |
| | | padding: 8rpx 18rpx 0; |
| | | padding: 8rpx 18rpx 26rpx; |
| | | } |
| | | |
| | | &__button { |
| | |
| | | height: 40rpx; |
| | | } |
| | | |
| | | &__tag-img { |
| | | width: 108rpx; |
| | | height: 40rpx; |
| | | } |
| | | |
| | | &__tag { |
| | | padding: 4rpx 10rpx; |
| | | border-radius: 8rpx; |
| | | font-size: 22rpx; |
| | | line-height: 1.2; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border: 1rpx solid #2473f5; |
| | | color: #2473f5; |
| | | |
| | | &--orange { |
| | | border-color: #ff9c45; |
| | | background: #ff9c45; |
| | | border: none; |
| | | background: linear-gradient(319deg, #EE9D0E 0%, #FF4E4E 100%); |
| | | color: #ffffff; |
| | | } |
| | | |
| | |
| | | border-color: #74a9ff; |
| | | color: #74a9ff; |
| | | } |
| | | |
| | | &--blue { |
| | | background: rgba(16,178,250,0.08); |
| | | border-radius: 15rpx; |
| | | border: 2rpx solid #106EFA; |
| | | font-weight: 400; |
| | | font-size: 22rpx; |
| | | color: #106EFA; |
| | | } |
| | | } |
| | | |
| | | &__route { |