Files
cashier_wx/groupBuying/goodsDetail/goodsDetail.vue
2025-12-18 11:06:54 +08:00

644 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>
<template v-if="item.gbOrderList.length">
<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>
</template>
<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 dingyue() {
return new Promise((revlove, reject) => {
uni.requestSubscribeMessage({
tmplIds: ['JGPAGmqcPEgWB6mvAl0SC5cMqr5H5Qjcim8JCpHAZd0',
'F4OyUhe_ZQ9BR731jlkaN2QXAUaA3HBZuUeVPfraSz0'
],
success(res) {},
complete() {
revlove()
}
})
})
}
async function payExchange() {
await dingyue();
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'
})
}
}
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: '',
groupOrderNo: ''
})
const coverImgs = ref([])
function init(opt) {
// 获取小程序进入场景和参数
const launchOptions = uni.getLaunchOptionsSync();
console.log(launchOptions);
// 获取链接上的参数
const launchOptionsQuery = launchOptions.query;
console.log('launchOptionsQuery', launchOptionsQuery);
Object.assign(query, launchOptionsQuery)
console.log(opt)
Object.assign(query, opt)
console.log(query)
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>