拼团功能完善

This commit is contained in:
2025-12-17 18:08:27 +08:00
parent 2fc5a428ff
commit 744f56468f
17 changed files with 2200 additions and 305 deletions

View File

@@ -0,0 +1,80 @@
// 引入 request 文件
import request from '@/common/api/request.js'
const url = '/order'
let platformType = '';
let payType='';
// #ifdef MP-WEIXIN
platformType = 'wechat'
payType='wechatPay'
// #endif
// #ifdef MP-ALIPAY
platformType = 'alipay'
payType='aliPay'
// #endif
export const warePage = (data) => {
return request({
url: url + '/user/gbOrder/ware/page',
method: 'get',
data: data
})
}
export const wareDetail = (data) => {
return request({
url: url + '/user/gbOrder/ware/detail',
method: 'get',
data: data
})
}
export const record = (data) => {
return request({
url: url + '/user/gbOrder/record/page',
method: 'get',
data: data
})
}
export const recordDetail = (data) => {
return request({
url: url + '/user/gbOrder/record/detail',
method: 'get',
data: data
})
}
export const exchange = (data) => {
// #ifdef MP-WEIXIN
const openId=uni.cache.get('userInfo').wechatOpenId;
// #endif
// #ifdef MP-ALIPAY
const openId=uni.cache.get('userInfo').alipayOpenId;
// #endif
return request({
url: url + '/user/gbOrder/exchange',
method: 'post',
data: {
platformType,
payType,
openId,
...data
}
})
}
export const applyRefund = (data) => {
return request({
url: url + '/user/gbOrder/applyRefund',
method: 'post',
data: data
})
}
export const cancelRefund = (data) => {
return request({
url: url + '/user/gbOrder/cancelRefund',
method: 'post',
data: data
})
}

View File

@@ -0,0 +1,97 @@
<template>
<view>
<up-popup
:show="show"
mode="center"
:safeAreaInsetBottom="mode === 'bottom' ? true : false"
>
<view class="popup-content">
<view class="top u-flex u-row-between">
<text class="font-bold u-font-32 color-333">{{ title }}</text>
<up-icon size="18" name="close" @click="show = false"></up-icon>
</view>
<up-line></up-line>
<scroll-view style="max-height: 50vh" :scroll-y="true">
<slot></slot>
</scroll-view>
<template v-if="showBottom">
<up-line></up-line>
<view class="bottom">
<view class="btn cancel" @click="close">{{ cancelText }}</view>
<view class="btn success" @click="confirm">{{ confirmText }}</view>
</view>
</template>
</view>
</up-popup>
</view>
</template>
<script setup>
const props = defineProps({
mode: {
type: String,
default: "center",
},
title: {
type: String,
default: "标题",
},
confirmText: {
type: String,
default: "确认",
},
cancelText: {
type: String,
default: "取消",
},
showBottom: {
type: Boolean,
default: true,
},
});
const show = defineModel({
type: Boolean,
default: false,
});
const emits = defineEmits(["close", "confirm"]);
function close() {
show.value = false;
emits("close");
}
function confirm() {
emits("confirm");
}
</script>
<style lang="scss">
.popup-content {
background: #fff;
width: 640rpx;
border-radius: 18rpx;
}
.top {
padding: 40rpx 48rpx;
}
.bottom {
padding: 48rpx 52rpx;
display: flex;
justify-content: space-between;
gap: 50rpx;
.btn {
flex: 1;
text-align: center;
padding: 18rpx 60rpx;
border-radius: 100rpx;
font-size: 32rpx;
border: 2rpx solid transparent;
&.success {
background-color: $my-main-color;
color: #fff;
}
&.cancel {
border-color: $my-main-color;
color: $my-main-color;
}
}
}
</style>

View File

@@ -0,0 +1,61 @@
<template>
<text class="status " :class="returnClass">{{status}}</text>
</template>
<script setup>
import { computed } from 'vue'
const props=defineProps({
status:{
default:''
}
})
const list =ref( [{
name: '全部',
value: ''
},
{
name: '待成团',
value: '',
class:'success'
},
{
name: '待核销',
value: ''
},
{
name: '已核销',
value: ''
},
{
name: '退款',
value: '',
class:'error'
},
])
const returnClass=computed(()=>{
const item=list.value.find(v=>props.status.includes(v.name))
return item?item.class:''
})
</script>
<style lang="scss">
.status {
padding: 8rpx 18rpx;
border-radius: 8rpx;
border: 2rpx solid transparent;
&.success {
border-color: rgba(123, 209, 54, 1);
color: rgba(123, 209, 54, 1);
background: rgba(123, 209, 54, 0.12);
}
&.error {
border-color: #FF1C1C;
color: #FF1C1C;
background: rgba(255, 28, 28, 0.18);
}
}
</style>

View File

@@ -1,59 +1,261 @@
<template>
<view class="min-page bg-f7 u-font-28">
<view class="min-page bg-f7 color-333 u-font-28 relative">
<view class="top" :style="topStyle">
<up-navbar bg-color="transparent" :fixed="false" :placeholder="false" title="订单详情" left-icon-color="#fff"
title-color="#fff"></up-navbar>
<view class="u-flex info u-col-center">
<image :src="imgs.map" class="map"></image>
</view>
</view>
<view class="bottom">
<view class="time">
<text class="color-666">剩余成团时间</text>
<view class="u-font-32">
<text class="number">{{returnNum(0)}}</text>
<text class="gap"></text>
<text class="number">{{returnNum(1)}}</text>
<text class="gap"></text>
<text class="number">{{returnNum(2)}}</text>
</view>
</view>
<view class="goods u-flex u-col-center">
<up-image width="158rpx" height="158rpx" radius="14rpx" :src="item.goodsImg"></up-image>
<view class=" u-flex-1 u-p-l-16">
<view class="u-flex u-col-center u-row-between">
<view class="u-flex tuan-members">
<image :src="imgs.pin" class="pin" mode=""></image>
<text class="">{{item.groupPeopleNum}}人团</text>
</view>
<statusVue></statusVue>
</view>
<view class="u-m-t-16 u-flex u-col-center">
<view style="width: 356rpx;">
<view class="font-bold" v-if="item.wareJson">{{item.wareJson
.wareName}}</view>
<view class="u-flex u-m-t-10 u-col-center ">
<view class="price">
<text class="u-font-30">¥</text>
<text class="u-font-48 font-bold">{{item.payAmount}} </text>
</view>
<view class="old-price u-m-l-32">
<text>¥</text>
<text>{{item.payAmount}} </text>
</view>
</view>
</view>
<view class="u-p-l-16 color-333">
数量{{item.num}}
</view>
</view>
</view>
</view>
<view class="refund" v-if="item.status=='待退款'">已申请退款需等待商家审核</view>
<view class="shop-box">
<view class="u-flex u-row-center u-flex-col u-col-center">
<up-qrcode :val="item.verifyCode" :size="104"></up-qrcode>
<view class="u-flex u-m-t-22 u-m-b-18 u-col-center">
<text>{{item.verifyCode}}</text>
<image @click="copyText(item.verifyCode)" class="copy" src="/groupBuying/static/image/copy.png">
</image>
</view>
</view>
<view class="shop ">
<view class="u-flex">
<text style="min-width: 180rpx;" class="color-666">可核销门店</text>
<text>{{item.shopName}}</text>
</view>
<view class="u-m-t-32 u-flex">
<text style="min-width: 180rpx;" class="color-666">门店地址</text>
<text>{{item.shopAddress}}</text>
</view>
</view>
</view>
<view class="members">
<view class="list">
<view class="item" v-for="(user,index) in item.users" :key="index">
<view class="box">
<view class="u-flex relative">
<up-avatar size="140rpx" :src="user.userAvatar"></up-avatar>
<view class="u-flex u-row-center absolute">
<text class="tuanzhang" v-if="index==0">团长</text>
</view>
</view>
<view class="u-line-1 u-m-t-16 color-000 text-center">{{user.userName}}</view>
</view>
</view>
<view class="item">
<view class="box">
<view class="u-flex relative">
<view class="add-box u-flex u-row-center">
<up-icon color="#D9D9D9" name="plus"></up-icon>
<button open-type="share" class="share" @click="share(item)">分享</button>
</view>
</view>
<view class="u-line-1 u-m-t-16 color-000 text-center">等待参团</view>
</view>
</view>
</view>
<view class="u-flex u-row-center">
<view class="pin-btn">立即参与拼团</view>
</view>
</view>
<view class="order">
<view class="u-flex u-row-between">
<view class="font-bold u-font-32">订单信息</view>
<view class="u-flex color-666" @click="showOrder=!showOrder" style="align-items: baseline;">
<text class="u-m-r-18">{{showOrder?'收起':'展开'}}</text>
<view class="guodu" :class="{rotate:!showOrder}">
<up-icon name="arrow-down" bold></up-icon>
</view>
</view>
</view>
<view class="u-m-t-16" v-if="showOrder">
<view class="u-flex u-row-between item">
<view class="color-666">商品总额</view>
<view class="">¥99.99</view>
</view>
<view class="u-flex u-row-between item">
<view class="color-666">实付金额</view>
<view class="">¥{{item.payAmount}}</view>
</view>
<view class="u-flex u-row-between item">
<view class="color-666">订单号</view>
<view class="">{{item.orderNo}}</view>
</view>
<view class="u-flex u-row-between item">
<view class="color-666">支付时间</view>
<view class="">{{item.payTime}}</view>
</view>
</view>
</view>
</view>
<view style="height: 100px;"></view>
<view class="btns">
<view class="btn" @click="refund(item)">申请退款</view>
<button open-type="share" class="btn main" @click="share(item)">邀请好友</button>
</view>
</view>
</template>
<script setup>
import statusVue from '@/groupBuying/components/status.vue'
import * as Api from '@/common/api/order/gbOrder.js'
import {
wxShare
} from '@/utils/share.js'
import {
computed,
reactive
} from 'vue';
} from 'vue'
import {
getRemainingHMS
} from '@/utils/countdown.js'
const imgs = {
bg: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/d21f2dfd7bec44618f2d5e4b88372b08.png',
bg1: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/604c3f917daa41af9239145196c6d3f3.png',
map: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/0a293f6e1a6a4e7b956379a5b6701104.png',
pin: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/a9a4f8f59a6d4a46abf91141df3531fb.png'
pin: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/2/3947892924dd481782331513aff00eb3.png'
}
const steps = ['发起拼团', '邀请好友', '成团优惠']
const topStyle = {
backgroundImage: 'url(' + imgs.bg + ')'
}
const tabs = reactive({
list: ['正在进行中', '我的拼团'],
sel: 1
const showOrder = ref(true)
function copyText(text) {
uni.setClipboardData({
data: text,
success() {}
})
}
const query = reactive({
shopId: '',
detailId: '',
})
const orders = reactive({
list: [{
name: '全部',
value: ''
},
{
name: '待成团',
value: ''
},
{
name: '待核销',
value: ''
},
{
name: '已核销',
value: ''
},
{
name: '退款',
value: ''
},
],
sel: 0
function init(opt) {
console.log(opt)
Object.assign(query, opt)
getDetail()
}
const item = reactive({})
function getDetail() {
Api.recordDetail(query).then(res => {
const wareJson = JSON.parse(res.wareJson)
wareJson.wareImgs = wareJson.wareImgs.split(',').filter(v => v)
res.wareJson = wareJson;
res.goodsImg = wareJson.wareImgs[0];
Object.assign(item, res)
console.log('item', item)
})
}
onLoad(init)
let timer = null
let nowTime = ref(Date.now())
timer = setInterval(() => {
nowTime.value = Date.now()
}, 1000)
const returnTime = computed(() => {
nowTime.value
return getRemainingHMS(item)
})
function returnNum(index) {
return returnTime.value.split(':')[index]
}
function refund(item) {
uni.showModal({
title: '提示',
content: '是否申请退款?',
showCancel: true,
success(res) {
if (res.confirm) {
Api.applyRefund({
recordId: item.id,
orderNo: item.orderNo,
}).then(res => {
if (res) {
uni.showToast({
title: '申请成功'
})
setTimeout(() => {
getDetail()
}, 1000)
}
})
}
}
})
}
let shareItem = null
function share(item) {
shareItem = item
}
onShareAppMessage(() => {
console.log('onShareAppMessage')
return wxShare({
title: shareItem.wareJson.wareName,
imageUrl: shareItem.goodsImg,
query: `detailId=${shareItem.id}&shopId=${shareItem.shopId}`,
type: 2,
})
})
</script>
@@ -69,243 +271,217 @@
font-weight: 700;
}
.map {
width: 48rpx;
height: 48rpx;
}
}
.steps {
display: flex;
padding: 20rpx 20rpx;
gap: 20rpx;
border-radius: 14rpx 14rpx 0 0;
background: linear-gradient(90deg, #FFF5E6 0%, #FFD2CA 100%);
.bottom {
margin: 0 28rpx;
transform: translateY(-160rpx);
.step {
.time {
padding: 18rpx 24rpx;
background: #FFF4E2;
display: flex;
}
align-items: center;
border-radius: 16rpx 16rpx 0 0;
.index {
border-radius: 60rpx;
color: #fff;
width: 44rpx;
height: 44rpx;
line-height: 44rpx;
text-align: center;
background: #E55626;
}
.text {
color: #5A352F;
margin-left: 10rpx;
margin-right: 22rpx;
white-space: nowrap;
}
.icon {
color: rgba(90, 53, 47, 0.42);
}
.icon1 {
color: #5A352F;
}
}
.lists {
padding: 18rpx 10rpx;
.item {
display: flex;
padding: 32rpx 16rpx;
border-radius: 32rpx;
background: #FFF;
margin-bottom: 14rpx;
.numbers {
background: #4C2828;
padding: 8rpx 18rpx;
border-radius: 10rpx;
font-weight: 700;
color: #FAEAC6;
white-space: nowrap;
}
.name {
max-width: 334rpx;
}
.members {
margin-top: 26rpx;
background: #FDF1CB;
padding: 4rpx 18rpx;
border-radius: 10rpx 10rpx 10rpx 0;
width: fit-content;
font-size: 24rpx;
font-weight: 700;
color: #ED5A2E;
}
.info {
background-image: url(https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/604c3f917daa41af9239145196c6d3f3.png);
width: 410rpx;
height: 102rpx;
background-size: cover;
transform: translateY(-10rpx);
display: flex;
.left {
width: 306rpx;
color: #fff;
padding: 16rpx 22rpx;
}
.right {
padding-left: 16rpx;
margin-top: 30rpx;
.pin {
width: 76rpx;
height: 54rpx;
}
}
}
}
}
.filters {
padding: 16rpx 28rpx;
border-radius: 6rpx 6rpx 16rpx 16rpx;
background: #FFF;
}
.tabs {
display: flex;
justify-content: space-between;
font-size: 24rpx;
color: #999;
padding: 32rpx 88rpx;
.tabs {
transition: all .3s linear;
}
.active {
color: #ED5A2E;
font-size: 32rpx;
font-weight: 700;
font-weight: 700;
}
}
.translateY20 {
transform: translateY(-20rpx);
}
.orderStatus {
padding: 26rpx 28rpx;
justify-content: space-between;
.orderState {
color: #999;
transition: all .3s linear;
&.active {
color: #000;
font-size: 32rpx;
font-weight: 700;
}
}
}
.orders {
padding: 0 26rpx 36rpx;
.item {
padding: 32rpx 24rpx;
border-radius: 16rpx;
background: #FFF;
margin-bottom: 32rpx;
.numbers {
background-color: #ED5A2E;
padding: 16rpx 30rpx;
border-radius: 12rpx;
font-size: 32rpx;
.number {
padding: 6rpx;
border-radius: 16rpx;
background: #ED5A2E;
color: #fff;
font-weight: 700;
line-height: 36rpx;
font-size: 32rpx;
margin-left: 18rpx;
margin-right: 18rpx;
}
.status {
padding: 8rpx 18rpx;
border-radius: 8rpx;
border: 2rpx solid transparent;
.gap {
font-size: 32rpx;
}
}
&.success {
border-color: rgba(123, 209, 54, 1);
color: rgba(123, 209, 54, 1);
background: rgba(123, 209, 54, 0.12);
}
.goods {
background-color: #fff;
padding: 22rpx 24rpx 0;
border-radius: 0 0 16rpx 16rpx;
&.error {
border-color: #FF1C1C;
color: #FF1C1C;
background: rgba(255, 28, 28, 0.18);
}
.pin {
width: 60rpx;
height: 38rpx;
}
.tuan-members {
padding: 0 16rpx 0 0;
border-radius: 0 6rpx 6rpx 0;
background: #4C2828;
color: #f5d9ad;
font-weight: 700;
}
.price {
color: #ED5A2E;
font-weight: 700;
line-height: 36rpx;
font-size: 40rpx;
color: #ed5a2e;
}
.info {
padding: 16rpx 34rpx;
flex-direction: column;
align-items: flex-start;
border-radius: 8rpx;
background: #F8F8F8;
.title {
min-width: 208rpx;
padding-right: 8rpx;
box-sizing: border-box;
}
.stitle {
color: #666;
&.price {
font-size: 32rpx;
color: #ed5a2e;
font-weight: 700;
}
}
.old-price {
color: #666666;
}
}
.btns {
margin-top: 28rpx;
.u-font-48 {
font-size: 48rpx;
}
.shop-box {
background: #fff;
padding: 32rpx 30rpx 28rpx 30rpx;
border-radius: 0 0 16rpx 16rpx;
}
.copy {
width: 22rpx;
height: 22rpx;
margin-left: 22rpx;
}
.shop {
border-radius: 8rpx;
background: #F8F8F8;
padding: 16rpx 34rpx;
}
.relative {
position: relative;
}
.members {
padding: 26rpx 24rpx;
border-radius: 16rpx;
margin-top: 32rpx;
background-color: #fff;
.list {
display: flex;
justify-content: flex-end;
gap: 34rpx;
}
.btn {
padding: 8rpx 14rpx;
border-radius: 10rpx;
border: 2rpx solid #D9D9D9;
background: #FFF;
.item {
width: calc(100% / 3);
display: flex;
&.black {
border-color: #343030;
background-color: #343030;
color: #fff;
.add-box {
width: 140rpx;
height: 140rpx;
border: 2rpx dashed #D9D9D9;
border-radius: 50%;
overflow: hidden;
position: relative;
.share {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
opacity: 0;
}
}
&:nth-of-type(2n) {
justify-content: center;
}
&:nth-of-type(3n) {
justify-content: flex-end;
}
.box {
display: flex;
flex-direction: column;
}
.absolute {
bottom: 0;
display: flex;
justify-content: center;
left: 0;
right: 0;
}
.tuanzhang {
padding: 8rpx 18rpx;
border-radius: 20rpx;
background: #ED5A2E;
color: #fff;
bottom: 0;
}
}
}
.order {
padding: 32rpx 28rpx;
border-radius: 16rpx;
margin-top: 32rpx;
background-color: #fff;
.item {
padding: 16rpx 0;
}
}
}
.btns {
position: fixed;
left: 87rpx;
right: 87rpx;
bottom: 0;
padding-bottom: 60rpx;
display: flex;
gap: 40rpx;
.btn {
flex: 1;
padding: 16rpx 28rpx;
border-radius: 200rpx;
text-align: center;
font-size: 32rpx;
line-height: 1;
margin: 0;
justify-content: center;
border: 2rpx solid #E8AD7B;
background: #FFF;
color: #E8AD7B;
&.main {
background-color: #E8AD7B;
color: #fff;
}
}
}
.guodu {
transition: all .3s;
}
.pin-btn {
padding: 14rpx 60rpx;
border-radius: 200rpx;
text-align: center;
font-size: 32rpx;
justify-content: center;
border: 2rpx solid #E8AD7B;
background: #E8AD7B;
color: #fff;
margin-top: 32rpx;
}
.rotate {
transform: rotate(-90deg);
}
.refund {
padding: 18rpx 164rpx;
border-radius: 0 0 16rpx 16rpx;
background: #FFF4E2;
font-weight: 700;
margin-bottom: 32rpx;
}
</style>

View File

@@ -0,0 +1,646 @@
<template>
<view class="color-333 u-font-28" v-if="item.id">
<view>
<up-swiper height="428rpx" :list="coverImgs" @click="prveImg"></up-swiper>
</view>
<view class="sku">
<view class="u-flex u-col-center">
<text class="price">¥{{item.groupPrice}}</text>
<text class="old-price">¥{{item.originalPrice}}</text>
</view>
<view>
<view class="u-m-t-16 text" v-if="item.limitBuyNum"> 限购{{item.limitBuyNum}} </view>
<view class="text u-m-t-10">已团{{item.groupedNum||0}}</view>
</view>
</view>
<view class="goods">
<view class="goods-name">{{item.wareName}}</view>
<view class="u-m-t-20 color-666">
{{item.wareDetail}}
</view>
</view>
<view class="bg-f7" style="height: 32rpx"></view>
<view class="desc">
<view class="u-flex">
<view class="color-666 no-wrap" style="min-width: 180rpx;">可核销门店</view>
<view class="">{{item.shopName}}</view>
</view>
<view class="u-flex u-m-t-16 u-col-baseline">
<view class="color-666 no-wrap" style="min-width: 180rpx;">门店地址</view>
<view class="">{{item.shopAddress}}</view>
</view>
</view>
<view class="bg-f7" style="height: 32rpx"></view>
<view class="groups">
<view class="color-000 u-m-b-28 u-font-32 font-700">立即拼团</view>
<view class="item u-flex" v-for="(item,index) in item.gbOrderList" :key="index">
<up-avatar size="76rpx" :src="item.avatar"></up-avatar>
<view class="u-flex u-flex-1 u-p-l-22 u-col-center u-row-between">
<view>
<view class="color-000 u-line-1" style="max-width: 180rpx;">{{item.nickName}}</view>
<view class="main-color u-m-t-2">{{returnNeedPerpole(item)}}人拼成</view>
</view>
<view class="u-m-t-22">
<text class="color-666">剩余</text>
<text class="main-color">{{getRemainingHMS(item)}}</text>
</view>
<view class="btn" @click="fastBuy(item)">快速拼成</view>
</view>
</view>
</view>
<view class="bg-f7" style="height: 32rpx"></view>
<view class="desc">
<view class="u-flex">
<view class="color-666 no-wrap" style="min-width: 180rpx;">可核销门店</view>
<view class="">这里是店铺名称</view>
</view>
<view class="u-flex u-m-t-16 u-col-baseline">
<view class="color-666 no-wrap" style="min-width: 180rpx;">门店地址</view>
<view class="">站前北街7号附近</view>
</view>
</view>
<view class="bg-f7" style="height: 24rpx"></view>
<view class="goods-detail" v-if="item.goodsCategory!='优惠券'">
<view class="u-flex u-row-center">
<view class="title">商品详情</view>
</view>
<view class="u-m-t-32">
<image class="w-full" v-for="(item,index) in item.wareCommentImgs" :key="index" mode="widthFix"
:src="item">
</image>
</view>
</view>
<view style="height: 140px"></view>
<view class="fixed-bottom u-flex u-row-center">
<view v-if="isCanExchange" class="btn" @click="exchangeClick">
{{returnBtmText}}
</view>
<view class="btn gray" v-else>
{{returnBtmText}}
</view>
</view>
<!-- 需要支付的弹窗 -->
<up-popup :show="popupData.show" mode="bottom" closeOnClickOverlay @close="popupData.show = false">
<view class="popup-content">
<view class="popup-content-top u-flex u-row-right">
<up-icon name="close" bold="" @click="popupData.show = false"></up-icon>
</view>
<view class="goods-info">
<view class="u-flex">
<image class="cover" :src="coverImgs[0]"></image>
<view class="u-flex u-flex-1 u-row-between u-p-l-16 u-col-center">
<view>
<view class="u-font-32 font-bold">{{item.wareName}}</view>
<view class="u-m-t-12 color-666 u-line-2">{{item.wareDetail}}</view>
</view>
<view>
<view class="price">¥{{item.groupPrice}}</view>
<view class="old-price">¥{{item.originalPrice}}</view>
<view class="limitBuyNum" v-if="item.limitBuyNum"> 限购{{item.limitBuyNum}} </view>
</view>
</view>
</view>
</view>
<view class="bottom u-flex u-row-between u-col-center">
<view class="u-flex u-col-baseline">
<text class="color-666">合计</text>
<text class=" price">{{totalPrice}}</text>
</view>
<view class="u-flex">
<up-icon name="minus-circle" size="20" color="#666" @click="changeNumber('-')"></up-icon>
<text class="u-m-l-20 u-m-r-20">{{number}}</text>
<up-icon name="plus-circle-fill" size="20" color="#ED5A2E" @click="changeNumber('+')"></up-icon>
</view>
</view>
<view class="u-m-t-42 u-flex u-row-center">
<view class="btn" @click="payExchange">去支付</view>
</view>
</view>
</up-popup>
<!-- 兑换确认弹窗end -->
</view>
</template>
<script setup>
import {
computed,
reactive,
watch
} from "vue";
import dayjs from "dayjs";
import {
getOpenId
} from '@/utils/uniapp.js'
import modal from "@/groupBuying/components/modal.vue";
import * as Api from '@/common/api/order/gbOrder.js'
import {
pay
} from '@/utils/pay.js'
const imgs = {
bg: "https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/2/9fd6a3ad2b384f6cb4e88ed6b77bd334.png",
};
const number = ref(1)
import {
BigNumber
} from "bignumber.js";
const totalPrice = computed(() => {
if (!item.groupPrice) {
return 0;
}
return BigNumber(number.value).times(item.groupPrice).toNumber()
})
function changeNumber(step) {
if (step === '-') {
if (number.value == 1) {
return
}
number.value--
return
}
if (step === '+') {
number.value++
return
}
}
const modalData = reactive({
show: false,
});
function prveImg(index) {
uni.previewImage({
urls: coverImgs.value,
current: index
})
}
const popupData = reactive({
show: false,
item:null
});
function exchangeClick() {
popupData.show = true
}
function fastBuy(item){
popupData.item=item;
popupData.show = true
}
watch(()=>popupData.show,(newval)=>{
if(!newval){
popupData.item=null
}
})
function confirmExchange() {
// modalData.show = false;
exchange()
}
async function payExchange() {
uni.showLoading({
title: '支付中……'
})
const openId = await getOpenId()
uni.hideLoading()
if (openId) {
Api.exchange({
paramId: item.id,
shopId: item.shopId,
number: 1,
price: item.extraPrice,
groupOrderNo:popupData.item?popupData.item.groupOrderNo:'',
openId
}).then(orderRes => {
popupData.show = false;
pay(orderRes.payInfo).then(res => {
console.log(res)
if (res) {
uni.redirectTo({
url: '/groupBuying/success/index?detailId='+orderRes.goodsRecord.id
})
} else {
uni.showToast({
title: '开团失败',
icon: 'none'
})
}
})
})
} else {
uni.showToast({
title: '鉴权失败,兑换失败',
icon: 'none'
})
}
}
async function exchange() {
uni.showLoading({
title: '兑换中……'
})
const openId = await getOpenId()
uni.hideLoading()
if (openId) {
Api.exchange({
pointsGoodsId: item.id,
shopId: item.shopId,
number: 1,
price: item.extraPrice,
openId
}).then(res => {
modalData.show = false;
if (res) {
uni.setStorageSync('exchange_goods_success', res)
uni.redirectTo({
url: '/scoreShop/success/index'
})
} else {
uni.showToast({
title: '兑换失败',
icon: 'none'
})
}
})
} else {
uni.showToast({
title: '鉴权失败,兑换失败',
icon: 'none'
})
}
}
const item = reactive({
goodsDescription: []
})
const isCanExchange = computed(() => {
if (item.quantity <= 0) {
return false
}
if (item.limitQuota && item.boughtCount >= item.limitQuota) {
return false
}
return true
})
const returnBtmText = computed(() => {
if (isCanExchange.value) {
return '立即开团'
}
if (pointsUser.pointBalance < item.requiredPoints) {
const num = item.requiredPoints - pointsUser.pointBalance
return `积分不足,还差${num}积分`
}
if (item.quantity <= 0) {
return `库存不足`
}
if (item.limitQuota && item.boughtCount >= item.limitQuota) {
return `单人兑换已达上限`
}
})
const query = reactive({
shopId: '',
wareId: '',
})
const coverImgs = ref([])
function init(opt) {
console.log(opt)
Object.assign(query, opt)
getDetail()
}
function getDetail() {
Api.wareDetail(query).then(res => {
res.wareCommentImgs = res.wareCommentImgs.split(',').filter(v=>v)
Object.assign(item, res)
console.log(item)
coverImgs.value = res.wareImgs.split(',')
})
}
/**
* 计算剩余时间差(毫秒)
* @param {Object} item - 包含groupEndTime的订单/拼团对象
* @returns {number} 剩余时间(毫秒)
*/
function returnRemainingTime(item) {
if (!item?.groupEndTime) return 0; // 容错无结束时间则返回0
return dayjs(item.groupEndTime).valueOf() - dayjs().valueOf();
}
/**
* 将毫秒差格式化为 HH:MM:SS最多72小时
* @param {number} ms - 时间差(毫秒)
* @returns {string} 格式化后的时分秒(如 09:09:09、72:00:00、00:00:00
*/
function formatTimeToHMS(ms) {
// 边界1已过期/无剩余时间 → 显示00:00:00
if (ms <= 0) return '00:00:00';
// 边界2超过72小时 → 按72小时算72*60*60*1000 = 259200000毫秒
const maxMs = 72 * 60 * 60 * 1000;
const validMs = Math.min(ms, maxMs);
// 转换为总秒数(取整,避免小数)
const totalSeconds = Math.floor(validMs / 1000);
// 拆解小时、分钟、秒
const hours = Math.floor(totalSeconds / 3600);
const remainingSeconds = totalSeconds % 3600;
const minutes = Math.floor(remainingSeconds / 60);
const seconds = remainingSeconds % 60;
// 补零(确保两位数,如 9 → 09
const pad = (num) => String(num).padStart(2, '0');
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
}
let timer=null
let nowTime=ref(Date.now())
timer=setInterval(()=>{
nowTime.value=Date.now()
},1000)
// 组合使用:获取格式化后的剩余时间
function getRemainingHMS(item) {
nowTime.value
const ms = returnRemainingTime(item);
return formatTimeToHMS(ms);
}
function returnNeedPerpole(data){
return data.groupPeopleNum-data.currentPeopleNum
}
onLoad(init)
</script>
<style lang="scss" scoped>
$topHeight: 350rpx;
.top-img {
width: 750rpx;
height: $topHeight;
}
.top {
margin: 14rpx 18rpx;
background-size: cover;
height: $topHeight;
box-sizing: border-box;
padding-left: 70rpx;
padding-top: 95rpx;
.name {
color: #000000;
font-size: 36rpx;
font-weight: 700;
}
.info {
color: #333333;
font-size: 48rpx;
font-weight: 700;
margin-top: 62rpx;
}
}
.sku {
display: flex;
justify-content: space-between;
padding: 20rpx 36rpx;
align-items: center;
background: linear-gradient(90deg, #ff4a63 0%, #fd1f48 100%);
.price {
color: #fff;
font-weight: 700;
font-size: 40rpx;
}
.old-price {
margin-left: 16rpx;
color: #E7E7E7;
opacity: .85;
font-weight: 700;
}
.text {
color: #fff;
font-size: 28rpx;
}
}
.goods {
padding: 20rpx 28rpx;
background: #fff;
.goods-name {
font-size: 32rpx;
font-weight: 700;
color: #333;
}
}
.desc {
padding: 32rpx 28rpx;
background-color: #fff;
}
.goods-detail {
padding: 32rpx;
.title {
position: relative;
padding: 0 22rpx;
&::before {
content: "";
display: block;
position: absolute;
right: 100%;
width: 70rpx;
height: 2rpx;
top: 50%;
transform: translateY(-50%);
background: linear-gradient(90deg, #f7f8f9 0%, #c9cbcc 100%);
}
&::after {
left: 100%;
content: "";
position: absolute;
top: 50%;
transform: translateY(-50%);
display: block;
width: 70rpx;
height: 2rpx;
background: linear-gradient(90deg, #c9cbcc 0%, #f7f8f9 100%);
}
}
}
.fixed-bottom {
position: fixed;
background-color: #fff;
left: 0;
right: 0;
padding-bottom: 30px;
padding-top: 32rpx;
background-color: #fff;
bottom: 0;
z-index: 10;
.btn {
padding: 22rpx;
border-radius: 200rpx;
font-size: 32rpx;
color: #fff;
width: 556rpx;
text-align: center;
background-color: #E8AD7B;
&.gray {
background: #9999992b;
color: #999999;
}
}
}
.waring {
background-color: rgba(255, 204, 0, 0.09);
padding: 32rpx 24rpx;
color: #ff8d28;
}
.popup-content {
font-size: 28rpx;
min-height: 300px;
.popup-content-top {
padding: 32rpx 28rpx;
border-bottom: 1px solid #ededed;
}
.goods-info {
padding: 32rpx 28rpx;
border-bottom: 1px solid #ededed;
.cover {
width: 184rpx;
height: 184rpx;
border-radius: 16rpx;
background: #d9d9d9;
&.bg-fff {
background-color: #fff;
}
}
.price {
font-size: 32rpx;
font-weight: 700;
color: #ed5a2e;
line-height: 46rpx;
}
.old-price {
font-size: 32rpx;
color: #999;
text-decoration-line: line-through;
line-height: 48rpx;
}
.limitBuyNum {
color: #666;
line-height: 42rpx;
}
}
.bottom {
padding: 20rpx;
border-bottom: 1px solid #ededed;
.price {
color: #ed5a2e;
font-size: 32rpx;
font-weight: 700;
}
}
.btn {
display: flex;
padding: 22rpx 214rpx;
align-items: flex-start;
gap: 20rpx;
border-radius: 66rpx;
background: #e8ad7b;
font-size: 32rpx;
color: #fff;
font-size: 700;
}
}
.w-full {
width: 100%;
}
.groups{
padding: 28rpx 22rpx;
background-color: #fff;
.item{
padding: 28rpx 0;
border-bottom: 2rpx solid #EDEDED;
&:last-child{
border-bottom: none;
}
.main-color{
color: #ed5a2e;
}
.btn{
padding: 8rpx 26rpx;
border-radius: 36rpx;
background: #E8AD7B;
font-weight: 700;
color: #fff;
}
}
}
</style>

View File

@@ -0,0 +1,25 @@
<template>
<view class="u-flex box">
<text class="color-333" v-if="modelValue">modelValue</text>
<text v-else class="color-999">成团人数</text>
<view class="u-flex u-m-l-20">
<up-icon name="arrow-down" color="#999" size="14"></up-icon>
</view>
</view>
</template>
<script setup>
const modelValue=defineModel({
default:''
})
</script>
<style lang="scss">
.box{
padding: 12rpx 24rpx;
border-radius: 8rpx;
border: 2rpx solid #DDDFE6;
}
</style>

View File

@@ -6,7 +6,7 @@
left-icon-color="#fff" title-color="#fff"></up-navbar>
<view class="u-flex info u-col-center">
<image :src="imgs.map" class="map"></image>
<view class="u-line-1 u-m-l-20">这里是店铺名称</view>
<view class="u-line-1 u-m-l-20">{{shopInfo.shopName||''}}</view>
</view>
</view>
<view class="translateY20">
@@ -30,8 +30,10 @@
@click="tabs.sel=index">{{item}}</view>
</view>
<view class="u-flex" v-if="tabs.sel==0">
<up-search></up-search>
<view class="u-flex u-col-center" v-if="tabs.sel==0">
<up-search v-model="query.wareName" @search="refresh" @clear="refresh"
@custom="refresh"></up-search>
<!-- <perpoleNumber v-model="query.groupPeopleNum"></perpoleNumber> -->
</view>
<view class="u-flex orderStatus" v-if="tabs.sel==1">
<view class="orderState" :class="{'active':orders.sel==index}"
@@ -47,24 +49,24 @@
<view class="lists" v-if="tabs.sel==0">
<view class="item" v-for="(item,index) in 10" :key="index">
<view class="item" v-for="(item,index) in list" :key="index" @click="toDetail(item)">
<up-image width="218rpx" radius="16rpx" height="218rpx"></up-image>
<up-image width="218rpx" radius="16rpx" height="218rpx" :src="returnCoverImg(item)"></up-image>
<view class="u-flex-1 u-p-l-16">
<view class="u-flex u-col-center">
<text class="numbers">3人团</text>
<text class="u-line-1 font-bold u-m-l-18 name">这里是商品名称啊啊嗷嗷啊啊</text>
<text class="numbers">{{item.groupPeopleNum}}人团</text>
<text class="u-line-1 font-bold u-m-l-18 name">{{item.wareName}}</text>
</view>
<view class="members">已团9999</view>
<view class="info">
<view class="left">
<view class="">
<text class="u-font-24">拼团到手</text>
<text class="u-font-32 font-bold"> ¥333</text>
<text class="u-font-32 font-bold"> ¥{{item.groupPrice}}</text>
</view>
<view style="opacity: 0.84;">
<text class="u-font-24">原价</text>
<text class=""> ¥333</text>
<text class=""> ¥{{item.originalPrice}}</text>
</view>
</view>
<view class="right">
@@ -76,40 +78,41 @@
</view>
<view class="orders" v-else-if="tabs.sel==1">
<view class="item" v-for="(item,index) in 10" :key="index">
<view class="item" v-for="(item,index) in list" :key="index" @click="toOrderDetail(item)">
<view class="u-flex u-col-center u-row-between">
<text class="numbers">3人团</text>
<text class="status success">待成团</text>
<text class="numbers">{{1}}人团</text>
<statusVue :status="item.status"></statusVue>
</view>
<view class="u-m-t-32 u-flex">
<up-image width="154rpx" height="154rpx"></up-image>
<up-image width="154rpx" height="154rpx" :src="item.goodsImg"></up-image>
<view class="u-flex-1 u-flex u-p-l-30 u-col-center u-row-between">
<view>
<view class="">这里是商品名称</view>
<view class="u-m-t-34 price">¥999.99</view>
<view class="">{{item.wareJson.wareName}}</view>
<view class="u-m-t-34 price">¥{{item.payAmount}}</view>
</view>
<text class="u-font-40 font-bold">x1</text>
<text class="u-font-40 font-bold">x{{item.num}}</text>
</view>
</view>
<view class="u-m-t-32 info">
<view class="u-flex u-col-center">
<text class="title">可核销门店</text>
<text class="stitle">这里是店铺名称</text>
<text class="stitle">{{item.shopName}}</text>
</view>
<view class="u-flex u-m-t-32 u-col-center">
<text class="title ">门店地址</text>
<text class="stitle">这里是店铺名称</text>
<text class="stitle">{{item.shopAddress}}</text>
</view>
<view class="u-flex u-m-t-32 u-col-center">
<view class="u-flex u-m-t-32 u-col-center" v-if="showTime(item)">
<text class="title">剩余成团时间</text>
<text class="stitle price">00:39:15</text>
<text class="stitle price">{{returnTime(item)}}</text>
</view>
</view>
<view class="btns">
<view class="btn ">申请退款</view>
<view class="btn black ">邀请好友</view>
<view class="btns" v-if="showBtns(item)">
<view class="btn " @click.stop="refund(item)">申请退款</view>
<view class="btn " @click.stop="cancelRefund(item)" v-if="item.status=='退款中'">取消退款</view>
<button open-type="share" class="btn black" @click.stop="share(item)">邀请好友</button>
</view>
</view>
@@ -119,24 +122,40 @@
<script setup>
import {
reactive
} from 'vue';
wxShare
} from '@/utils/share.js'
import {
getRemainingHMS
} from '@/utils/countdown.js'
import {
onShow
} from '@dcloudio/uni-app'
import * as Api from '@/common/api/order/gbOrder.js'
import perpoleNumber from './components/perpole-number.vue'
import statusVue from '@/groupBuying/components/status.vue'
import {
computed,
reactive,
watch
} from 'vue'
const imgs = {
bg: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/d21f2dfd7bec44618f2d5e4b88372b08.png',
bg1: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/604c3f917daa41af9239145196c6d3f3.png',
map: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/0a293f6e1a6a4e7b956379a5b6701104.png',
pin: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/a9a4f8f59a6d4a46abf91141df3531fb.png'
pin: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/3/4bcce5bdfa074f2a838a0209fac24241.png'
}
const shopInfo = ref(uni.cache.get('shopInfo'))
const steps = ['发起拼团', '邀请好友', '成团优惠']
const topStyle = {
backgroundImage: 'url(' + imgs.bg + ')'
}
const tabs = reactive({
list: ['正在进行中', '我的拼团'],
sel: 1
list: ['活动列表', '我的拼团'],
sel: 0
})
const orders = reactive({
list: [{
@@ -145,23 +164,215 @@
},
{
name: '待成团',
value: ''
value: '待成团'
},
{
name: '待核销',
value: ''
value: '待核销'
},
{
name: '已核销',
value: ''
value: '已核销'
},
{
name: '退款',
value: ''
value: '退款'
},
],
sel: 0
})
const query = reactive({
page: 1,
size: 10,
wareName: '',
groupPeopleNum: '',
shopId: uni.cache.get('shopId'),
})
const list = ref([])
const isEnd = ref(false)
const status = computed(() => {
return orders.list[orders.sel].value
})
watch(() => status.value, () => {
refresh()
})
function getData() {
if (tabs.sel == 0) {
Api.warePage(query).then(res => {
const newArr = res.records || []
if (query.page == 1) {
list.value = newArr
} else {
list.value.push(...newArr)
}
isEnd.value = query.page >= res.totalPage * 1 ? true : false
})
return
}
if (tabs.sel == 1) {
const {
page,
size,
shopId
} = query
Api.record({
page,
size,
shopId,
status: status.value,
}).then(res => {
const newArr = (res.records || []).map(v => {
const wareJson = JSON.parse(v.wareJson)
wareJson.wareImgs = wareJson.wareImgs.split(',').filter(v => v)
return {
...v,
goodsImg: wareJson.wareImgs[0],
wareJson,
}
})
if (query.page == 1) {
list.value = newArr
} else {
list.value.push(...newArr)
}
console.log(list.value)
isEnd.value = query.page >= res.totalPage * 1 ? true : false
})
return
}
}
function toDetail(item) {
uni.navigateTo({
url: '/groupBuying/goodsDetail/goodsDetail?wareId=' + item.id + '&shopId=' + item.shopId
})
}
function toOrderDetail(item) {
uni.navigateTo({
url: '/groupBuying/detail/index?detailId=' + item.id + '&shopId=' + item.shopId
})
}
function returnCoverImg(item) {
if (!item.wareImgs) {
return ''
}
const arr = item.wareImgs.split(',')
return arr[0]
}
function refresh() {
query.page = 1
isEnd.value = false
getData()
}
function refund(item) {
uni.showModal({
title: '提示',
content: '是否申请退款?',
showCancel: true,
success(res) {
if (res.confirm) {
Api.applyRefund({
recordId: item.id,
orderNo: item.orderNo,
}).then(res => {
if (res) {
uni.showToast({
title: '申请成功'
})
setTimeout(() => {
refresh()
}, 1000)
}
})
}
}
})
}
function camcelRefund(item) {
uni.showModal({
title: '提示',
content: '是否取消退款?',
showCancel: true,
success(res) {
if (res.confirm) {
Api.cancelRefund({
recordId: item.id,
orderNo: item.orderNo,
}).then(res => {
if (res) {
uni.showToast({
title: '取消成功'
})
setTimeout(() => {
refresh()
}, 1000)
}
})
}
}
})
}
watch(() => tabs.sel, (newval) => {
orders.sel = 0
refresh()
})
function showBtns(item) {
if (item.status == '已退款') {
return false
}
return true
}
function showTime(item) {
if (item.status == '已退款') {
return false
}
return true
}
let shareItem = null
function share(item) {
shareItem = item
}
onShareAppMessage(() => {
console.log('onShareAppMessage')
return wxShare({
title: shareItem.wareJson.wareName,
imageUrl: shareItem.goodsImg,
path:'/groupBuying/goodsDetail/goodsDetail',
query: `wareId=${shareItem.wareId}&shopId=${shareItem.shopId}`,
})
})
onLoad(getData)
let timer = null
let nowTime = ref(Date.now())
timer = setInterval(() => {
nowTime.value = Date.now()
}, 1000)
function returnTime(item) {
nowTime.value
return getRemainingHMS(item)
}
</script>
<style lang="scss" scoped>
@@ -188,9 +399,11 @@
gap: 20rpx;
border-radius: 14rpx 14rpx 0 0;
background: linear-gradient(90deg, #FFF5E6 0%, #FFD2CA 100%);
align-items: center;
.step {
display: flex;
align-items: center;
}
.index {
@@ -343,60 +556,74 @@
font-weight: 700;
line-height: 36rpx;
}
.status{
.status {
padding: 8rpx 18rpx;
border-radius: 8rpx;
border: 2rpx solid transparent;
&.success{
&.success {
border-color: rgba(123, 209, 54, 1);
color: rgba(123, 209, 54, 1);
background: rgba(123, 209, 54, 0.12);
}
&.error{
border-color:#FF1C1C;
&.error {
border-color: #FF1C1C;
color: #FF1C1C;
background: rgba(255, 28, 28, 0.18);
}
}
.price{
.price {
color: #ED5A2E;
font-weight: 700;
line-height: 36rpx;
font-size: 40rpx;
font-weight: 700;
line-height: 36rpx;
font-size: 40rpx;
}
.info{
.info {
padding: 16rpx 34rpx;
flex-direction: column;
align-items: flex-start;
border-radius: 8rpx;
background: #F8F8F8;
.title{
.title {
min-width: 208rpx;
padding-right: 8rpx;
box-sizing: border-box;
}
.stitle{
.stitle {
color: #666;
&.price{
font-size: 32rpx;
color: #ed5a2e;
font-weight: 700;
&.price {
font-size: 32rpx;
color: #ed5a2e;
font-weight: 700;
}
}
}
.btns{
.btns {
margin-top: 28rpx;
display: flex;
justify-content: flex-end;
gap: 34rpx;
.btn{
padding: 8rpx 14rpx;
.btn {
line-height: 1;
margin: 0;
padding: 12rpx 14rpx;
border-radius: 10rpx;
border: 2rpx solid #D9D9D9;
background: #FFF;
&.black{
border-color:#343030 ;
font-size: 28rpx;
&.black {
border-color: #343030;
background-color: #343030;
color: #fff;
}
@@ -404,5 +631,4 @@
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,497 @@
<template>
<view class="min-page bg-f7 color-333 u-font-28 relative">
<view class="bottom">
<view class="u-p-32 u-flex u-row-center u-col-center u-flex-col bg-fff">
<image :src="imgs.success" mode="" class="success"></image>
<view class="u-font-36 font-bold u-m-t-16">拼团成功</view>
</view>
<view class="shop ">
<view class="u-flex">
<text style="min-width: 180rpx;" class="color-666">可核销门店</text>
<text>{{item.shopName}}</text>
</view>
<view class="u-m-t-32 u-flex">
<text style="min-width: 180rpx;" class="color-666">门店地址</text>
<text>{{item.shopAddress}}</text>
</view>
</view>
<view class="goods u-flex u-col-center">
<up-image width="158rpx" height="158rpx" radius="14rpx" :src="item.goodsImg"></up-image>
<view class=" u-flex-1 u-p-l-16">
<view class="u-flex u-col-center u-row-between">
<view class="u-flex tuan-members">
<image :src="imgs.pin" class="pin" mode=""></image>
<text class="">{{item.groupPeopleNum}}人团</text>
</view>
</view>
<view class="u-m-t-16 u-flex u-col-center">
<view style="width: 356rpx;">
<view class="font-bold" v-if="item.wareJson">{{item.wareJson
.wareName}}</view>
<view class="u-flex u-m-t-10 u-col-center ">
<view class="price">
<text class="u-font-30">¥</text>
<text class="u-font-48 font-bold">{{item.payAmount}} </text>
</view>
<view class="old-price u-m-l-32">
<text>¥</text>
<text>{{item.payAmount}} </text>
</view>
</view>
</view>
<view class="u-p-l-16 color-333">
数量{{item.num}}
</view>
</view>
</view>
</view>
<view class="shop-box" v-if="false">
<view class="u-flex u-row-center u-flex-col u-col-center">
<up-qrcode :val="item.verifyCode" :size="104"></up-qrcode>
<view class="u-flex u-m-t-22 u-m-b-18 u-col-center">
<text>{{item.verifyCode}}</text>
<image @click="copyText(item.verifyCode)" class="copy" src="/groupBuying/static/image/copy.png">
</image>
</view>
</view>
</view>
<view class="members">
<view class="list">
<view class="item" v-for="(user,index) in item.users" :key="index">
<view class="box">
<view class="u-flex relative">
<up-avatar size="140rpx" :src="user.userAvatar"></up-avatar>
<view class="u-flex u-row-center absolute">
<text class="tuanzhang" v-if="index==0">团长</text>
</view>
</view>
<view class="u-line-1 u-m-t-16 color-000 text-center">{{user.userName}}</view>
</view>
</view>
<view class="item" v-if="false">
<view class="box">
<view class="u-flex relative">
<view class="add-box u-flex u-row-center">
<up-icon color="#D9D9D9" name="plus"></up-icon>
<button open-type="share" class="share" @click="share(item)">分享</button>
</view>
</view>
<view class="u-line-1 u-m-t-16 color-000 text-center">等待参团</view>
</view>
</view>
</view>
</view>
</view>
<view class="order">
<view class="u-flex u-row-between">
<view class="font-bold u-font-32">订单信息</view>
<view class="u-flex color-666" @click="showOrder=!showOrder" style="align-items: baseline;">
<text class="u-m-r-18">{{showOrder?'收起':'展开'}}</text>
<view class="guodu" :class="{rotate:!showOrder}">
<up-icon name="arrow-down" bold></up-icon>
</view>
</view>
</view>
<view class="u-m-t-16" v-if="showOrder">
<view class="u-flex u-row-between item">
<view class="color-666">商品总额</view>
<view class="">¥99.99</view>
</view>
<view class="u-flex u-row-between item">
<view class="color-666">实付金额</view>
<view class="">¥{{item.payAmount}}</view>
</view>
<view class="u-flex u-row-between item">
<view class="color-666">订单号</view>
<view class="">{{item.orderNo}}</view>
</view>
<view class="u-flex u-row-between item">
<view class="color-666">支付时间</view>
<view class="">{{item.payTime}}</view>
</view>
</view>
</view>
<view style="height: 100px;"></view>
<view class="btns">
<view class="btn" @click="uni.navigateBack()">继续拼团</view>
<button open-type="share" class="btn main" @click="toOrderDetail(item)">查看订单</button>
</view>
</view>
</template>
<script setup>
import statusVue from '@/groupBuying/components/status.vue'
import * as Api from '@/common/api/order/gbOrder.js'
import {
wxShare
} from '@/utils/share.js'
import {
computed,
reactive
} from 'vue'
import {
getRemainingHMS
} from '@/utils/countdown.js'
const imgs = {
bg: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/d21f2dfd7bec44618f2d5e4b88372b08.png',
pin: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/2/3947892924dd481782331513aff00eb3.png',
success: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/3/6a75acd0c0db4bfe9937854b2b2df3f0.png'
}
const topStyle = {
backgroundImage: 'url(' + imgs.bg + ')'
}
const showOrder = ref(true)
function copyText(text) {
uni.setClipboardData({
data: text,
success() {}
})
}
const query = reactive({
shopId: '',
detailId: '',
})
function init(opt) {
console.log(opt)
Object.assign(query, opt)
getDetail()
}
const item = reactive({})
function getDetail() {
Api.recordDetail(query).then(res => {
const wareJson = JSON.parse(res.wareJson)
wareJson.wareImgs = wareJson.wareImgs.split(',').filter(v => v)
res.wareJson = wareJson;
res.goodsImg = wareJson.wareImgs[0];
Object.assign(item, res)
console.log('item', item)
})
}
onLoad(init)
let timer = null
let nowTime = ref(Date.now())
timer = setInterval(() => {
nowTime.value = Date.now()
}, 1000)
const returnTime = computed(() => {
nowTime.value
return getRemainingHMS(item)
})
function returnNum(index) {
return returnTime.value.split(':')[index]
}
function refund(item) {
uni.showModal({
title: '提示',
content: '是否申请退款?',
showCancel: true,
success(res) {
if (res.confirm) {
Api.applyRefund({
recordId: item.id,
orderNo: item.orderNo,
}).then(res => {
if (res) {
uni.showToast({
title: '申请成功'
})
setTimeout(() => {
getDetail()
}, 1000)
}
})
}
}
})
}
onShareAppMessage(() => {
console.log('onShareAppMessage')
return wxShare({
title: shareItem.wareJson.wareName,
imageUrl: shareItem.goodsImg,
query: `detailId=${shareItem.id}&shopId=${shareItem.shopId}`,
})
})
function toOrderDetail(item) {
uni.navigateTo({
url: '/groupBuying/detail/index?detailId=' + item.id + '&shopId=' + item.shopId
})
}
</script>
<style lang="scss" scoped>
.top {
height: 422rpx;
background-size: cover;
.info {
padding: 32rpx 30rpx 0 30rpx;
color: #fff;
font-size: 32rpx;
font-weight: 700;
}
}
.bottom {
background-color: #fff;
padding: 0 28rpx;
.time {
padding: 18rpx 24rpx;
background: #FFF4E2;
display: flex;
align-items: center;
border-radius: 16rpx 16rpx 0 0;
.number {
padding: 6rpx;
border-radius: 16rpx;
background: #ED5A2E;
color: #fff;
font-size: 32rpx;
margin-left: 18rpx;
margin-right: 18rpx;
}
.gap {
font-size: 32rpx;
}
}
.goods {
background-color: #fff;
padding: 22rpx 24rpx 0;
border-radius: 0 0 16rpx 16rpx;
margin-top: 32rpx;
.pin {
width: 60rpx;
height: 38rpx;
}
.tuan-members {
padding: 0 16rpx 0 0;
border-radius: 0 6rpx 6rpx 0;
background: #4C2828;
color: #f5d9ad;
font-weight: 700;
}
.price {
color: #ed5a2e;
}
.old-price {
color: #666666;
text-decoration-line: line-through;
}
}
.u-font-48 {
font-size: 48rpx;
}
.shop-box {
background: #fff;
padding: 32rpx 30rpx 28rpx 30rpx;
border-radius: 0 0 16rpx 16rpx;
}
.copy {
width: 22rpx;
height: 22rpx;
margin-left: 22rpx;
}
.shop {
border-radius: 8rpx;
background: #F8F8F8;
padding: 16rpx 34rpx;
}
.relative {
position: relative;
}
.members {
padding: 26rpx 24rpx;
border-radius: 16rpx;
margin-top: 32rpx;
background-color: #fff;
.list {
display: flex;
}
.item {
width: calc(100% / 3);
display: flex;
.add-box {
width: 140rpx;
height: 140rpx;
border: 2rpx dashed #D9D9D9;
border-radius: 50%;
overflow: hidden;
position: relative;
.share {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
opacity: 0;
}
}
&:nth-of-type(2n) {
justify-content: center;
}
&:nth-of-type(3n) {
justify-content: flex-end;
}
.box {
display: flex;
flex-direction: column;
}
.absolute {
bottom: 0;
display: flex;
justify-content: center;
left: 0;
right: 0;
}
.tuanzhang {
padding: 8rpx 18rpx;
border-radius: 20rpx;
background: #ED5A2E;
color: #fff;
bottom: 0;
}
}
}
}
.btns {
position: fixed;
left: 87rpx;
right: 87rpx;
bottom: 0;
padding-bottom: 60rpx;
display: flex;
gap: 40rpx;
.btn {
flex: 1;
padding: 16rpx 28rpx;
border-radius: 200rpx;
text-align: center;
font-size: 32rpx;
line-height: 1;
margin: 0;
justify-content: center;
border: 2rpx solid #E8AD7B;
background: #FFF;
color: #E8AD7B;
&.main {
background-color: #E8AD7B;
color: #fff;
}
}
}
.guodu {
transition: all .3s;
}
.pin-btn {
padding: 14rpx 60rpx;
border-radius: 200rpx;
text-align: center;
font-size: 32rpx;
justify-content: center;
border: 2rpx solid #E8AD7B;
background: #E8AD7B;
color: #fff;
margin-top: 32rpx;
}
.rotate {
transform: rotate(-90deg);
}
.order {
padding: 32rpx 28rpx;
border-radius: 16rpx;
margin: 14rpx 30rpx 30rpx 30rpx;
background-color: #fff;
.item {
padding: 16rpx 0;
}
}
.refund {
padding: 18rpx 164rpx;
border-radius: 0 0 16rpx 16rpx;
background: #FFF4E2;
font-weight: 700;
margin-bottom: 32rpx;
}
.success {
width: 102rpx;
height: 102rpx;
}
.bg-fff {
background-color: #fff;
}
</style>

View File

@@ -376,6 +376,18 @@
"navigationBarTitleText": "订单详情",
"navigationStyle": "custom"
}
},
{
"path": "goodsDetail/goodsDetail",
"style": {
"navigationBarTitleText": "拼团特惠"
}
},
{
"path": "success/index",
"style": {
"navigationBarTitleText": "支付成功"
}
}
]

View File

@@ -50,7 +50,7 @@
<image :src="imgs.big_orderFood" class="big_img"></image>
</view>
<view class="groupBuying" v-if="allConfig.group">
<view class="groupBuying" v-if="allConfig.group" @click="toGroupBuying">
<view class="u-flex u-col-center">
<image class="img" :src="imgs.groupBuying"></image>
<view class="u-font-32 color-333 font-700 u-m-l-12">快乐拼单</view>
@@ -139,6 +139,12 @@ const props = defineProps({
}
});
function toGroupBuying(){
uni.navigateTo({
url:'/groupBuying/index/index'
})
}
function toFenxiao() {
uni.navigateTo({
url: '/distribution/shop-detail/index?shopId=' + uni.cache.get('shopId')

View File

@@ -2438,9 +2438,12 @@
})
const oldOrder = ref(null);
onMounted(async () => {
// #ifdef MP-WEIXIN
await proxy.$onLaunched;
// #endif
// 获取当前页面栈
const pages = getCurrentPages();
// 获取当前页面实例
const currentPage = pages[pages.length - 1];
// 获取页面参数

View File

@@ -38,6 +38,7 @@ declare global {
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onShareAppMessage: typeof import('@dcloudio/uni-app')['onShareAppMessage']
const onShow: typeof import('@dcloudio/uni-app')['onShow']
const onUnload: typeof import('@dcloudio/uni-app')['onUnload']
const onUnmounted: typeof import('vue')['onUnmounted']

View File

@@ -179,6 +179,13 @@ export const productStore = defineStore("product", {
*/
async scanCodeactions(q) {
console.log("扫码内容", q);
// #ifdef H5
uni.navigateTo({
url:'/pages/product/index'
})
return
// #endif
return new Promise(async (resolve, reject) => {
if (q) {
console.log(q);
@@ -218,7 +225,7 @@ export const productStore = defineStore("product", {
}
}
} else {
// #ifdef APP || MP-WEIXIN || MP-ALIPAY
// #ifdef APP || MP-WEIXIN || MP-ALIPAY
uni.scanCode({
success: async (res) => {
let tableCode = this.getQueryString(

51
utils/countdown.js Normal file
View File

@@ -0,0 +1,51 @@
import dayjs from "dayjs"
/**
* 计算剩余时间差(毫秒)
* @param {Object} item - 包含groupEndTime的订单/拼团对象
* @returns {number} 剩余时间(毫秒)
*/
function returnRemainingTime(item) {
if (!item?.groupEndTime) return 0; // 容错无结束时间则返回0
return dayjs(item.groupEndTime).valueOf() - dayjs().valueOf();
}
/**
* 将毫秒差格式化为 HH:MM:SS最多72小时
* @param {number} ms - 时间差(毫秒)
* @returns {string} 格式化后的时分秒(如 09:09:09、72:00:00、00:00:00
*/
function formatTimeToHMS(ms) {
// 边界1已过期/无剩余时间 → 显示00:00:00
if (ms <= 0) return '00:00:00';
// 边界2超过72小时 → 按72小时算72*60*60*1000 = 259200000毫秒
const maxMs = 72 * 60 * 60 * 1000;
const validMs = Math.min(ms, maxMs);
// 转换为总秒数(取整,避免小数)
const totalSeconds = Math.floor(validMs / 1000);
// 拆解小时、分钟、秒
const hours = Math.floor(totalSeconds / 3600);
const remainingSeconds = totalSeconds % 3600;
const minutes = Math.floor(remainingSeconds / 60);
const seconds = remainingSeconds % 60;
// 补零(确保两位数,如 9 → 09
const pad = (num) => String(num).padStart(2, '0');
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
}
let timer = null
let nowTime = ref(Date.now())
timer = setInterval(() => {
nowTime.value = Date.now()
}, 1000)
// 组合使用:获取格式化后的剩余时间
export function getRemainingHMS(item) {
const ms = returnRemainingTime(item);
return formatTimeToHMS(ms);
}

6
utils/share.js Normal file
View File

@@ -0,0 +1,6 @@
export function wxShare(par) {
return {
...par,
type: 2
}
}

View File

@@ -14,6 +14,7 @@ module.exports = defineConfig({
"vue",
{
"@dcloudio/uni-app": [
"onShareAppMessage",
"onLoad",
"onShow",
"onHide",