You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

2211 lines
59 KiB

<template>
<view class="pending-page" :class="{'store-scene': isStoreScene, 'pending-page--embedded': embedded}">
<!-- 嵌入吸顶改用 fixed,占位避免列表上跳 -->
<view
class="pending-chrome-holder"
v-if="embedded && stickyActive"
:style="'height:' + chromeHeight + 'px;'"
></view>
<view
class="pending-chrome"
:class="{
'pending-chrome--fixed': !embedded,
'pending-chrome--sticky': embedded && stickyActive
}"
:style="chromeStyle"
>
<view class="nav-row" v-if="!embedded">
<view class="back-btn" @tap="back">
<uni-icons type="left" size="28" :color="searchIconColor"></uni-icons>
</view>
<view class="nav-title">加入立即省钱</view>
</view>
<view class="earn-guide" v-else>
<view class="earn-guide-light"></view>
<view class="earn-guide-icon">拼</view>
<text class="earn-guide-main">附近待成团</text>
<text class="earn-guide-tag">马上拼成更省钱</text>
</view>
<view class="tab-card" :class="{'tab-card--four': displayTabs.length > 2}">
<view class="tab-item" v-for="item in displayTabs" :key="item.scene"
:class="{'tab-item-active': scene === item.scene}" @tap="switchScene(item.scene)">
<text>{{item.label}}</text>
<text class="tab-badge" v-if="item.countKey && getTabCount(item.countKey) > 0">
{{formatBadgeCount(getTabCount(item.countKey))}}
</text>
</view>
</view>
<view class="search-row" v-if="isGroupScene">
<view class="search-card">
<view class="search-box">
<uni-icons type="search" size="18" :color="searchIconColor"></uni-icons>
<input type="text" v-model="query.keyWord" confirm-type="search" placeholder="搜索商品或商家"
@confirm="searchGroup" />
<view class="search-btn" @tap="searchGroup">搜索</view>
</view>
</view>
<view class="seckill-entry" @tap="goSeckillGroup">
<view class="heat-dot heat-dot-a"></view>
<view class="heat-dot heat-dot-b"></view>
<view class="seckill-entry-light"></view>
<view class="seckill-entry-info">
<view class="seckill-entry-title">秒杀团</view>
</view>
<view class="seckill-entry-btn">抢</view>
</view>
</view>
<view class="delivery-toolbar" v-else>
<view class="delivery-filter" @tap="checkAdd">
<text>取货地址</text>
<view class="delivery-filter-options" v-if="isgetArea">
<view class="delivery-filter-option" v-for="item in getshopArea" :key="item.id"
@tap.stop="searchDelivery('getarea', item)">{{item.title}}</view>
</view>
</view>
<view class="delivery-filter" @tap="checkAdd1">
<text>送货地址</text>
<view class="delivery-filter-options" v-if="isputArea">
<view class="delivery-filter-option" v-for="item in putshopArea" :key="item.id"
@tap.stop="searchDelivery('putarea', item)">{{item.title}}</view>
</view>
</view>
<view class="delivery-filter delivery-filter--small" :class="{'delivery-filter--active': checkYongjin}"
@tap="searchDelivery('yongjin')">佣金</view>
<view class="delivery-filter delivery-filter--small" @tap="searchDelivery('shuaxin')">刷新</view>
</view>
</view>
<view class="hero-holder" v-if="!embedded"></view>
<view class="hero-holder hero-holder--embedded" v-else></view>
<view class="list-wrap" v-if="isGroupScene">
<view class="empty-card" v-if="groupList.length === 0 && loadStatus !== 'loading'">
<view class="empty-icon">拼</view>
<view class="empty-title">暂时没有待成团</view>
<view class="empty-sub">换个关键词试试,或者稍后再来看看新发起的团</view>
</view>
<view class="group-card" v-for="(item,index) in groupList" :key="item.id" @tap="joinGroup(item)">
<view class="rank-pill">热拼 {{index + 1}}</view>
<view class="shop-row">
<view class="shop-img">
<image :src="item.uiShopIcon" mode="aspectFill"></image>
<view class="shop-img-store-badge" v-if="isStoreConsumeOnly(item.shopItem)">到店消费</view>
<view class="scene-mark" :class="{'scene-mark--with-store': isStoreConsumeOnly(item.shopItem)}">{{isStoreScene ? '团' : '饭'}}</view>
</view>
<view class="shop-info">
<view class="shop-name">{{item.uiShopName}}</view>
<view class="product-name">{{item.uiProductName}}</view>
<view class="tag-row">
<text>{{item.uiShopType}}</text>
<text>{{item.uiTargetMembers}}人成团</text>
<text>还差{{item.uiMissingMembers}}人</text>
</view>
</view>
</view>
<view class="product-row">
<image :src="item.uiProductImage" mode="aspectFill"></image>
<view class="product-copy">
<view class="price-row">
<text class="origin-price" v-if="item.uiStoreOriginalPrice">¥{{item.uiStoreOriginalPrice}}</text>
<text class="group-label">拼团价</text>
<text class="price">¥{{item.uiGroupPrice}}</text>
</view>
<view class="progress-bar">
<view class="progress-inner" :style="item.uiProgressStyle || ''"></view>
</view>
<view class="progress-copy">{{item.uiCurrentMembers}}人已拼 · 点击参与这一团</view>
</view>
</view>
<view class="card-bottom">
<view class="time-copy">{{item.uiExpireText}}</view>
<view class="join-btn">立即拼单</view>
</view>
</view>
<uni-load-more :status="loadStatus" v-if="groupList.length > 0" />
</view>
<view class="list-wrap delivery-list-wrap" v-else>
<view class="delivery-active-filters" v-if="getAreaData.title || putAreaData.title || checkYongjin">
<view class="delivery-filter-tag" v-if="getAreaData.title" @tap="searchDelivery('delgetarea')">取 {{getAreaData.title}}</view>
<view class="delivery-filter-tag" v-if="putAreaData.title" @tap="searchDelivery('delputarea')">送 {{putAreaData.title}}</view>
<view class="delivery-filter-tag" v-if="checkYongjin" @tap="searchDelivery('delyongjin')">佣金优先</view>
<view class="delivery-filter-clear" @tap="clearDeliveryFilter">清空</view>
</view>
<view class="empty-delivery" v-if="deliveryItem.length === 0 && loadStatus !== 'loading'">
<view class="empty-delivery-icon">{{isKuaidiScene ? '递' : '跑'}}</view>
<view class="empty-delivery-title">暂时没有可抢订单</view>
<view class="empty-delivery-desc">可以切换分类或调整筛选条件,新的订单会在这里出现</view>
</view>
<view class="delivery-card" v-for="(item,index) in deliveryItem" :key="item.deliveryKey">
<view class="delivery-time-row">
<view class="appointment-delivery-tag" v-if="item.appointmentDelivery == 1">预约配送</view>
<view class="delivery-time">送达时间:{{formatDeliveryTime(item.mustFinishTime)}}</view>
</view>
<view class="delivery-order-box">
<view class="delivery-fee-tag" :class="'delivery-fee-tag--' + item.deliveryType">
¥{{formatDeliveryAmount(item)}}
</view>
<view class="delivery-shop-row">
<text class="delivery-shop-name">{{item.shopName || ''}}</text>
<view class="delivery-goods-count">共{{item.allCount ? item.allCount : 0}}件</view>
</view>
<view class="delivery-route">
<view class="delivery-route-row">
<view class="delivery-route-icon">取</view>
<view class="delivery-route-text">{{item.shopAddress || ''}}</view>
<image class="delivery-phone" v-if="item.deliveryType != 2 && item.shopPhone"
@tap.stop="makeCall(item.shopPhone)"
src="https://jewel-shop.oss-cn-beijing.aliyuncs.com/869a7af6a1c24bf3a0d523c4a18b55c6.png"
mode="aspectFit"></image>
</view>
<view class="delivery-route-row">
<view class="delivery-route-icon delivery-route-icon--to">送</view>
<view class="delivery-route-text">{{item.receiverAddress || ''}}</view>
<image class="delivery-phone" v-if="shouldShowReceiverPhoneIcon(item) && item.receiverPhone"
@tap.stop="makeCall(item.receiverPhone)"
src="https://jewel-shop.oss-cn-beijing.aliyuncs.com/869a7af6a1c24bf3a0d523c4a18b55c6.png"
mode="aspectFit"></image>
</view>
</view>
</view>
<view class="delivery-commission" v-if="(item.isBig != null && item.isBig == 1) || item.deliveryFeeMarketplace > 0">
<view class="delivery-big-tag" v-if="item.isBig != null && item.isBig == 1">超大/超重</view>
<text v-if="item.deliveryFeeMarketplace > 0">包含¥{{item.deliveryFeeMarketplace}}平台奖励佣金</text>
</view>
<view class="delivery-time-warning" v-if="isNearMustFinishTime(item)">
<view class="delivery-time-warning-text">{{getMustFinishWarningText(item)}}</view>
<image class="delivery-time-warning-phone" v-if="item.receiverPhone"
@tap.stop="makeCall(item.receiverPhone)"
src="https://jewel-shop.oss-cn-beijing.aliyuncs.com/869a7af6a1c24bf3a0d523c4a18b55c6.png"
mode="aspectFit"></image>
</view>
<view class="delivery-remark" v-if="item.remark">备注:{{item.remark}}</view>
<view class="dispatch-action-box">
<view class="qiangdan-btn" :id="'pending-swipe-order-' + item.id"
:class="{'qiangdan-btn--swiping': swipeOrder.id === item.id && swipeOrder.index === index, 'qiangdan-btn--low-perf': swipeOrderLowPerf}"
@tap.stop="noop"
@touchstart.stop="startSwipeOrder($event,item,index)"
@touchmove.stop.prevent="moveSwipeOrder"
@touchend.stop="endSwipeOrder(item,index)"
@touchcancel.stop="resetSwipeOrder">
<view class="qiangdan-progress"
:style="swipeOrder.id === item.id && swipeOrder.index === index ? swipeOrder.progressStyle : swipeOrderDefaultStyle.progress"></view>
<view class="qiangdan-text">
<text v-if="swipeOrder.id !== item.id || swipeOrder.index !== index || swipeOrder.progress <= 0">向右滑动抢单</text>
<text v-else-if="swipeOrder.progress < 92">继续滑动确认</text>
<text v-else>松手接单</text>
</view>
<view class="qiangdan-thumb"
:style="swipeOrder.id === item.id && swipeOrder.index === index ? swipeOrder.thumbStyle : swipeOrderDefaultStyle.thumb">
<text>接</text>
</view>
</view>
</view>
</view>
<uni-load-more :status="loadStatus" v-if="deliveryItem.length > 0" />
</view>
</view>
</template>
<script>
import { requireLoginToCurrentPage } from '@/utils/authRedirect.js'
export default {
name: 'homeGroupPending',
props: {
embedded: {
type: Boolean,
default: false
},
stickyTop: {
type: Number,
default: 0
},
stickyActive: {
type: Boolean,
default: false
}
},
data() {
return {
scene: 'takeaway',
groupTabs: [{
label: '饭团',
scene: 'takeaway'
}, {
label: '团购',
scene: 'storeGroup'
}],
deliveryTabs: [{
label: '快递',
scene: 'kuaidi',
countKey: 'kuaidiCount'
}, {
label: '跑腿',
scene: 'paotui',
countKey: 'paotuiCount'
}],
loadStatus: 'more',
totalPages: 1,
query: {
regionId: '',
status: 0,
isFace: 0,
merchantType: 1,
pageNumber: 1,
pageSize: 10,
keyWord: ''
},
groupList: [],
deliveryItem: [],
deliveryTotalPages: 1,
deliveryQueryVersion: 0,
deliveryNowTime: Date.now(),
deliveryTimer: null,
deliveryQuery: {
hallOnly: true,
status: 0,
pageNum: 1,
pageSize: 10,
getAreaId: '',
putAreaId: '',
order: '',
regionId: '',
userId: uni.getStorageSync('id') || '',
deliveryType: 2,
waimaiData: [],
kuaidiData: [],
workerId: uni.getStorageSync('worker') ? uni.getStorageSync('worker').workerId : ''
},
kuaidiCount: 0,
paotuiCount: 0,
expressErrandEnabled: true,
getshopArea: [],
putshopArea: [],
getAreaData: {},
putAreaData: {},
isgetArea: false,
isputArea: false,
checkYongjin: false,
lastRefreshTime: 0,
latitude: 39.909,
longitude: 116.39742,
swipeOrderLowPerf: false,
swipeOrderDefaultStyle: {
progress: 'transform: translate3d(0, 0, 0) scaleX(0); transition: transform 0.14s ease-out;',
thumb: 'transform: translate3d(0, 0, 0); transition: transform 0.14s ease-out;'
},
swipeOrder: {
id: '',
index: -1,
startX: 0,
startProgress: 0,
progress: 0,
progressStyle: 'transform: translate3d(0, 0, 0) scaleX(0); transition: none;',
thumbStyle: 'transform: translate3d(0, 0, 0); transition: none;',
trackWidth: 0,
confirmed: false
},
chromeHeight: 160
}
},
computed: {
displayTabs() {
return this.embedded && this.expressErrandEnabled ? [...this.groupTabs, ...this.deliveryTabs] : this.groupTabs
},
isGroupScene() {
return this.scene === 'takeaway' || this.scene === 'storeGroup'
},
isDeliveryScene() {
return this.scene === 'kuaidi' || this.scene === 'paotui'
},
isKuaidiScene() {
return this.scene === 'kuaidi'
},
isStoreScene() {
return this.scene === 'storeGroup'
},
searchIconColor() {
return this.isStoreScene ? '#f0642f' : '#08735d'
},
chromeStyle() {
if (!this.embedded) return ''
const top = Number(this.stickyTop) || 0
if (this.stickyActive) {
// 与首页 daimai-box(95%) 对齐,盖在 sticky-cover 之上
return 'top:' + top + 'px;left:2.5%;width:95%;'
}
return 'top:' + top + 'px;'
}
},
watch: {
stickyActive(val) {
if (!val) {
this.$nextTick(() => this.measureChrome())
}
}
},
mounted() {
this.initSwipeMetrics()
this.startDeliveryTimeTimer()
if (this.embedded) {
this.$nextTick(() => {
setTimeout(() => this.measureChrome(), 80)
})
}
},
beforeDestroy() {
this.stopDeliveryTimeTimer()
clearTimeout(this._swipeOrderTapTimer)
},
methods: {
measureChrome() {
if (!this.embedded || this.stickyActive) return
const query = uni.createSelectorQuery().in(this)
query.select('.pending-chrome').boundingClientRect(rect => {
if (rect && rect.height) {
this.chromeHeight = Math.ceil(rect.height)
}
}).exec()
},
init(option) {
this.scene = this.normalizeScene(option && option.scene)
this.resetGroupQuery()
this.resetDeliveryQuery()
this.loadStatus = 'more'
this.initRegion()
this.$nextTick(() => {
setTimeout(() => this.measureChrome(), 80)
})
},
loadMore() {
if (this.isDeliveryScene) {
if (this.loadStatus === 'loading' || this.deliveryQuery.pageNum >= this.deliveryTotalPages) return
this.deliveryQuery.pageNum += 1
this.getDelivery()
return
}
if (this.loadStatus === 'loading' || this.query.pageNumber >= this.totalPages) return
this.query.pageNumber += 1
this.getGroupList()
},
normalizeScene(scene) {
if (scene === 'storeGroup') return 'storeGroup'
if (this.embedded && this.expressErrandEnabled && (scene === 'kuaidi' || scene === 'paotui')) return scene
return 'takeaway'
},
initRegion() {
const area = uni.getStorageSync('area')
if (!area) {
this.tui.toast('请先选择区域')
return
}
try {
const areaInfo = typeof area === 'string' ? JSON.parse(area) : area
this.query.regionId = areaInfo.id || ''
this.deliveryQuery.regionId = areaInfo.id || ''
this.expressErrandEnabled = this.resolveExpressErrandEnabled(areaInfo)
} catch (e) {
this.query.regionId = ''
this.deliveryQuery.regionId = ''
this.expressErrandEnabled = true
}
if (!this.query.regionId) {
this.tui.toast('请先选择区域')
return
}
if (!this.expressErrandEnabled && this.isDeliveryScene) {
this.scene = 'takeaway'
}
if (!this.expressErrandEnabled) {
this.kuaidiCount = 0
this.paotuiCount = 0
}
this.deliveryQuery.userId = uni.getStorageSync('id') || this.deliveryQuery.userId || ''
this.deliveryQuery.workerId = uni.getStorageSync('worker') ? uni.getStorageSync('worker').workerId : ''
if (this.expressErrandEnabled) {
this.getShopArea()
this.applyDeliverySceneQuery()
}
if (this.isDeliveryScene) {
this.getDelivery()
} else {
this.getGroupList()
if (this.embedded && this.expressErrandEnabled) {
this.getDelivery(true)
}
}
},
searchGroup() {
if (!this.isGroupScene) return
this.query.pageNumber = 1
this.totalPages = 1
this.getGroupList()
},
switchScene(scene) {
const nextScene = this.normalizeScene(scene)
if (this.scene === nextScene) {
if (this.embedded && this.expressErrandEnabled && this.isGroupScene) {
this.getDelivery(true)
}
return
}
this.scene = nextScene
this.loadStatus = 'more'
if (this.isDeliveryScene) {
this.resetDeliveryQuery(true)
this.applyDeliverySceneQuery()
this.getDelivery()
} else {
this.resetGroupQuery(true)
this.getGroupList()
if (this.embedded && this.expressErrandEnabled) {
this.getDelivery(true)
}
}
this.$nextTick(() => {
setTimeout(() => this.measureChrome(), 80)
})
},
getGroupList() {
if (!this.query.regionId) return
this.loadStatus = 'loading'
this.tui.request('/mall/admin/orderGroup/page', 'POST', this.query, false, false).then((res) => {
if (res.code == 200) {
const result = res.result || {}
const list = Array.isArray(result.records) ? result.records.map(item => this.formatGroupItem(item)) : []
if (this.query.pageNumber === 1) {
this.groupList = list
} else {
this.groupList = [...this.groupList, ...list]
}
this.totalPages = result.pages || result.totalPages || 1
this.loadStatus = this.query.pageNumber >= this.totalPages ? 'nomore' : 'more'
} else {
this.loadStatus = 'nomore'
this.tui.toast(res.message)
}
}).catch(() => {
this.loadStatus = 'nomore'
})
},
getDelivery(countsOnly = false) {
if (!this.expressErrandEnabled) return
if (!this.deliveryQuery.regionId) return
const requestVersion = this.deliveryQueryVersion
const requestPageNum = this.deliveryQuery.pageNum
const requestForm = {
...this.deliveryQuery,
userId: uni.getStorageSync('id') || this.deliveryQuery.userId || '',
workerId: uni.getStorageSync('worker') ? uni.getStorageSync('worker').workerId : '',
waimaiData: [...(this.deliveryQuery.waimaiData || [])],
kuaidiData: [...(this.deliveryQuery.kuaidiData || [])]
}
if (!countsOnly) {
this.loadStatus = 'loading'
}
this.tui.request('/mall/delivery/page', 'POST', requestForm, false, false).then((res) => {
if (requestVersion !== this.deliveryQueryVersion) return
if (res.code == 200) {
const result = res.result || {}
this.applyDeliveryCounts(result.orderCount || [])
if (countsOnly) return
const records = this.normalizeDeliveryRecords(result.records, requestPageNum)
if (requestPageNum === 1) {
this.deliveryItem = records
} else {
this.deliveryItem = [...this.deliveryItem, ...records]
}
this.deliveryTotalPages = result.pages || result.totalPages || 1
this.loadStatus = this.deliveryQuery.pageNum >= this.deliveryTotalPages ? 'nomore' : 'more'
} else {
if (!countsOnly) this.loadStatus = 'nomore'
this.tui.toast(res.message)
}
}).catch(() => {
if (!countsOnly) this.loadStatus = 'nomore'
})
},
applyDeliveryCounts(orderCount) {
this.kuaidiCount = 0
this.paotuiCount = 0
if (!Array.isArray(orderCount)) return
orderCount.forEach((item) => {
if (item.deliveryType == 2) {
this.kuaidiCount = Number(item.orderCount) || 0
} else if (item.deliveryType == 3) {
this.paotuiCount = Number(item.orderCount) || 0
}
})
},
normalizeDeliveryRecords(records, pageNum) {
if (!Array.isArray(records)) return []
return records.filter(item => item && typeof item === 'object').map((item, index) => {
return {
...item,
deliveryKey: item.id !== undefined && item.id !== null ? String(item.id) : (pageNum + '-' + index)
}
})
},
resetGroupQuery(keepList = false) {
this.query.merchantType = this.isStoreScene ? 2 : 1
this.query.keyWord = ''
this.query.pageNumber = 1
this.totalPages = 1
if (!keepList) {
this.groupList = []
}
},
resetDeliveryQuery(keepList = false) {
this.deliveryQueryVersion++
this.deliveryQuery.pageNum = 1
this.deliveryTotalPages = 1
if (!keepList) {
this.deliveryItem = []
}
this.resetSwipeOrder()
},
applyDeliverySceneQuery() {
this.deliveryQuery.deliveryType = this.isKuaidiScene ? 2 : 3
this.deliveryQuery.waimaiData = []
this.deliveryQuery.kuaidiData = this.isKuaidiScene ? this.getCachedRouteData('kuaidiData') : []
},
getCachedRouteData(key) {
const cached = uni.getStorageSync(key)
if (!Array.isArray(cached)) return []
return cached.map(item => ({
getAreaId: item.getAreaId,
putAreaId: item.putAreaId
}))
},
resolveExpressErrandEnabled(areaInfo) {
if (!areaInfo || Number(areaInfo.areaKind) !== 2) return true
if (areaInfo.expressErrandEnabled === undefined || areaInfo.expressErrandEnabled === null || areaInfo.expressErrandEnabled === '') {
return true
}
return Number(areaInfo.expressErrandEnabled) !== 0
},
getShopArea() {
if (!this.deliveryQuery.regionId) return
this.tui.request('/app/shopArea/getByParentId/' + this.deliveryQuery.regionId, 'GET', {}, false, true).then((res) => {
if (res.code == 200) {
this.getshopArea = res.result || []
this.putshopArea = res.result || []
} else {
this.tui.toast(res.message)
}
}).catch(() => {})
},
searchDelivery(type, value) {
if (type === 'shuaxin') {
const now = Date.now()
const refreshInterval = 10 * 1000
const diff = now - this.lastRefreshTime
if (diff < refreshInterval) {
this.tui.toast(Math.ceil((refreshInterval - diff) / 1000) + '秒后再刷新')
return
}
this.lastRefreshTime = now
}
if (type === 'getarea') {
if (this.deliveryQuery.getAreaId === value.id) {
this.deliveryQuery.getAreaId = ''
this.getAreaData = {}
} else {
this.deliveryQuery.getAreaId = value.id
this.getAreaData = value
}
this.isgetArea = false
} else if (type === 'putarea') {
if (this.deliveryQuery.putAreaId === value.id) {
this.deliveryQuery.putAreaId = ''
this.putAreaData = {}
} else {
this.deliveryQuery.putAreaId = value.id
this.putAreaData = value
}
this.isputArea = false
} else if (type === 'yongjin') {
this.checkYongjin = !this.checkYongjin
this.deliveryQuery.order = this.checkYongjin ? 'deliveryFee' : ''
} else if (type === 'delgetarea') {
this.deliveryQuery.getAreaId = ''
this.getAreaData = {}
} else if (type === 'delputarea') {
this.deliveryQuery.putAreaId = ''
this.putAreaData = {}
} else if (type === 'delyongjin') {
this.checkYongjin = false
this.deliveryQuery.order = ''
}
this.resetDeliveryQuery(true)
this.applyDeliverySceneQuery()
this.getDelivery()
},
clearDeliveryFilter() {
this.getAreaData = {}
this.putAreaData = {}
this.checkYongjin = false
this.deliveryQuery.getAreaId = ''
this.deliveryQuery.putAreaId = ''
this.deliveryQuery.order = ''
this.resetDeliveryQuery(true)
this.applyDeliverySceneQuery()
this.getDelivery()
},
checkAdd() {
this.isgetArea = !this.isgetArea
this.isputArea = false
},
checkAdd1() {
this.isputArea = !this.isputArea
this.isgetArea = false
},
initSwipeMetrics() {
const info = uni.getSystemInfoSync ? uni.getSystemInfoSync() : {}
this.swipeOrderBaseWidth = (info.windowWidth || 375) * 0.86
this.swipeOrderThumbWidth = uni.upx2px ? uni.upx2px(82) : 41
const iosVersionMatch = info.platform === 'ios' && info.system ? String(info.system).match(/\d+/) : null
const iosMajorVersion = iosVersionMatch ? Number(iosVersionMatch[0]) : 0
this.swipeOrderLowPerf = iosMajorVersion >= 26
this.swipeOrderFrameGap = info.platform === 'ios' ? (this.swipeOrderLowPerf ? 36 : 24) : 16
this.swipeOrderProgressStep = info.platform === 'ios' ? (this.swipeOrderLowPerf ? 4 : 2) : 1
},
startDeliveryTimeTimer() {
this.deliveryNowTime = Date.now()
if (this.deliveryTimer) return
this.deliveryTimer = setInterval(() => {
this.deliveryNowTime = Date.now()
}, 60 * 1000)
},
stopDeliveryTimeTimer() {
if (!this.deliveryTimer) return
clearInterval(this.deliveryTimer)
this.deliveryTimer = null
},
startSwipeOrder(event, item, index) {
if (!item || item.id === undefined || item.id === null) return
if (!requireLoginToCurrentPage()) return
const touch = event.touches && event.touches[0]
if (!touch) return
this._swipeOrderProgress = 0
this._swipeOrderLastRenderTime = 0
this.swipeOrder = {
id: item.id,
index,
startX: touch.clientX,
startProgress: 0,
progress: 0,
progressStyle: 'transform: translate3d(0, 0, 0) scaleX(0); transition: none;',
thumbStyle: 'transform: translate3d(0, 0, 0); transition: none;',
trackWidth: this.swipeOrderBaseWidth || 0,
confirmed: false
}
this.$nextTick(() => {
uni.createSelectorQuery().in(this).select('#pending-swipe-order-' + item.id).boundingClientRect((rect) => {
if (rect && this.swipeOrder.id === item.id && this.swipeOrder.index === index) {
this.swipeOrder.trackWidth = rect.width
}
}).exec()
})
},
moveSwipeOrder(event) {
const touch = event.touches && event.touches[0]
if (!touch || this.swipeOrder.id === '') return
const trackWidth = this.swipeOrder.trackWidth || this.swipeOrderBaseWidth || 1
const deltaX = Math.max(0, touch.clientX - this.swipeOrder.startX)
const progress = Math.min(100, Math.round(deltaX / (trackWidth * 0.72) * 100))
this._swipeOrderProgress = progress
const now = Date.now()
const frameGap = this.swipeOrderFrameGap || 16
const progressStep = this.swipeOrderProgressStep || 1
if (now - (this._swipeOrderLastRenderTime || 0) < frameGap &&
Math.abs(progress - this.swipeOrder.progress) < progressStep &&
progress < 92) {
return
}
this._swipeOrderLastRenderTime = now
this.setSwipeOrderProgress(progress)
},
endSwipeOrder(item, index) {
if (!requireLoginToCurrentPage()) {
this.resetSwipeOrder()
return
}
if (this.swipeOrder.id !== item.id || this.swipeOrder.index !== index) return
const progress = this._swipeOrderProgress !== undefined ? this._swipeOrderProgress : this.swipeOrder.progress
if (progress >= 92 && !this.swipeOrder.confirmed) {
this.swipeOrder.confirmed = true
this.setSwipeOrderProgress(100)
this.acceptDeliveryOrder(item, index)
return
}
this.resetSwipeOrder()
},
resetSwipeOrder() {
this._swipeOrderProgress = 0
this._swipeOrderLastRenderTime = 0
clearTimeout(this._swipeOrderTapTimer)
this.swipeOrder = {
id: '',
index: -1,
startX: 0,
startProgress: 0,
progress: 0,
progressStyle: 'transform: translate3d(0, 0, 0) scaleX(0); transition: none;',
thumbStyle: 'transform: translate3d(0, 0, 0); transition: none;',
trackWidth: 0,
confirmed: false
}
},
setSwipeOrderProgress(progress) {
if (this.swipeOrder.progress === progress) return
const trackWidth = this.swipeOrder.trackWidth || this.swipeOrderBaseWidth || 0
const thumbWidth = this.swipeOrderThumbWidth || 0
const thumbOffset = Math.max(0, trackWidth - thumbWidth) * progress / 100
this.swipeOrder.progress = progress
this.swipeOrder.progressStyle = 'transform: translate3d(0, 0, 0) scaleX(' + (progress / 100) + '); transition: none;'
this.swipeOrder.thumbStyle = 'transform: translate3d(' + thumbOffset + 'px, 0, 0); transition: none;'
},
noop() {},
acceptDeliveryOrder(item, index) {
if (!requireLoginToCurrentPage()) {
this.resetSwipeOrder()
return
}
const worker = uni.getStorageSync('worker')
const area = uni.getStorageSync('area')
let areaInfo = {}
try {
areaInfo = typeof area === 'string' ? JSON.parse(area) : (area || {})
} catch (e) {
areaInfo = {}
}
const data = {
groupId: item.groupId ? item.groupId : '',
deliveryId: item.id,
workerId: worker ? worker.workerId : '',
workerPhone: worker ? worker.mobile : '',
workerName: worker ? worker.workerName : '',
regionId: areaInfo.id || this.deliveryQuery.regionId,
geolocation: this.latitude + ',' + this.longitude
}
this.tui.request('/mall/delivery/accept', 'POST', data, false, true).then((res) => {
if (res.code == 200) {
this.resetSwipeOrder()
if (!worker && res.result != null) {
uni.setStorageSync('worker', res.result)
}
this.tui.toast(res.message || '接单成功', 2000)
this.deliveryItem.splice(index, 1)
this.decreaseDeliveryCount(item.deliveryType)
const workerId = uni.getStorageSync('worker') ? uni.getStorageSync('worker').workerId : (res.result && res.result.workerId)
if (workerId) {
setTimeout(() => this.updateWorkerLocation(workerId), 2000)
}
} else {
this.resetSwipeOrder()
this.tui.toast(res.message)
}
}).catch(() => {
this.resetSwipeOrder()
})
},
decreaseDeliveryCount(deliveryType) {
if (deliveryType == 2) {
this.kuaidiCount = Math.max(0, (Number(this.kuaidiCount) || 0) - 1)
} else if (deliveryType == 3) {
this.paotuiCount = Math.max(0, (Number(this.paotuiCount) || 0) - 1)
}
},
updateWorkerLocation(workerId) {
uni.getLocation({
type: 'gcj02',
isHighAccuracy: true,
success: (res) => {
this.latitude = res.latitude
this.longitude = res.longitude
this.tui.request('/worker/admin/editLocation', 'POST', {
workerId,
geolocation: this.latitude + ',' + this.longitude
}, false, false, true).catch(() => {})
},
fail: () => {}
})
},
joinGroup(item) {
if (!requireLoginToCurrentPage()) return
const shopItem = item.shopItem || {}
if (!shopItem.id) {
this.tui.toast('商家信息缺失,暂时无法参团')
return
}
const sceneQuery = this.isStoreScene ? '&orderScene=storeGroup' : ''
const shopItemStr = encodeURIComponent(JSON.stringify(shopItem))
uni.navigateTo({
url: '/package2/group/groupBuySingle?type=shop&item=' + shopItemStr +
'&groupId=' + item.id +
'&targetMembers=' + (item.targetMembers || 2) +
'&isFaceToFace=0' + sceneQuery
})
},
goSeckillGroup() {
if (!requireLoginToCurrentPage()) return
uni.navigateTo({
url: '/package3/seckill/seckillGroup'
})
},
getShopIcon(item) {
return (item.shopItem && item.shopItem.shopIcon) || item.productPicture || '/static/images/img/shangpintu.png'
},
formatGroupItem(item) {
return {
...item,
uiShopIcon: this.getShopIcon(item),
uiProductImage: item.productPicture || this.getShopIcon(item),
uiShopName: this.getShopName(item),
uiProductName: item.productName || '热门拼团',
uiShopType: this.getShopType(item),
uiTargetMembers: item.targetMembers || 2,
uiCurrentMembers: item.currentMembers || 0,
uiMissingMembers: this.missingMembers(item),
uiStoreOriginalPrice: this.formatOptionalPrice(item.storeOriginalPrice),
uiGroupPrice: this.formatPrice(item.groupPrice),
uiExpireText: this.formatExpireText(item),
uiProgressStyle: this.buildProgressStyle(item)
}
},
getShopName(item) {
return (item.shopItem && item.shopItem.shopName) || item.shopName || '未知商家'
},
getShopType(item) {
const shopItem = item.shopItem || {}
return shopItem.shopTypeTitle || (shopItem.shopTakeaway && shopItem.shopTakeaway.type) || (this.isStoreScene ? '团购' : '美食')
},
isStoreConsumeOnly(shopItem) {
return shopItem && shopItem.storeConsumeOnly == 1
},
getTabCount(countKey) {
return Number(this[countKey] || 0)
},
formatBadgeCount(count) {
const value = Number(count) || 0
return value > 99 ? '99+' : value
},
formatDeliveryAmount(item) {
const deliveryFee = Number(item && item.deliveryFee || 0)
const marketplaceFee = Number(item && item.deliveryFeeMarketplace || 0)
return (deliveryFee + marketplaceFee).toFixed(2)
},
formatDeliveryTime(value) {
if (!value) return ''
const date = new Date(value)
if (isNaN(date.getTime())) return value
const hour = String(date.getHours()).padStart(2, '0')
const minute = String(date.getMinutes()).padStart(2, '0')
return hour + ':' + minute
},
shouldShowReceiverPhoneIcon(item) {
return !!(item && item.workerId)
},
makeCall(phone) {
if (!phone) return
uni.makePhoneCall({
phoneNumber: phone
})
},
parseDeliveryTime(value) {
if (!value) return NaN
if (typeof value === 'number') {
return String(value).length === 10 ? value * 1000 : value
}
const timeText = String(value).trim()
if (!timeText) return NaN
const nativeParsedTime = new Date(timeText).getTime()
if (!isNaN(nativeParsedTime)) return nativeParsedTime
const timeOnlyMatch = timeText.match(/^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/)
if (timeOnlyMatch) {
const currentDate = new Date(this.deliveryNowTime || Date.now())
currentDate.setHours(Number(timeOnlyMatch[1]), Number(timeOnlyMatch[2]), Number(timeOnlyMatch[3] || 0), 0)
return currentDate.getTime()
}
return new Date(timeText.replace(/-/g, '/')).getTime()
},
isNearMustFinishTime(item) {
if (!item || !item.mustFinishTime) return false
const mustFinishTime = this.parseDeliveryTime(item.mustFinishTime)
if (isNaN(mustFinishTime)) return false
return mustFinishTime - this.deliveryNowTime <= 10 * 60 * 1000
},
getMustFinishWarningText(item) {
const mustFinishTime = this.parseDeliveryTime(item && item.mustFinishTime)
if (!isNaN(mustFinishTime) && this.deliveryNowTime > mustFinishTime) {
return '已超过用户要求送达时间,为避免纠纷,建议和用户沟通是否继续接单配送'
}
return '已经临近用户要求送达时间,为避免纠纷,建议和用户沟通是否继续接单配送。'
},
missingMembers(item) {
const target = Number(item.targetMembers || 2)
const current = Number(item.currentMembers || 0)
return Math.max(target - current, 0)
},
buildProgressStyle(item) {
const target = Number(item.targetMembers || 2)
const current = Number(item.currentMembers || 0)
const percent = target > 0 ? Math.min(100, Math.max(12, Math.round(current / target * 100))) : 12
return 'width: ' + percent + '%;'
},
formatPrice(price) {
const num = Number(price || 0)
return num ? num.toFixed(2) : '--'
},
formatOptionalPrice(price) {
const num = Number(price || 0)
return num ? num.toFixed(2) : ''
},
formatExpireText(item) {
if (!item.expireTime) return '24小时内成团'
const expire = new Date(item.expireTime).getTime()
const diff = expire - Date.now()
if (isNaN(expire) || diff <= 0) return '即将结束'
const hours = Math.floor(diff / 3600000)
const minutes = Math.floor((diff % 3600000) / 60000)
return hours > 0 ? '剩余约' + hours + '小时' + minutes + '分' : '剩余约' + minutes + '分'
},
back() {
uni.navigateBack()
}
}
}
</script>
<style lang="scss" scoped>
.pending-page {
min-height: 100vh;
padding-bottom: 140rpx;
background:
radial-gradient(circle at 12% 8%, rgba(197, 255, 153, 0.5), transparent 34%),
radial-gradient(circle at 90% 18%, rgba(175, 255, 231, 0.48), transparent 36%),
linear-gradient(180deg, #f6ffea 0%, #f7fff9 42%, #fff9ef 100%);
color: #06382f;
box-sizing: border-box;
}
.pending-page--embedded {
min-height: auto;
padding-bottom: 40rpx;
background: transparent;
}
/* 嵌入首页时外层已有 daimai-box(95%),去掉组件内二次缩进,与上方广告区对齐 */
.pending-page--embedded .pending-chrome {
padding-left: 0;
padding-right: 0;
}
.pending-page--embedded .pending-chrome--sticky {
padding-top: 0;
}
.pending-page--embedded .pending-chrome--sticky .earn-guide {
margin-top: 0;
}
.pending-page--embedded .list-wrap {
width: 100%;
}
.store-scene {
background:
radial-gradient(circle at 82% 120rpx, rgba(255, 241, 210, 0.86) 0, rgba(255, 241, 210, 0) 260rpx),
radial-gradient(circle at 10% 260rpx, rgba(255, 190, 185, 0.3) 0, rgba(255, 190, 185, 0) 240rpx),
linear-gradient(180deg, #ffd0b8 0%, #ffe6d6 260rpx, #fff7ef 560rpx, #fffaf6 100%);
color: #3f2618;
}
.pending-page--embedded.store-scene {
background: transparent;
}
.pending-chrome {
width: 100%;
box-sizing: border-box;
padding: 0 26rpx 22rpx;
}
.pending-chrome--fixed {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
padding: 88rpx 26rpx 22rpx;
background: rgba(247, 255, 249, 0.94);
box-shadow: 0 12rpx 30rpx rgba(20, 115, 88, 0.08);
backdrop-filter: blur(18rpx);
}
.store-scene .pending-chrome--fixed {
background: rgba(255, 249, 243, 0.96);
box-shadow: 0 12rpx 30rpx rgba(178, 102, 48, 0.1);
}
.pending-chrome-holder {
width: 100%;
box-sizing: border-box;
}
.pending-chrome--sticky {
/* 小程序组件内 sticky 不可靠,嵌入首页改用 fixed */
position: fixed;
right: auto;
z-index: 98;
padding-top: 16rpx;
padding-bottom: 18rpx;
box-sizing: border-box;
background: rgba(246, 255, 251, 0.98);
box-shadow: 0 12rpx 28rpx rgba(20, 115, 88, 0.1);
backdrop-filter: blur(16rpx);
}
.store-scene .pending-chrome--sticky {
background: rgba(255, 249, 243, 0.98);
box-shadow: 0 12rpx 28rpx rgba(178, 102, 48, 0.1);
}
.pending-title {
height: 56rpx;
margin-top: 8rpx;
color: #073d33;
font-size: 32rpx;
font-weight: 900;
line-height: 56rpx;
}
.store-scene .pending-title {
color: #3f2618;
}
.earn-guide {
height: 64rpx;
margin: 8rpx 0 0;
padding: 0 12rpx 0 18rpx;
box-sizing: border-box;
border-radius: 16rpx;
background:
radial-gradient(circle at 10% 0%, rgba(255, 255, 255, 0.9) 0, rgba(255, 255, 255, 0) 74rpx),
linear-gradient(90deg, rgba(236, 255, 248, 0.98) 0%, rgba(255, 252, 237, 0.98) 58%, rgba(255, 238, 226, 0.98) 100%);
border: 1px solid rgba(255, 255, 255, 0.88);
box-shadow: 0 10rpx 24rpx rgba(74, 151, 111, 0.08), inset 0 1rpx 0 rgba(255, 255, 255, 0.9);
display: flex;
align-items: center;
position: relative;
overflow: hidden;
}
.store-scene .earn-guide {
background:
radial-gradient(circle at 10% 0%, rgba(255, 255, 255, 0.92) 0, rgba(255, 255, 255, 0) 74rpx),
linear-gradient(90deg, rgba(255, 244, 236, 0.98) 0%, rgba(255, 248, 236, 0.98) 58%, rgba(255, 236, 220, 0.98) 100%);
box-shadow: 0 10rpx 24rpx rgba(178, 102, 48, 0.08), inset 0 1rpx 0 rgba(255, 255, 255, 0.9);
}
.earn-guide-light {
width: 180rpx;
height: 100rpx;
background: linear-gradient(105deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.74) 48%, rgba(255, 255, 255, 0) 100%);
position: absolute;
top: -18rpx;
left: -210rpx;
transform: rotate(12deg);
animation: seckillEntrySweep 5.2s ease-in-out infinite;
}
.earn-guide-icon {
width: 48rpx;
height: 48rpx;
margin-right: 12rpx;
border-radius: 14rpx;
background: linear-gradient(135deg, #e8ffb8, #b8ffe9);
color: #08735d;
font-size: 26rpx;
font-weight: 900;
line-height: 48rpx;
text-align: center;
position: relative;
z-index: 1;
}
.store-scene .earn-guide-icon {
background: linear-gradient(135deg, #ff9a72 0%, #ffd38a 100%);
color: #fff;
}
.earn-guide-main {
color: #0c6958;
font-size: 26rpx;
font-weight: 900;
letter-spacing: 1rpx;
position: relative;
z-index: 1;
white-space: nowrap;
}
.store-scene .earn-guide-main {
color: #3f2618;
}
.earn-guide-tag {
height: 36rpx;
margin-left: auto;
padding: 0 12rpx;
border-radius: 999rpx;
color: #ff7a3f;
font-size: 22rpx;
font-weight: 900;
line-height: 36rpx;
position: relative;
z-index: 1;
white-space: nowrap;
}
.hero-holder {
width: 100%;
height: 396rpx;
}
.hero-holder--embedded {
height: 0;
}
.nav-row {
position: relative;
display: flex;
align-items: center;
height: 64rpx;
}
.back-btn {
display: flex;
align-items: center;
justify-content: center;
width: 64rpx;
height: 64rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.96);
border: 1rpx solid rgba(255, 255, 255, 0.82);
box-shadow: 0 8rpx 18rpx rgba(0, 45, 29, 0.08);
box-sizing: border-box;
flex-shrink: 0;
}
.nav-title {
position: absolute;
left: 50%;
width: 220rpx;
height: 64rpx;
color: #073d33;
font-size: 34rpx;
font-weight: 900;
line-height: 64rpx;
text-align: center;
transform: translateX(-50%);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.store-scene .nav-title {
color: #3f2618;
}
.tab-card {
display: flex;
height: 76rpx;
margin-top: 24rpx;
padding: 8rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.72);
box-shadow: inset 0 0 0 1rpx rgba(255, 255, 255, 0.82);
box-sizing: border-box;
}
.tab-item {
flex: 1;
height: 60rpx;
border-radius: 999rpx;
color: rgba(7, 61, 51, 0.56);
font-size: 27rpx;
font-weight: 900;
line-height: 60rpx;
text-align: center;
position: relative;
}
.tab-card--four .tab-item {
font-size: 25rpx;
}
.tab-badge {
position: absolute;
top: -6rpx;
right: 12rpx;
min-width: 30rpx;
height: 30rpx;
padding: 0 8rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, #ff8a1f 0%, #ff3f5f 100%);
border: 3rpx solid rgba(255, 255, 255, 0.96);
color: #fff;
font-size: 18rpx;
font-weight: 900;
line-height: 24rpx;
box-sizing: border-box;
}
.tab-item-active {
background: linear-gradient(90deg, #dcff9c, #baffec);
color: #08735d;
box-shadow: 0 10rpx 24rpx rgba(13, 114, 82, 0.14);
}
.store-scene .tab-item {
color: rgba(84, 43, 22, 0.56);
}
.store-scene .tab-item-active {
background: linear-gradient(135deg, #ff7444 0%, #ffae53 100%);
color: #fff;
box-shadow: 0 10rpx 24rpx rgba(255, 126, 39, 0.2);
}
.search-row {
width: 100%;
margin: 18rpx auto 0;
display: flex;
align-items: center;
}
.search-card {
flex: 1;
min-width: 0;
margin-right: 14rpx;
height: 60rpx;
padding: 0;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.76);
box-shadow: 0 18rpx 42rpx rgba(20, 115, 88, 0.12);
box-sizing: border-box;
position: relative;
z-index: 3;
}
.store-scene .search-card {
box-shadow: 0 18rpx 42rpx rgba(178, 102, 48, 0.12);
}
.search-box {
height: 60rpx;
padding: 0 12rpx;
border-radius: 999rpx;
background: #fff;
display: flex;
align-items: center;
}
.search-box input {
flex: 1;
min-width: 0;
height: 60rpx;
margin-left: 10rpx;
font-size: 22rpx;
color: #243d36;
}
.search-btn {
width: 74rpx;
height: 38rpx;
border-radius: 999rpx;
background: linear-gradient(90deg, #dcff9c, #baffec);
color: #08735d;
font-size: 20rpx;
font-weight: 900;
line-height: 38rpx;
text-align: center;
}
.store-scene .search-btn {
background: linear-gradient(135deg, #ff7444 0%, #ffae53 100%);
color: #fff;
}
.seckill-entry {
width: 172rpx;
height: 60rpx;
padding: 0 10rpx;
border-radius: 999rpx;
border: 1rpx solid rgba(255, 255, 255, 0.52);
background:
radial-gradient(circle at 18% 4%, rgba(255, 255, 255, 0.55) 0, rgba(255, 255, 255, 0) 76rpx),
linear-gradient(135deg, #ff4b27 0%, #ff8b18 48%, #ffd257 100%);
box-shadow: 0 16rpx 30rpx rgba(255, 91, 39, 0.24), 0 0 40rpx rgba(255, 188, 80, 0.26);
box-sizing: border-box;
display: flex;
align-items: center;
flex-shrink: 0;
position: relative;
overflow: hidden;
}
.seckill-entry:active {
transform: scale(0.96);
}
.seckill-entry-light {
width: 190rpx;
height: 120rpx;
background: linear-gradient(105deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.76) 46%, rgba(255, 255, 255, 0) 100%);
position: absolute;
top: -20rpx;
left: -220rpx;
transform: rotate(12deg);
animation: seckillEntrySweep 4.8s ease-in-out infinite;
}
.seckill-entry-info {
flex: 1;
min-width: 0;
padding-left: 14rpx;
position: relative;
z-index: 1;
color: #fff;
}
.seckill-entry-title {
font-size: 26rpx;
font-weight: 900;
line-height: 60rpx;
letter-spacing: 1rpx;
text-shadow: 0 3rpx 8rpx rgba(183, 35, 0, 0.24);
}
.seckill-entry-btn {
width: 42rpx;
height: 42rpx;
border-radius: 50%;
background: #fff;
color: #ff3d18;
font-size: 24rpx;
font-weight: 900;
line-height: 42rpx;
text-align: center;
position: relative;
z-index: 1;
box-shadow: 0 8rpx 18rpx rgba(158, 32, 0, 0.22);
animation: seckillBtnBeat 2.8s ease-in-out infinite;
}
.heat-dot {
width: 12rpx;
height: 12rpx;
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.92);
box-shadow: 0 0 16rpx rgba(255, 255, 255, 0.88);
z-index: 1;
animation: heatParticle 3.2s ease-in-out infinite;
}
.delivery-toolbar {
width: 100%;
margin: 18rpx auto 0;
display: flex;
align-items: center;
gap: 10rpx;
position: relative;
z-index: 4;
}
.delivery-filter {
flex: 1;
min-width: 0;
height: 60rpx;
padding: 0 10rpx;
border-radius: 20rpx;
background: rgba(255, 255, 255, 0.86);
border: 1rpx solid rgba(166, 255, 234, 0.48);
box-shadow: 0 10rpx 22rpx rgba(20, 115, 88, 0.08);
color: #40534d;
font-size: 22rpx;
font-weight: 800;
line-height: 60rpx;
text-align: center;
box-sizing: border-box;
position: relative;
}
.delivery-filter--small {
flex: 0 0 86rpx;
}
.delivery-filter--active {
background: linear-gradient(135deg, rgba(255, 247, 215, 0.95), rgba(166, 255, 234, 0.72));
border-color: rgba(255, 188, 119, 0.72);
}
.delivery-filter-options {
width: 220rpx;
max-height: 320rpx;
overflow: scroll;
position: absolute;
top: 70rpx;
left: 0;
z-index: 20;
border-radius: 22rpx;
background: rgba(0, 35, 28, 0.9);
color: #fff;
box-shadow: 0 16rpx 34rpx rgba(0, 35, 28, 0.16);
}
.delivery-filter-option {
min-height: 60rpx;
padding: 0 16rpx;
line-height: 60rpx;
font-size: 22rpx;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.12);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.heat-dot-a {
left: 28rpx;
top: 18rpx;
}
.heat-dot-b {
right: 64rpx;
bottom: 16rpx;
animation-delay: 0.5s;
}
.list-wrap {
width: 94%;
min-height: 720rpx;
margin: 0 auto;
position: relative;
z-index: 1;
}
.delivery-list-wrap {
width: 100%;
}
.delivery-active-filters {
display: flex;
align-items: center;
flex-wrap: wrap;
padding: 0 6rpx 8rpx;
}
.delivery-filter-tag,
.delivery-filter-clear {
height: 40rpx;
margin: 0 12rpx 12rpx 0;
padding: 0 18rpx;
border-radius: 999rpx;
background: rgba(166, 255, 234, 0.62);
color: #08735d;
font-size: 22rpx;
font-weight: 800;
line-height: 40rpx;
max-width: 300rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.delivery-filter-clear {
background: rgba(0, 191, 255, 0.08);
color: #00A8D8;
}
.empty-card,
.group-card {
border-radius: 34rpx;
background: rgba(255, 255, 255, 0.86);
box-shadow: 0 18rpx 44rpx rgba(19, 91, 70, 0.12);
border: 1rpx solid rgba(255, 255, 255, 0.86);
box-sizing: border-box;
}
.store-scene .empty-card,
.store-scene .group-card {
box-shadow: 0 16rpx 34rpx rgba(178, 102, 48, 0.12);
border-color: rgba(255, 221, 188, 0.72);
}
.empty-card {
min-height: 460rpx;
padding-top: 110rpx;
text-align: center;
}
.empty-icon {
width: 100rpx;
height: 100rpx;
margin: 0 auto 22rpx;
border-radius: 34rpx;
background: linear-gradient(135deg, #e8ffb8, #b8ffe9);
color: #08735d;
font-size: 34rpx;
font-weight: 900;
line-height: 100rpx;
}
.store-scene .empty-icon {
background: linear-gradient(135deg, #ff9a72 0%, #ffd38a 100%);
color: #fff;
}
.empty-title {
font-size: 30rpx;
font-weight: 900;
}
.empty-sub {
width: 78%;
margin: 14rpx auto 0;
color: rgba(0, 35, 28, 0.56);
font-size: 24rpx;
line-height: 38rpx;
}
.group-card {
margin-bottom: 22rpx;
padding: 20rpx 18rpx 18rpx;
position: relative;
overflow: hidden;
}
.rank-pill {
position: absolute;
right: 18rpx;
top: 18rpx;
z-index: 2;
padding: 5rpx 13rpx;
border-radius: 999rpx;
background: rgba(219, 255, 157, 0.72);
color: #08735d;
font-size: 18rpx;
font-weight: 900;
}
.store-scene .rank-pill {
background: #fff0dc;
color: #e96632;
}
.shop-row {
display: flex;
}
.shop-img {
width: 168rpx;
height: 168rpx;
flex-shrink: 0;
border-radius: 30rpx;
overflow: hidden;
position: relative;
background: #f4fff3;
box-shadow: 0 14rpx 28rpx rgba(20, 75, 57, 0.14);
}
.shop-img image {
width: 100%;
height: 100%;
}
.scene-mark {
width: 42rpx;
height: 42rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.92);
color: #ff5a35;
font-size: 22rpx;
font-weight: 900;
line-height: 42rpx;
text-align: center;
position: absolute;
left: 10rpx;
top: 10rpx;
}
.scene-mark--with-store {
top: auto;
bottom: 10rpx;
}
.shop-img-store-badge {
position: absolute;
left: 1rpx;
top: 1rpx;
z-index: 2;
height: 36rpx;
line-height: 36rpx;
padding: 0 12rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.94);
color: #ff5b37;
font-size: 19rpx;
font-weight: 900;
box-shadow: 0 8rpx 16rpx rgba(255, 83, 42, 0.18);
}
.shop-info {
flex: 1;
min-width: 0;
padding-left: 20rpx;
box-sizing: border-box;
}
.shop-name {
padding-right: 92rpx;
font-size: 33rpx;
line-height: 42rpx;
font-weight: 900;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.product-name {
margin-top: 10rpx;
color: rgba(0, 35, 28, 0.58);
font-size: 24rpx;
line-height: 34rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.store-scene .product-name {
color: #876247;
}
.tag-row {
margin-top: 16rpx;
overflow: hidden;
}
.tag-row text {
display: inline-block;
margin: 0 8rpx 8rpx 0;
padding: 5rpx 10rpx;
border-radius: 12rpx;
background: rgba(184, 255, 225, 0.62);
color: #08735d;
font-size: 20rpx;
font-weight: 900;
}
.store-scene .tag-row text {
background: #fff0dc;
color: #d65f2c;
}
.product-row {
margin-top: 18rpx;
padding: 14rpx;
border-radius: 26rpx;
background: rgba(255, 250, 237, 0.72);
display: flex;
}
.store-scene .product-row {
background: #fff7ee;
}
.product-row image {
width: 110rpx;
height: 110rpx;
border-radius: 22rpx;
background: #fff;
flex-shrink: 0;
}
.product-copy {
flex: 1;
min-width: 0;
padding-left: 16rpx;
box-sizing: border-box;
}
.price-row {
height: 42rpx;
display: flex;
align-items: center;
}
.group-label {
margin-right: 10rpx;
padding: 4rpx 9rpx;
border-radius: 10rpx;
background: #fff0dc;
color: #b4572d;
font-size: 19rpx;
font-weight: 900;
}
.origin-price {
margin-right: 10rpx;
color: rgba(0, 35, 28, 0.42);
font-size: 22rpx;
font-weight: 800;
text-decoration: line-through;
}
.store-scene .origin-price {
color: rgba(84, 43, 22, 0.42);
}
.price {
color: #f0441f;
font-size: 30rpx;
font-weight: 900;
}
.progress-bar {
height: 14rpx;
margin-top: 16rpx;
border-radius: 999rpx;
background: rgba(214, 239, 210, 0.8);
overflow: hidden;
}
.progress-inner {
height: 100%;
border-radius: 999rpx;
background: linear-gradient(90deg, #a6ffea 0%, #e3ff96 100%);
}
.store-scene .progress-inner {
background: linear-gradient(90deg, #ff7444 0%, #ffd16e 100%);
}
.progress-copy {
margin-top: 10rpx;
color: rgba(0, 35, 28, 0.52);
font-size: 21rpx;
font-weight: 800;
}
.store-scene .progress-copy {
color: #8f715b;
}
.card-bottom {
height: 58rpx;
margin-top: 16rpx;
padding-top: 12rpx;
border-top: 1rpx solid rgba(20, 90, 70, 0.08);
display: flex;
align-items: center;
}
.time-copy {
flex: 1;
color: rgba(0, 35, 28, 0.58);
font-size: 22rpx;
font-weight: 800;
}
.store-scene .time-copy {
color: #8f715b;
}
.join-btn {
width: 160rpx;
height: 50rpx;
border-radius: 999rpx;
background: linear-gradient(90deg, rgba(227, 255, 150, 1), rgba(166, 255, 234, 1));
color: #08735d;
font-size: 24rpx;
font-weight: 900;
line-height: 50rpx;
text-align: center;
box-shadow: 0 12rpx 24rpx rgba(13, 114, 82, 0.16);
}
.store-scene .join-btn {
background: linear-gradient(135deg, #ff7444 0%, #ff9f38 100%);
color: #fff;
box-shadow: 0 12rpx 24rpx rgba(255, 126, 39, 0.24);
}
.empty-delivery {
width: 98%;
min-height: 620rpx;
margin: 24rpx auto 0;
padding: 54rpx 36rpx;
box-sizing: border-box;
border-radius: 36rpx;
border: 1rpx solid rgba(166, 255, 234, 0.5);
background:
radial-gradient(circle at 78% 8%, rgba(255, 216, 168, 0.28) 0, rgba(255, 216, 168, 0) 150rpx),
linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(247, 255, 251, 0.96) 100%);
box-shadow: 0 16rpx 38rpx rgba(0, 35, 28, 0.06);
text-align: center;
}
.empty-delivery-icon {
width: 104rpx;
height: 104rpx;
margin: 0 auto 22rpx;
border-radius: 36rpx;
background: linear-gradient(135deg, #fff7d7 0%, #a6ffea 100%);
color: #125e50;
font-size: 32rpx;
font-weight: 900;
line-height: 104rpx;
box-shadow: 0 14rpx 26rpx rgba(0, 35, 28, 0.08);
}
.empty-delivery-title {
color: #243f38;
font-size: 30rpx;
font-weight: 900;
}
.empty-delivery-desc {
width: 88%;
margin: 14rpx auto 0;
color: #74817d;
font-size: 24rpx;
line-height: 38rpx;
}
.delivery-card {
width: 98%;
margin: 22rpx auto 0;
border-radius: 34rpx;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(253, 255, 250, 0.96) 100%);
border: 1rpx solid rgba(255, 255, 255, 0.86);
box-shadow: 0 16rpx 38rpx rgba(0, 35, 28, 0.06);
overflow: hidden;
position: relative;
}
.delivery-time-row {
display: flex;
align-items: center;
margin: 20rpx 0 20rpx 20rpx;
}
.appointment-delivery-tag {
height: 48rpx;
margin-right: 12rpx;
padding: 0 16rpx;
border-radius: 14rpx;
background: linear-gradient(135deg, #ff8f1f 0%, #ff6a3d 100%);
color: #fff;
font-size: 22rpx;
font-weight: 800;
line-height: 48rpx;
box-shadow: 0 8rpx 18rpx rgba(255, 106, 61, 0.16);
}
.delivery-time {
min-width: 242rpx;
height: 48rpx;
padding: 0 14rpx;
border-radius: 14rpx;
background: rgba(166, 255, 234, 0.42);
color: #125e50;
font-size: 24rpx;
font-weight: 700;
line-height: 48rpx;
}
.delivery-order-box {
width: 95%;
margin: 0 auto;
border-radius: 32rpx;
border: 1rpx solid rgba(166, 255, 234, 0.78);
background: rgba(255, 255, 255, 0.74);
box-shadow: inset 0 0 0 1rpx rgba(255, 255, 255, 0.7);
position: relative;
}
.delivery-fee-tag {
width: 94rpx;
height: 74rpx;
position: absolute;
right: 10rpx;
top: -58rpx;
border-radius: 26rpx 26rpx 20rpx 20rpx;
background: linear-gradient(135deg, #d9ff8c, #75e8c9);
color: #173f36;
font-size: 23rpx;
font-weight: 900;
line-height: 74rpx;
text-align: center;
box-shadow: 0 12rpx 24rpx rgba(20, 115, 88, 0.14);
}
.delivery-fee-tag--3 {
background: linear-gradient(135deg, #ffe08c, #ff9675);
color: #63351d;
}
.delivery-shop-row {
min-height: 70rpx;
padding: 12rpx 118rpx 8rpx 28rpx;
box-sizing: border-box;
display: flex;
align-items: flex-start;
}
.delivery-shop-name {
flex: 1;
min-width: 0;
font-weight: 800;
line-height: 38rpx;
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.delivery-goods-count {
flex-shrink: 0;
padding-left: 30rpx;
font-size: 24rpx;
font-weight: 800;
line-height: 38rpx;
color: #40534d;
}
.delivery-route {
width: 95%;
margin: 0 auto 18rpx;
font-size: 24rpx;
}
.delivery-route-row {
display: flex;
align-items: flex-start;
margin-top: 12rpx;
}
.delivery-route-icon {
width: 44rpx;
height: 44rpx;
margin-right: 12rpx;
border-radius: 50%;
background: linear-gradient(135deg, #e3ff96, #a6ffea);
color: #08735d;
font-size: 20rpx;
font-weight: 900;
line-height: 44rpx;
text-align: center;
flex-shrink: 0;
}
.delivery-route-icon--to {
background: rgba(209, 206, 255, 1);
color: #4e4a96;
}
.delivery-route-text {
flex: 1;
min-width: 0;
color: #40534d;
line-height: 44rpx;
word-break: break-all;
}
.delivery-phone {
width: 55rpx;
height: 55rpx;
margin-left: 12rpx;
flex-shrink: 0;
}
.delivery-commission,
.delivery-remark {
width: 95%;
margin: 20rpx auto;
color: #40534d;
font-size: 24rpx;
line-height: 42rpx;
}
.delivery-commission {
display: flex;
justify-content: space-between;
align-items: center;
}
.delivery-big-tag {
height: 30rpx;
line-height: 30rpx;
padding: 0 10rpx;
background: rgba(255, 117, 88, 1);
color: #fff;
border-radius: 10rpx;
font-size: 20rpx;
font-weight: 800;
}
.delivery-time-warning {
width: 95%;
margin: 20rpx auto 0;
padding: 18rpx 20rpx;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(255, 117, 88, 0.08);
border: 1rpx solid rgba(255, 117, 88, 0.18);
border-radius: 20rpx;
color: rgba(255, 57, 57, 1);
font-size: 24rpx;
line-height: 36rpx;
}
.delivery-time-warning-text {
flex: 1;
padding-right: 18rpx;
}
.delivery-time-warning-phone {
width: 55rpx;
height: 55rpx;
flex-shrink: 0;
}
.dispatch-action-box {
width: 95%;
margin: 20rpx auto 24rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.qiangdan-btn {
width: 100%;
height: 100rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, rgba(246, 255, 252, 0.98) 0%, rgba(235, 255, 249, 0.96) 100%);
border: 1rpx solid rgba(166, 255, 234, 0.72);
box-shadow: inset 0 0 0 1rpx rgba(255, 255, 255, 0.72), 0 12rpx 26rpx rgba(0, 35, 28, 0.06);
position: relative;
overflow: hidden;
touch-action: pan-y;
}
.qiangdan-btn--swiping {
box-shadow: inset 0 0 0 1rpx rgba(255, 255, 255, 0.62), 0 6rpx 14rpx rgba(0, 35, 28, 0.04);
}
.qiangdan-btn--swiping .qiangdan-progress {
background: rgba(108, 226, 202, 0.52);
}
.qiangdan-btn--low-perf,
.qiangdan-btn--low-perf .qiangdan-thumb {
box-shadow: none;
}
.qiangdan-progress {
width: 100%;
height: 100%;
border-radius: 999rpx;
background: linear-gradient(90deg, rgba(166, 255, 234, 0.42) 0%, rgba(108, 226, 202, 0.78) 100%);
transform: translate3d(0, 0, 0) scaleX(0);
transform-origin: left center;
will-change: transform;
backface-visibility: hidden;
}
.qiangdan-text {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
color: #2a665b;
font-size: 28rpx;
font-weight: 900;
letter-spacing: 2rpx;
}
.qiangdan-thumb {
width: 82rpx;
height: 82rpx;
border-radius: 50%;
position: absolute;
left: 0;
top: 9rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.96);
border: 1rpx solid rgba(166, 255, 234, 0.9);
box-shadow: 0 10rpx 22rpx rgba(0, 35, 28, 0.12);
color: #1b7a69;
font-size: 28rpx;
font-weight: 900;
will-change: transform;
backface-visibility: hidden;
}
@keyframes seckillEntrySweep {
0% {
left: -220rpx;
opacity: 0;
}
38% {
opacity: 0;
}
58% {
opacity: 1;
}
100% {
left: 220rpx;
opacity: 0;
}
}
@keyframes seckillBtnBeat {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
@keyframes heatParticle {
0%,
100% {
opacity: 0.5;
transform: translateY(0) scale(1);
}
50% {
opacity: 1;
transform: translateY(-6rpx) scale(1.2);
}
}
</style>