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.

695 lines
16 KiB

1 week ago
<template>
<view class="pending-page" :class="{'store-scene': isStoreScene}">
<view class="hero">
<view class="top-bar" @tap="back">
<view class="back-btn" :style="navPaddingStyle">
<uni-icons type="left" size="27" color="#fff"></uni-icons>
</view>
</view>
<view class="hero-copy">
<view class="hero-kicker">{{isStoreScene ? '校园团购' : '校园外卖'}}</view>
<view class="hero-title">{{isStoreScene ? '正在等你来拼团' : '附近饭搭子开团中'}}</view>
<view class="hero-sub">{{isStoreScene ? '同学们发起的到店团,凑齐就一起省' : '公开拼饭局都在这里,点进店铺直接参团'}}</view>
</view>
<view class="hero-badge">
<image src="/static/images/img/loading.gif" mode="aspectFit"></image>
<text>待成团</text>
</view>
</view>
<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="list-wrap">
<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="scene-mark">{{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="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">{{isStoreScene ? '参与到店团' : '参与拼饭局'}}</view>
</view>
</view>
<uni-load-more :status="loadStatus" v-if="groupList.length > 0" />
</view>
</view>
</template>
<script>
export default {
data() {
return {
scene: 'takeaway',
menuButtonInfo: {
top: 0
},
loadStatus: 'more',
navPaddingStyle: '',
totalPages: 1,
query: {
regionId: '',
status: 0,
isFace: 0,
merchantType: 1,
pageNumber: 1,
pageSize: 10,
keyWord: ''
},
groupList: []
}
},
computed: {
isStoreScene() {
return this.scene === 'storeGroup'
},
searchIconColor() {
return this.isStoreScene ? '#f0642f' : '#08735d'
}
},
onLoad(option) {
this.scene = option && option.scene ? option.scene : 'takeaway'
this.query.merchantType = this.isStoreScene ? 2 : 1
this.initRegion()
},
onShow() {
this.menuButtonInfo = uni.getMenuButtonBoundingClientRect()
this.navPaddingStyle = 'padding-top: ' + this.menuButtonInfo.top + 'px;'
},
onReachBottom() {
if (this.loadStatus === 'loading' || this.query.pageNumber >= this.totalPages) return
this.query.pageNumber += 1
this.getGroupList()
},
methods: {
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 || ''
} catch (e) {
this.query.regionId = ''
}
if (!this.query.regionId) {
this.tui.toast('请先选择区域')
return
}
this.getGroupList()
},
searchGroup() {
this.query.pageNumber = 1
this.totalPages = 1
this.groupList = []
this.getGroupList()
},
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'
})
},
joinGroup(item) {
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
})
},
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),
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 ? '团购' : '美食')
},
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) : '--'
},
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;
}
.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;
}
.hero {
min-height: 360rpx;
padding: 0 26rpx 36rpx;
box-sizing: border-box;
position: relative;
overflow: hidden;
background: linear-gradient(135deg, #0f8b6b 0%, #87eabf 58%, #fff0c8 100%);
color: #fff;
}
.store-scene .hero {
background: linear-gradient(115deg, rgba(255, 132, 80, 0.98) 0%, rgba(255, 185, 113, 0.94) 56%, rgba(255, 236, 218, 0.9) 100%);
}
.hero::after {
content: '';
width: 420rpx;
height: 420rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.22);
position: absolute;
right: -170rpx;
top: -130rpx;
}
.top-bar {
height: 98rpx;
position: relative;
z-index: 2;
}
.back-btn {
width: 68rpx;
height: 68rpx;
display: flex;
align-items: center;
justify-content: center;
}
.hero-copy {
position: relative;
z-index: 2;
width: 66%;
padding-top: 24rpx;
}
.hero-kicker {
display: inline-block;
padding: 8rpx 18rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.34);
font-size: 22rpx;
font-weight: 900;
}
.hero-title {
margin-top: 18rpx;
font-size: 48rpx;
line-height: 58rpx;
font-weight: 900;
}
.hero-sub {
margin-top: 12rpx;
font-size: 24rpx;
line-height: 36rpx;
font-weight: 800;
color: rgba(255, 255, 255, 0.86);
}
.hero-badge {
width: 190rpx;
height: 190rpx;
border-radius: 42rpx;
position: absolute;
right: 32rpx;
bottom: 42rpx;
z-index: 2;
background: rgba(255, 255, 255, 0.42);
box-shadow: 0 22rpx 44rpx rgba(0, 45, 29, 0.16);
text-align: center;
overflow: hidden;
}
.hero-badge image {
width: 120rpx;
height: 120rpx;
margin-top: 16rpx;
}
.hero-badge text {
display: block;
font-size: 23rpx;
font-weight: 900;
}
.search-card {
width: 94%;
margin: -32rpx auto 22rpx;
padding: 16rpx;
border-radius: 34rpx;
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: 72rpx;
padding: 0 16rpx;
border-radius: 28rpx;
background: #fff;
display: flex;
align-items: center;
}
.search-box input {
flex: 1;
min-width: 0;
height: 72rpx;
margin-left: 12rpx;
font-size: 24rpx;
color: #243d36;
}
.search-btn {
width: 86rpx;
height: 44rpx;
border-radius: 999rpx;
background: linear-gradient(90deg, #dcff9c, #baffec);
color: #08735d;
font-size: 22rpx;
font-weight: 900;
line-height: 44rpx;
text-align: center;
}
.store-scene .search-btn {
background: linear-gradient(135deg, #ff7444 0%, #ffae53 100%);
color: #fff;
}
.list-wrap {
width: 94%;
margin: 0 auto;
}
.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;
}
.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;
}
.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);
}
</style>