| | |
| | | <template> |
| | | <view class="hall-page"> |
| | | <view class="hall-page__header" :style="{ paddingTop: statusBarHeight + 'px' }"> |
| | | <view class="hall-page__user-row"> |
| | | <view class="hall-page__user-row" @click="handleUserClick"> |
| | | <view class="hall-page__user"> |
| | | <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> |
| | |
| | | <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-else-if="userInfo.auditStatus === 3 && acceptingStatus === 1 && 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.remainMinutes }}</text> |
| | | <text class="order-card__time-sub">分钟</text> |
| | | <view class="order-card__time" v-if="item.remainMinutes > 0"> |
| | | <text class="order-card__time-main">{{ formatRemainTime(item.remainMinutes) }}</text> |
| | | <text class="order-card__time-sub">送达</text> |
| | | </view> |
| | | <view class="order-card__time" v-else> |
| | | <text class="order-card__time-main">配送已超时,请尽快送达</text> |
| | | </view> |
| | | <view v-if="activeTab === 'hall'" class="order-card__price-wrap"> |
| | | <text class="order-card__price">¥{{ (item.driverFee / 100).toFixed(1) }}</text> |
| | | <text class="order-card__price">¥{{ item.platformRewardAmount ? (item.driverFee + item.platformRewardAmount) / 100 : (item.driverFee / 100).toFixed(2) }}</text> |
| | | </view> |
| | | <view v-else class="order-card__price-wrap order-card__price-wrap--serial-only"> |
| | | <text v-if="item.code" class="order-card__serial">#{{ index + 1 }}</text> |
| | |
| | | <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' && item.urgentAmount" class="order-card__extra">含加急¥{{ item.urgentAmount / 100 }}</text> |
| | | <text v-if="activeTab === 'hall' && item.platformRewardAmount" class="order-card__extra">含加急¥{{ (item.platformRewardAmount / 100).toFixed(2) }}</text> |
| | | </view> |
| | | |
| | | <view class="order-card__route"> |
| | | <view class="order-card__route-side"> |
| | | <text class="order-card__distance-top">{{ item.depositDistance }}</text> |
| | | <view class="order-card__distance-block order-card__distance-block--top"> |
| | | <text class="order-card__distance-value">{{ formatDistanceParts(item.depositDistance).value }}</text> |
| | | <text class="order-card__distance-unit">{{ formatDistanceParts(item.depositDistance).unit }}</text> |
| | | </view> |
| | | <view class="order-card__line"></view> |
| | | <text class="order-card__distance-bottom">{{ item.takeDistance }}</text> |
| | | <view class="order-card__distance-block order-card__distance-block--bottom"> |
| | | <text class="order-card__distance-value">{{ formatDistanceParts(item.takeDistance).value }}</text> |
| | | <text class="order-card__distance-unit">{{ formatDistanceParts(item.takeDistance).unit }}</text> |
| | | </view> |
| | | </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.depositShopName }}</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> |
| | | <image src="/static/image/ic_daohang@2x.png" mode="widthFix" class="order-card__nav" @click.stop="navigateToAddress(item, 'deposit')"></image> |
| | | </view> |
| | | <view class="order-card__route-item order-card__route-item--destination"> |
| | | <view class="order-card__route-texts"> |
| | |
| | | </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> |
| | | <image src="/static/image/ic_daohang@2x.png" mode="widthFix" class="order-card__nav" @click.stop="navigateToAddress(item, 'take')"></image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <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 class="order-card__goods" v-if="item.items && item.items.length > 0" @click.stop="toggleGoodsExpand(item.id)"> |
| | | <text class="order-card__goods-text">{{ getGoodsText(item.items, item.id) }}</text> |
| | | <u-icon v-if="getGoodsText(item.items, item.id).length > 20" name="arrow-down" :class="{ 'order-card__goods-arrow--expanded': expandedGoodsIds.includes(item.id) }" size="12" color="#a4a9b1"></u-icon> |
| | | </view> |
| | | |
| | | <view class="order-card__actions" :class="'order-card__actions--' + activeTab"> |
| | |
| | | </view> |
| | | <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="handleGrabOrder(item)">立即抢单</button> |
| | | <button v-else class="order-card__button" hover-class="order-card__button--hover" @click.stop="handleGrabOrder(item)">立即抢单</button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-else class="hall-page__empty"> |
| | | <image class="hall-page__empty-icon" src="/static/image/default_nodata_grey@2x.png" mode="aspectFit"></image> |
| | | <image class="hall-page__empty-icon" :src="acceptingStatus === 0 ? '/static/image/default_nodata_grey@2x.png' : '/static/image/default_nodata@2x.png'" mode="aspectFit"></image> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | |
| | | ], |
| | | centerUserInfo: {}, |
| | | activeTab: 'hall', |
| | | expandedGoodsIds: [], |
| | | categoryList: [], |
| | | filterSections: [ |
| | | { key: 'sort', title: '排序', options: ['综合排序', '距离最近'] }, |
| | |
| | | this.headerHeight = this.statusBarHeight + uni.upx2px(308) |
| | | this.tabbarHeight = uni.upx2px(100) + safeBottom |
| | | this.scrollHeight = Math.max(windowHeight - this.headerHeight, 0) |
| | | if (this.userInfo.auditStatus === 99) return; |
| | | this.acceptingStatus = this.userInfo.acceptingStatus || 0 |
| | | this.loadOrdersByTab(this.activeTab) |
| | | }, |
| | | |
| | |
| | | }, |
| | | |
| | | onReachBottom() { |
| | | if (this.acceptingStatus === 0) return; |
| | | if (this.activeTab === 'hall') { |
| | | this.hallPage++ |
| | | this.getHallOrders() |
| | |
| | | }, |
| | | |
| | | methods: { |
| | | formatRemainTime(minutes) { |
| | | if (!minutes) return 0 |
| | | if (minutes === 0) { |
| | | return '配送已超时' |
| | | } |
| | | if (minutes >= 60) { |
| | | return (minutes / 60).toFixed(2) + '小时内' |
| | | } |
| | | return minutes + '分钟内' |
| | | }, |
| | | |
| | | getGoodsText(items, itemId) { |
| | | if (!items || items.length === 0) return '无' |
| | | const text = items.map(i => `${i.name}*${i.quantity}`).join('、') |
| | | if (text.length > 20 && !this.expandedGoodsIds.includes(itemId)) { |
| | | return text.substring(0, 20) + '...' |
| | | } |
| | | return text |
| | | }, |
| | | |
| | | toggleGoodsExpand(id) { |
| | | const index = this.expandedGoodsIds.indexOf(id) |
| | | if (index > -1) { |
| | | this.expandedGoodsIds.splice(index, 1) |
| | | } else { |
| | | this.expandedGoodsIds.push(id) |
| | | } |
| | | }, |
| | | |
| | | formatDistanceParts(distance) { |
| | | if (distance === null || distance === undefined || distance === '') { |
| | | return { value: '--', unit: '' } |
| | | } |
| | | |
| | | if (typeof distance === 'number') { |
| | | if (distance >= 1000) { |
| | | return { value: (distance / 1000).toFixed(1), unit: 'km' } |
| | | } |
| | | return { value: String(Math.round(distance)), unit: 'm' } |
| | | } |
| | | |
| | | const text = String(distance).trim() |
| | | const match = text.match(/^([\d.]+)\s*([a-zA-Z\u4e00-\u9fa5]*)$/) |
| | | if (match) { |
| | | return { |
| | | value: match[1], |
| | | unit: match[2] || '' |
| | | } |
| | | } |
| | | |
| | | return { value: text, unit: '' } |
| | | }, |
| | | |
| | | navigateToAddress(item, type) { |
| | | let latitude, longitude, name, address |
| | | if (type === 'deposit') { |
| | | latitude = item.depositLat |
| | | longitude = item.depositLng |
| | | name = item.depositShopName |
| | | address = item.depositShopAddress |
| | | } else { |
| | | latitude = item.takeLat |
| | | longitude = item.takeLng |
| | | name = item.takeName |
| | | address = item.takeAddress |
| | | } |
| | | if (!latitude || !longitude) { |
| | | uni.showToast({ title: '地址坐标缺失', icon: 'none' }) |
| | | return |
| | | } |
| | | uni.openLocation({ |
| | | latitude, |
| | | longitude, |
| | | name, |
| | | address, |
| | | success: () => {}, |
| | | fail: (err) => { |
| | | uni.showToast({ title: '打开地图失败', icon: 'none' }) |
| | | console.error('openLocation fail:', err) |
| | | } |
| | | }) |
| | | }, |
| | | |
| | | handleUserClick() { |
| | | uni.navigateTo({ |
| | | url: '/pages/test/test' |
| | | }) |
| | | }, |
| | | getActiveOrderCount() { |
| | | this.$u.api.activeOrderCount().then(res => { |
| | | if (res.code === 200) { |
| | |
| | | }, |
| | | |
| | | handleGrabOrder(item) { |
| | | console.log(item) |
| | | this.selectedGrabOrder = item |
| | | this.showGrabModal = true |
| | | }, |
| | |
| | | }, |
| | | |
| | | goToOrderDetail(item, index) { |
| | | uni.navigateTo({ |
| | | url: `/pages/order-detail/order-detail?id=${item.id}&index=${index + 1}` |
| | | }) |
| | | let url = `/pages/order-detail/order-detail?id=${item.id}` |
| | | if (this.activeTab === 'pickup' || this.activeTab === 'delivering') { |
| | | url += `&index=${index + 1}` |
| | | } |
| | | uni.navigateTo({ url }) |
| | | }, |
| | | |
| | | getCenterInfo() { |
| | |
| | | }, |
| | | |
| | | loadOrdersByTab(tab) { |
| | | if (this.userInfo.auditStatus === 99) return; |
| | | if (this.acceptingStatus === 0) return; |
| | | if (tab === 'hall') { |
| | | this.hallPage = 1 |
| | | this.hallHasMore = true |
| | |
| | | }, |
| | | |
| | | getHallOrders() { |
| | | if (this.acceptingStatus === 0) return; |
| | | if (this.hallLoading || !this.hallHasMore) { |
| | | return |
| | | } |
| | |
| | | }, |
| | | |
| | | getPickupOrders() { |
| | | if (this.acceptingStatus === 0) return; |
| | | if (this.pickupLoading) return |
| | | this.pickupLoading = true |
| | | this.$u.api.activeOrders({ status: 3 }).then(res => { |
| | |
| | | }, |
| | | |
| | | getDeliveringOrders() { |
| | | if (this.acceptingStatus === 0) return; |
| | | if (this.deliveringLoading) return |
| | | this.deliveringLoading = true |
| | | this.$u.api.activeOrders({ status: 4 }).then(res => { |
| | |
| | | const selectedValue = e.value[0] |
| | | this.$u.api.updateAcceptingStatus({ status: selectedValue.value }).then(res => { |
| | | if (res.code === 200) { |
| | | this.acceptingStatus = selectedValue.value |
| | | this.getCenterInfo() |
| | | if (this.acceptingStatus === 0) { |
| | | this.orderList = [] |
| | | this.pickupOrderList = [] |
| | | this.deliveringOrderList = [] |
| | | } else { |
| | | if (this.activeTab === 'pickup') { |
| | | this.getPickupOrders() |
| | | } else if (this.activeTab === 'delivering') { |
| | | this.getDeliveringOrders() |
| | | } else { |
| | | this.hallPage = 1 |
| | | this.hallHasMore = true |
| | | this.orderList = [] |
| | | this.getHallOrders() |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | }, |
| | |
| | | }, |
| | | |
| | | resetFilters() { |
| | | this.showFilterPopup = false |
| | | if (this.userInfo.auditStatus === 99) return; |
| | | this.selectedFilters = { |
| | | sort: '综合排序', |
| | | level: '不限', |
| | | distance: '不限' |
| | | } |
| | | this.showFilterPopup = false |
| | | if (this.activeTab === 'hall') { |
| | | this.hallPage = 1 |
| | | this.hallHasMore = true |
| | |
| | | |
| | | confirmFilters() { |
| | | this.showFilterPopup = false |
| | | if (this.userInfo.auditStatus === 99) { |
| | | this.hallHasMore = true |
| | | return |
| | | } |
| | | if (this.activeTab === 'hall') { |
| | | this.hallPage = 1 |
| | | this.hallHasMore = true |
| | |
| | | left: 0; |
| | | right: 0; |
| | | z-index: 20; |
| | | background: rgba(0, 0, 0, 0.24); |
| | | |
| | | &__panel { |
| | | display: flex; |
| | |
| | | } |
| | | |
| | | &__route-side { |
| | | width: 64rpx; |
| | | width: 70rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | flex-shrink: 0; |
| | | padding: 14rpx 0 8rpx; |
| | | border-radius: 26rpx; |
| | | background: #f6f7f9; |
| | | margin-right: 20rpx; |
| | | } |
| | | |
| | | &__distance-top, |
| | | &__distance-bottom { |
| | | font-size: 22rpx; |
| | | font-weight: 600; |
| | | color: #555b66; |
| | | &__distance-block { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | line-height: 1; |
| | | |
| | | &--top { |
| | | margin-bottom: 6rpx; |
| | | } |
| | | |
| | | &--bottom { |
| | | margin-top: 6rpx; |
| | | } |
| | | } |
| | | |
| | | &__distance-value { |
| | | font-size: 26rpx; |
| | | font-weight: 700; |
| | | color: #333333; |
| | | text-align: center; |
| | | } |
| | | |
| | | &__distance-unit { |
| | | margin-top: 6rpx; |
| | | font-size: 18rpx; |
| | | font-weight: 500; |
| | | color: #8c939f; |
| | | text-align: center; |
| | | text-transform: lowercase; |
| | | } |
| | | |
| | | &__line { |
| | | position: relative; |
| | | width: 4rpx; |
| | | width: 100%; |
| | | flex: 1; |
| | | min-height: 86rpx; |
| | | margin: 10rpx 0; |
| | | border-radius: 999rpx; |
| | | background: #d8dbe1; |
| | | min-height: 62rpx; |
| | | margin: 8rpx 0; |
| | | background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='26' viewBox='0 0 20 26'%3E%3Cpath fill='%238C939F' d='M10 0C5.03 0 1 3.99 1 8.92c0 6.31 7.28 12.87 8.07 13.57a1.4 1.4 0 0 0 1.86 0C11.72 21.79 19 15.23 19 8.92 19 3.99 14.97 0 10 0Zm0 12.24a3.32 3.32 0 1 1 0-6.64 3.32 3.32 0 0 1 0 6.64Z'/%3E%3C/svg%3E") center center no-repeat; |
| | | background-size: 20rpx 26rpx; |
| | | |
| | | &::before, |
| | | &::after { |
| | |
| | | position: absolute; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | width: 14rpx; |
| | | height: 14rpx; |
| | | border-radius: 50%; |
| | | background: #6a6f79; |
| | | width: 2rpx; |
| | | border-radius: 999rpx; |
| | | background: #cfd4dc; |
| | | } |
| | | |
| | | &::before { |
| | | top: -4rpx; |
| | | top: 0; |
| | | height: 18rpx; |
| | | } |
| | | |
| | | &::after { |
| | | bottom: -4rpx; |
| | | bottom: 0; |
| | | height: 24rpx; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | &__route-title { |
| | | width: 500rpx; |
| | | display: block; |
| | | font-size: 40rpx; |
| | | font-weight: 700; |
| | | color: #2d3139; |
| | | line-height: 1.3; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | &__route-desc { |
| | | width: 500rpx; |
| | | display: block; |
| | | margin-top: 8rpx; |
| | | font-size: 28rpx; |
| | | color: #9ea4ae; |
| | | line-height: 1.4; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | &__nav { |
| | |
| | | margin-left: 12rpx; |
| | | font-size: 24rpx; |
| | | color: #a4a9b1; |
| | | transition: transform 0.3s; |
| | | |
| | | &--expanded { |
| | | transform: rotate(180deg); |
| | | } |
| | | } |
| | | |
| | | &__button { |