新增营销系统、分销系统、会员功能、门店、提现功能
This commit is contained in:
250
views/activity/afterVerification/index.vue
Normal file
250
views/activity/afterVerification/index.vue
Normal file
@ -0,0 +1,250 @@
|
||||
<!--
|
||||
@name: index
|
||||
@author: kahu4
|
||||
@date: 2024-01-21 15:38
|
||||
@description:订单核销
|
||||
@update: 2024-01-21 15:38
|
||||
-->
|
||||
<script setup>
|
||||
import Header from "@/components/Header/index.vue"
|
||||
import { useScroll } from "@/hooks/useScroll";
|
||||
import { afterVerificationQrScan } from "@/utils/images";
|
||||
import { ref } from 'vue'
|
||||
import { cancelAfterVerification } from "@/api/activity/afterVerification";
|
||||
import { useInterface } from "@/hooks/useInterface";
|
||||
import Modal from "@/components/Modal/index.vue";
|
||||
import { useQrCodeScan } from "@/hooks/useQrCodeScan";
|
||||
|
||||
// ========================= hooks ==========================
|
||||
const {toast, loading, hideLoading} = useInterface();
|
||||
const {scrollTop} = useScroll();
|
||||
const {qrCodeScan, cancelScan} = useQrCodeScan()
|
||||
|
||||
// ========================= 核销 ==========================
|
||||
const checkOffCode = ref('') // 核销码
|
||||
|
||||
/**
|
||||
* 核销
|
||||
*/
|
||||
async function doCancelAfterVerification() {
|
||||
if (!checkOffCode.value) return toast({title: '核销码有误'})
|
||||
try {
|
||||
loading()
|
||||
await cancelAfterVerification({writeOffCode: checkOffCode.value})
|
||||
checkOffCode.value = ''
|
||||
hideLoading()
|
||||
toast({title: '核销成功'})
|
||||
} catch (e) {
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
const modalRef = ref()
|
||||
|
||||
function openModal() {
|
||||
if (checkOffCode.value.length <= 0) return
|
||||
modalRef.value.show()
|
||||
}
|
||||
|
||||
function cancelModal() {
|
||||
checkOffCode.value = ''
|
||||
}
|
||||
|
||||
// ====================== 扫码相关 ==============================
|
||||
const showH5QrScan = ref(false)
|
||||
|
||||
async function doQrScan() {
|
||||
try {
|
||||
showH5QrScan.value = true
|
||||
uni.showLoading({
|
||||
title: '加载中...'
|
||||
})
|
||||
checkOffCode.value = await qrCodeScan();
|
||||
openModal()
|
||||
} finally {
|
||||
showH5QrScan.value = false
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
function h5CancelQrScan() {
|
||||
cancelScan()
|
||||
showH5QrScan.value = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view>
|
||||
<Header
|
||||
:scroll-top="scrollTop"
|
||||
system-bar-area-bg="#fff"
|
||||
header-area-bg="#fff"> 订单核销
|
||||
</Header>
|
||||
<view class="verification">
|
||||
<view class="ver-card">
|
||||
<view class="title">核销券码</view>
|
||||
<view class="input row">
|
||||
<input
|
||||
v-model="checkOffCode"
|
||||
type="text"
|
||||
placeholder="请输入核销券码" />
|
||||
</view>
|
||||
<view
|
||||
class="btn-row row animation-button"
|
||||
:class="{disabled:checkOffCode.length<=0}"
|
||||
@click="openModal">
|
||||
确认核销
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="ver-card">
|
||||
<view class="title">二维码核销</view>
|
||||
<view class="qr-scan row">
|
||||
<image
|
||||
:src="afterVerificationQrScan"
|
||||
@click="doQrScan" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<Modal
|
||||
ref="modalRef"
|
||||
content="确认要核销此订单吗?"
|
||||
@confirm="doCancelAfterVerification"
|
||||
@cancel="cancelModal" />
|
||||
<!-- #ifdef H5 -->
|
||||
<!-- h5 qr-scan UI -->
|
||||
<div
|
||||
class="qr-h5"
|
||||
:style="{
|
||||
scale:showH5QrScan?1:0
|
||||
}">
|
||||
<div
|
||||
id="reader">
|
||||
</div>
|
||||
<div
|
||||
class="cancel"
|
||||
@click="h5CancelQrScan">
|
||||
<u-icon
|
||||
name="close"
|
||||
color="#fff"
|
||||
size="28" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss">
|
||||
.qr-h5 {
|
||||
scale: 0;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: #fff;
|
||||
transition: all .3s;
|
||||
|
||||
.cancel {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
background: red;
|
||||
box-shadow: 0 0 20rpx rgba(236, 236, 236, 0.47);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
z-index: 99;
|
||||
right: 50%;
|
||||
bottom: 10px;
|
||||
transform-origin: center center;
|
||||
transform: translateX(50%) translateY(-100%);
|
||||
transition: all .3s;
|
||||
|
||||
&:active {
|
||||
scale: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.verification {
|
||||
@include usePadding(34, 34);
|
||||
|
||||
.ver-card {
|
||||
@include usePadding(32, 32);
|
||||
margin-bottom: 24rpx;
|
||||
border-radius: 15rpx;
|
||||
background: #fff;
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
font-size: 38rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.btn-row {
|
||||
@include useFlex(center, center);
|
||||
height: 80rpx;
|
||||
border-radius: 15rpx;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
background: #f3997d;
|
||||
|
||||
&:active {
|
||||
scale: 1;
|
||||
animation: disabledAnimation 200ms 15;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
@include usePadding(21, 24);
|
||||
width: 100%;
|
||||
background: #F6F8F8;
|
||||
border-radius: 8rpx;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.qr-scan {
|
||||
@include useFlex(center, center);
|
||||
margin-top: 40rpx !important;
|
||||
|
||||
image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
transition: all .3s;
|
||||
|
||||
&:active {
|
||||
scale: 1.1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes disabledAnimation {
|
||||
0%, 90% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
30% {
|
||||
transform: translateX(-20rpx);
|
||||
}
|
||||
60% {
|
||||
transform: translateX(20rpx);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
70
views/activity/groupBy/component/GoodsItemOptions.vue
Normal file
70
views/activity/groupBy/component/GoodsItemOptions.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<!--
|
||||
@name: GoodsItemOptions
|
||||
@author: kahu4
|
||||
@date: 2024-01-16 11:57
|
||||
@description:拼团商品底部按钮
|
||||
@update: 2024-01-16 11:57
|
||||
-->
|
||||
<script setup>
|
||||
import { toRefs } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
goods: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const {goods} = toRefs(props)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="options flex flex-jc__sb flex-ai__end">
|
||||
<view class="left-info">
|
||||
<view class="row">
|
||||
3人团
|
||||
</view>
|
||||
<view class="price-row flex flex-ai__end">
|
||||
¥329
|
||||
<view class="old-price">
|
||||
¥666
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="button animation-button" @click.stop="">
|
||||
立即拼团
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss"
|
||||
scoped>
|
||||
.options {
|
||||
width: 100%;
|
||||
|
||||
|
||||
.left-info{
|
||||
color:$tips-color;
|
||||
font-size: 24rpx;
|
||||
.price-row{
|
||||
margin-top: 5rpx;
|
||||
color: $primary-color;
|
||||
font-size: 30rpx;
|
||||
.old-price{
|
||||
text-decoration: line-through;
|
||||
color:$tips-color;
|
||||
font-size: 20rpx;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.button{
|
||||
min-width: 45%;
|
||||
background: $primary-color;
|
||||
color: #fff;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
608
views/activity/groupBy/detail.vue
Normal file
608
views/activity/groupBy/detail.vue
Normal file
@ -0,0 +1,608 @@
|
||||
<!--
|
||||
@name: detail
|
||||
@author: kahu4
|
||||
@date: 2024-01-16 16:35
|
||||
@description:拼团详情
|
||||
@update: 2024-01-16 16:35
|
||||
-->
|
||||
<script setup>
|
||||
import Header from "@/components/Header/index.vue";
|
||||
import { useScroll } from "@/hooks/useScroll";
|
||||
import Goods from "@/components/goodsComponents/Goods.vue";
|
||||
import { onLoad, onShareAppMessage, onShareTimeline } from "@dcloudio/uni-app";
|
||||
import { computed, onBeforeUnmount, ref } from "vue";
|
||||
import InviteFriends from "@/components/Share/InviteFriends.vue";
|
||||
import GroupByPoster from "@/components/Poster/GroupBy.vue";
|
||||
import { SharePathKey, useShare } from "@/hooks/useShare";
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
import { getGroupByDetailTeamworkId } from "@/api/goods";
|
||||
import { getTimeAfterNow } from "@/utils/utils";
|
||||
import { useMainStore } from "@/store/store";
|
||||
import GoodAttrSelect from "@/components/good-attr-select/good-attr-select.vue";
|
||||
import { getProductDetail } from "@/api/product";
|
||||
import { useInterface } from "@/hooks/useInterface";
|
||||
import { getCartAdd } from "@/api/cart";
|
||||
|
||||
const {getParams, push} = useRouter();
|
||||
const {scrollTop} = useScroll()
|
||||
const mainStore = useMainStore();
|
||||
|
||||
const teamworkId = ref()
|
||||
const {toast} = useInterface();
|
||||
|
||||
onLoad((option) => {
|
||||
const params = getParams(option);
|
||||
// 邀请进来的
|
||||
if (params.t && params.t === SharePathKey.GROUP_BY) {
|
||||
teamworkId.value = params.id
|
||||
} else {
|
||||
teamworkId.value = params.teamworkId
|
||||
}
|
||||
doGetGroupByDetailTeamworkId()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopTimePlay()
|
||||
})
|
||||
|
||||
// 拼团详情
|
||||
const groupDetail = ref()
|
||||
// 拼团状态 0 进行中 1 成功 2 失败
|
||||
const groupState = ref(0)
|
||||
// 拼团title
|
||||
const groupTitle = computed(() => {
|
||||
if (!groupDetail.value) return
|
||||
if (groupState.value === 0) {
|
||||
return `再邀${ residuePersonNum.value }位即可拼团成功`
|
||||
}
|
||||
if (groupState.value === 1) {
|
||||
return '拼团成功,请等待商家发货'
|
||||
}
|
||||
if (groupState.value === 2) {
|
||||
return '拼团失败,可以再次开团啊~'
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取拼团详情
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async function doGetGroupByDetailTeamworkId() {
|
||||
const res = await getGroupByDetailTeamworkId({id: teamworkId.value});
|
||||
groupDetail.value = res
|
||||
groupDetail.value.teamworkId = teamworkId.value
|
||||
// 校验状态 state 0 进行中 1 成功 2 失败
|
||||
groupState.value = res.state
|
||||
await doGetGoodsDetail()
|
||||
if (res.state > 0) return
|
||||
// 设置倒计时
|
||||
setTimePlay(res.closeTime)
|
||||
}
|
||||
|
||||
// ========================= sku 商品相关 ===========================================
|
||||
const selectAttrPanel = ref()
|
||||
const canDoOrder = ref(true) // 是否能立即下单
|
||||
const goodsDetail = ref() // 商品详情
|
||||
const selectSku = ref() // 选中的sku
|
||||
const skuNum = ref(1) // 选中sku下单数量
|
||||
/**
|
||||
* 获取商品详情
|
||||
* 请求回来给sku选择器使用
|
||||
*/
|
||||
async function doGetGoodsDetail() {
|
||||
goodsDetail.value = await getProductDetail({productId: groupDetail.value.id, skuId: groupDetail.value.skuId});
|
||||
canDoOrder.value = true
|
||||
groupByInvitationShare({...groupDetail.value,cartInfo:[{productInfo:{image:groupDetail.value.image}}]});
|
||||
const {productValue} = goodsDetail.value
|
||||
for (const skuName in productValue) {
|
||||
const sku = productValue[skuName]
|
||||
if (sku.id !== groupDetail.value.skuId) continue;
|
||||
selectSku.value = sku
|
||||
break;
|
||||
}
|
||||
// 没有sku
|
||||
if (!selectSku.value) {
|
||||
toast({title: '此规格下架了~看看其他商品吧~'})
|
||||
canDoOrder.value = false
|
||||
return
|
||||
}
|
||||
// sku没有库存
|
||||
if (!selectSku.value.campaignStock || selectSku.value <= 0) {
|
||||
toast({title: '此规格库存不足~看看其他商品吧~'})
|
||||
canDoOrder.value = false
|
||||
return
|
||||
}
|
||||
canDoOrder.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* sku选择器confirm
|
||||
* @param attr
|
||||
*/
|
||||
function handleSelectAttr(attr) {
|
||||
const {store, num} = attr
|
||||
// 与拼团商品sku不一致
|
||||
if (store.id !== groupDetail.value.skuId) {
|
||||
toast({title: '检测到您选择的规格和好友下单拼团规格不一致~请重新选择', time: 3000})
|
||||
return
|
||||
}
|
||||
// 下单逻辑
|
||||
selectSku.value = store
|
||||
skuNum.value = num
|
||||
handleBuy()
|
||||
}
|
||||
|
||||
/**
|
||||
* 下单
|
||||
* @param orderType 1、普通下单,2、商品活动下单
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
const handleBuy = async (orderType = 2) => {
|
||||
uni.showLoading({
|
||||
title: "加载中",
|
||||
});
|
||||
let res = await getCartAdd({
|
||||
orderType,
|
||||
cartNum: skuNum.value,
|
||||
productId: selectSku.value.productId,
|
||||
uniqueId: selectSku.value.unique,
|
||||
new: 1,
|
||||
teamworkId: groupDetail.value.teamworkId,
|
||||
});
|
||||
uni.hideLoading();
|
||||
const data = handleSubmitParams(orderType, res)
|
||||
push(
|
||||
{url: "/pages/submitOrder/submitOrder"},
|
||||
{data}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理跳转参数
|
||||
* @param orderType
|
||||
* @param cardRes 下单购物车返回信息
|
||||
* @return {{orderType, campaignDetailId: any, campaignType: any, cartId, teamworkType: number}|{orderType, cartId}}
|
||||
*/
|
||||
const handleSubmitParams = (orderType, cardRes) => {
|
||||
let data = {
|
||||
cartId: cardRes.cartId,
|
||||
orderType
|
||||
}
|
||||
// 活动
|
||||
if (orderType === 2) {
|
||||
data.campaignType = selectSku.value.campaignType
|
||||
data.campaignDetailId = selectSku.value.campaignDetailId
|
||||
data.teamworkType = 2 // 1开团 2拼团
|
||||
data.teamworkId = groupDetail.value.teamworkId
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
// ========================= 倒计时相关 ===========================================
|
||||
let timeObj = ref({}), time
|
||||
const timestamp = ref(0)
|
||||
|
||||
/**
|
||||
* 开始倒计时
|
||||
* @param closeTime
|
||||
*/
|
||||
function setTimePlay(closeTime) {
|
||||
const nowTime = Date.now()
|
||||
timestamp.value = closeTime
|
||||
if (timestamp.value === 0 || closeTime - nowTime <= 0) return
|
||||
const setTime = () => {
|
||||
timeObj.value = getTimeAfterNow(timestamp.value)
|
||||
if (closeTime - nowTime <= 0) {
|
||||
// 倒计时结束
|
||||
stopTimePlay()
|
||||
timeOver()
|
||||
}
|
||||
}
|
||||
setTime()
|
||||
time = setInterval(() => setTime(), 1000)
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止倒计时
|
||||
*/
|
||||
function stopTimePlay() {
|
||||
time && clearInterval(time)
|
||||
time = undefined
|
||||
timestamp.value = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 倒计时结束
|
||||
*/
|
||||
function timeOver() {
|
||||
doGetGroupByDetailTeamworkId()
|
||||
}
|
||||
|
||||
/**
|
||||
* 还差多少人成团
|
||||
* @type {ComputedRef<number|*>}
|
||||
*/
|
||||
const residuePersonNum = computed(() => {
|
||||
if (!groupDetail.value) return 1
|
||||
return groupDetail.value.person - groupDetail.value.users.length
|
||||
})
|
||||
|
||||
/**
|
||||
* 当前用户是否已经购买
|
||||
* @type {ComputedRef<unknown>}
|
||||
*/
|
||||
const isBuy = computed(() => {
|
||||
if (!groupDetail.value) return false
|
||||
return groupDetail.value.users.findIndex(item => item.uid === mainStore.user.id) > -1
|
||||
})
|
||||
|
||||
const groupRules = [
|
||||
{
|
||||
index: 1,
|
||||
info: '开团或成团享团购价'
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
info: '邀请好友参与优惠多'
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
info: '人满发货不满退款'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
/**
|
||||
* 立即拼团
|
||||
*/
|
||||
function takePartInAGroup() {
|
||||
if (!canDoOrder.value) return toast({title: "此规格不能下单,可以看看其他上哦~"})
|
||||
selectAttrPanel.value.open()
|
||||
}
|
||||
|
||||
|
||||
// ============================ 邀请 ==================================================
|
||||
const {shareInfo,groupByInvitationShare, shareH5, shareAppMessage, shareTimeline} = useShare()
|
||||
onShareAppMessage(shareAppMessage)
|
||||
onShareTimeline(shareTimeline)
|
||||
|
||||
const groupByPosterRef = ref()
|
||||
const inviteFriendShareRef = ref()
|
||||
|
||||
function inviteFriend() {
|
||||
if (groupState.value === 2) return
|
||||
inviteFriendShareRef.value.open()
|
||||
}
|
||||
|
||||
function againGroup() {
|
||||
push({
|
||||
url: '/pages/goodsDetail/goodsDetail',
|
||||
}, {
|
||||
data: {id: groupDetail.value.id, "skuId": groupDetail.value.skuId},
|
||||
type: 'redirectTo'
|
||||
})
|
||||
}
|
||||
|
||||
function handleShareConfirm(shareItem) {
|
||||
if (shareItem.value === 'wechat') {
|
||||
shareH5()
|
||||
} else {
|
||||
groupByPosterRef.value.open(groupDetail.value, shareInfo.value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
class="group-by-detail"
|
||||
>
|
||||
<Header :scroll-top="scrollTop"> 拼团详情</Header>
|
||||
<view
|
||||
class="main"
|
||||
v-if="groupDetail">
|
||||
<!-- 商品信息 -->
|
||||
<view
|
||||
class="goods-row row">
|
||||
<Goods
|
||||
:goods="groupDetail"
|
||||
:ratio="true"
|
||||
imgWidth="200rpx"
|
||||
infoPadding="20rpx 20rpx"
|
||||
row
|
||||
>
|
||||
<template #options="{goods}">
|
||||
<span class="price">
|
||||
¥{{ goods.price }}
|
||||
</span>
|
||||
</template>
|
||||
</Goods>
|
||||
</view>
|
||||
|
||||
<!-- 拼团信息 -->
|
||||
<view class="row group-info">
|
||||
<view class="title">
|
||||
{{ groupTitle }}
|
||||
</view>
|
||||
<view
|
||||
class="time"
|
||||
v-if="timeObj && groupState===0">
|
||||
剩余
|
||||
<view class="time-group">
|
||||
<text class="time-item">{{ timeObj.hours }}</text>
|
||||
<text class="primary-color">:</text>
|
||||
<text class="time-item">{{ timeObj.minutes }}</text>
|
||||
<text class="primary-color">:</text>
|
||||
<text class="time-item">{{ timeObj.seconds }}</text>
|
||||
</view>
|
||||
结束
|
||||
</view>
|
||||
<view class="users">
|
||||
<view
|
||||
v-for="user in groupDetail.users"
|
||||
:key="user.id"
|
||||
class="user-item">
|
||||
<image :src="user.avatar" />
|
||||
<view
|
||||
class="first-group"
|
||||
v-if="user.isHead === '1'">
|
||||
团长
|
||||
</view>
|
||||
</view>
|
||||
<!-- 该团还需要几个人 -->
|
||||
<view
|
||||
class="user-item plus"
|
||||
v-for="item in residuePersonNum"
|
||||
:key="item"
|
||||
@click="inviteFriend">
|
||||
<u-icon name="plus" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 如果自己已经在团内 -->
|
||||
<template v-if="isBuy">
|
||||
<view
|
||||
v-if="groupState === 0"
|
||||
class="btn animation-button"
|
||||
@click="inviteFriend">
|
||||
邀请好友拼团
|
||||
</view>
|
||||
<view
|
||||
v-if="[1,2].includes(groupState)"
|
||||
class="btn animation-button"
|
||||
@click="againGroup">
|
||||
再次拼团
|
||||
</view>
|
||||
</template>
|
||||
<!-- 如果自己不在团内 -->
|
||||
<template v-else>
|
||||
<view
|
||||
v-if="[1,2].includes(groupState)"
|
||||
class="btn animation-button"
|
||||
@click="againGroup">
|
||||
再次拼团
|
||||
</view>
|
||||
<view
|
||||
v-else
|
||||
class="btn animation-button"
|
||||
@click="takePartInAGroup">
|
||||
立即参团
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<!-- 拼团玩法 -->
|
||||
<view class="group-rule group-info row">
|
||||
<view class="title">
|
||||
拼团玩法
|
||||
</view>
|
||||
<view class="rule-box">
|
||||
<view
|
||||
v-for="item in groupRules"
|
||||
:key="item.index"
|
||||
class="rule-item">
|
||||
<view class="circle-number">
|
||||
{{ item.index }}
|
||||
</view>
|
||||
<view class="info">
|
||||
{{ item.info }}
|
||||
</view>
|
||||
<view class="line"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- SKU选择器 -->
|
||||
<GoodAttrSelect
|
||||
v-if="goodsDetail && selectSku"
|
||||
ref="selectAttrPanel"
|
||||
:goods-detail="goodsDetail"
|
||||
:sku-id="selectSku.id"
|
||||
@select="handleSelectAttr" />
|
||||
|
||||
<!-- 邀请好友 -->
|
||||
<InviteFriends
|
||||
ref="inviteFriendShareRef"
|
||||
@share="handleShareConfirm" />
|
||||
<GroupByPoster ref="groupByPosterRef" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
background: $page-bg-color;
|
||||
}
|
||||
</style>
|
||||
<style
|
||||
lang="scss"
|
||||
scoped>
|
||||
.group-by-detail {
|
||||
width: 100%;
|
||||
|
||||
.main {
|
||||
width: 100%;
|
||||
@include usePadding(30, 30);
|
||||
|
||||
.row {
|
||||
width: 100%;
|
||||
background: $white-color;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 25rpx;
|
||||
}
|
||||
|
||||
.price {
|
||||
color: $primary-color;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.group-info {
|
||||
@include usePadding(30, 30);
|
||||
width: 100%;
|
||||
|
||||
.title, .time {
|
||||
@include useFlex(center, center)
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.time {
|
||||
margin-top: 15rpx;
|
||||
font-size: 24rpx;
|
||||
@include useFlex(center, center);
|
||||
|
||||
.time-group {
|
||||
margin: 0 15rpx;
|
||||
@include useFlex(center, center);
|
||||
|
||||
.time-item {
|
||||
@include usePadding(10, 6);
|
||||
background: #FDEFEA;
|
||||
border: 1rpx solid #E85A2B;
|
||||
color: #E85A2B;
|
||||
border-radius: 5rpx;
|
||||
}
|
||||
|
||||
.primary-color {
|
||||
color: #E85A2B;
|
||||
padding: 0 5rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.users {
|
||||
margin: 40rpx 0;
|
||||
@include useFlex(center, center, row, wrap, 20rpx);
|
||||
width: 100%;
|
||||
|
||||
.user-item {
|
||||
width: 130rpx;
|
||||
height: 130rpx;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 10rpx rgba(220, 220, 220, 0.7);
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background: #e5e5e5;
|
||||
}
|
||||
|
||||
.first-group {
|
||||
@include usePadding(16, 5);
|
||||
background: $primary-color;
|
||||
color: $white-color;
|
||||
white-space: nowrap;
|
||||
border-radius: 34rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plus {
|
||||
transition: all .3s;
|
||||
|
||||
&:active {
|
||||
scale: 1.1;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-top: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 80rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.group-rule {
|
||||
width: 100%;
|
||||
|
||||
.rule-box {
|
||||
@include usePadding(0, 30);
|
||||
@include useFlex(space-between, center);
|
||||
font-size: 20rpx;
|
||||
color: $tips-color;
|
||||
|
||||
.rule-item {
|
||||
position: relative;
|
||||
@include useFlex(center, center, column);
|
||||
|
||||
.circle-number {
|
||||
@include useFlex(center, center);
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
color: #000;
|
||||
border-radius: 50%;
|
||||
background: #f5f5f5;
|
||||
margin: 16rpx 0;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 5rpx;
|
||||
background: #f5f5f5;
|
||||
top: 35%;
|
||||
left: 65%;
|
||||
}
|
||||
|
||||
&:last-child:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
background: #f5f5f5;
|
||||
top: 35%;
|
||||
left: 60%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.success-color {
|
||||
color: #E85A2B;
|
||||
}
|
||||
</style>
|
229
views/activity/groupBy/index.vue
Normal file
229
views/activity/groupBy/index.vue
Normal file
@ -0,0 +1,229 @@
|
||||
<!--
|
||||
@name: index
|
||||
@author: kahu4
|
||||
@date: 2024-01-16 11:19
|
||||
@description:index
|
||||
@update: 2024-01-16 11:19
|
||||
-->
|
||||
<script setup>
|
||||
import { emptyCollectIcon, groupByBg } from "@/utils/images";
|
||||
import Header from "@/components/Header/index.vue";
|
||||
import { useScroll } from "@/hooks/useScroll";
|
||||
import ListLoadLoading from "@/components/ListLoadLoading/index.vue";
|
||||
import ListLoadOver from "@/components/ListLoadOver/index.vue";
|
||||
import Empty from "@/components/Empty/index.vue";
|
||||
import Goods from "@/components/goodsComponents/Goods.vue";
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { usePage } from "@/hooks";
|
||||
import { getProductList } from "@/api/product";
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
import { computed, toRefs } from "vue";
|
||||
import GoodsItemOptions from "@/views/activity/groupBy/component/GoodsItemOptions.vue";
|
||||
|
||||
const {scrollTop} = useScroll()
|
||||
|
||||
const {refresh, dataList, loadend, loading, listEmpty} = usePage(getProductList)
|
||||
const {push} = useRouter()
|
||||
onLoad(() => {
|
||||
refresh()
|
||||
})
|
||||
const props = defineProps({
|
||||
more: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
}
|
||||
})
|
||||
const {more} = toRefs(props)
|
||||
|
||||
|
||||
const colList = computed(() => [
|
||||
{
|
||||
name: 'all',
|
||||
data: dataList.value.filter((item, index) => index % 2 === 0)
|
||||
},
|
||||
{
|
||||
name: 'right',
|
||||
data: dataList.value.filter((item, index) => index % 2 !== 0)
|
||||
}
|
||||
])
|
||||
const executeRefresh = () => {
|
||||
refresh()
|
||||
console.log()
|
||||
}
|
||||
|
||||
defineExpose({executeRefresh})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header
|
||||
:scroll-top="scrollTop"
|
||||
>
|
||||
拼团专区
|
||||
</Header>
|
||||
<view class="group-buy-container">
|
||||
<!-- 背景 -->
|
||||
<view class="bg-box">
|
||||
<image
|
||||
class="bg"
|
||||
:src="groupByBg" />
|
||||
</view>
|
||||
|
||||
<!-- 内容 -->
|
||||
<view class="main-box">
|
||||
<view class="main-box__inner">
|
||||
<view
|
||||
class="row-product"
|
||||
v-if="colList[0].data.length>0">
|
||||
<Goods
|
||||
row
|
||||
:ratio="true"
|
||||
:goods="colList[0].data[0]"
|
||||
imgWidth="200rpx"
|
||||
infoPadding="0rpx 20rpx"
|
||||
>
|
||||
<template #options="{goods}">
|
||||
<GoodsItemOptions :goods="goods" />
|
||||
</template>
|
||||
</Goods>
|
||||
</view>
|
||||
<view
|
||||
class="product-box"
|
||||
v-if="!listEmpty"
|
||||
>
|
||||
<!-- 分两列 后期瀑布流 -->
|
||||
<template
|
||||
v-for="col in colList"
|
||||
:key="col.name"
|
||||
>
|
||||
<view class="goods-col">
|
||||
<template
|
||||
v-for="item in col.data"
|
||||
:key="item.id"
|
||||
>
|
||||
<view class="product">
|
||||
<Goods
|
||||
:ratio="true"
|
||||
:goods="item"
|
||||
infoPadding="30rpx 10rpx"
|
||||
>
|
||||
<template #options="{goods}">
|
||||
<GoodsItemOptions :goods="goods" />
|
||||
</template>
|
||||
</Goods>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
<Empty
|
||||
v-else
|
||||
:iconSrc="emptyCollectIcon"
|
||||
>
|
||||
暂时没有商品推荐哦~
|
||||
</Empty>
|
||||
<!-- 加载中 -->
|
||||
<ListLoadLoading v-if="loading" />
|
||||
<!-- 加载完毕-->
|
||||
<ListLoadOver v-if="loadend">
|
||||
<template v-if="more">
|
||||
<span @click="push({ url: '/pages/goodsList/goodsList' })">
|
||||
浏览更多商品
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
到底了~
|
||||
</template>
|
||||
</ListLoadOver>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
page {
|
||||
background: $page-bg-color;
|
||||
}
|
||||
</style>
|
||||
<style
|
||||
scoped
|
||||
lang="scss">
|
||||
.group-buy-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
.bg-box {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
aspect-ratio: 750/435;
|
||||
|
||||
.bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.main-box {
|
||||
top: 250rpx;
|
||||
position: relative;
|
||||
@include usePadding(30, 30);
|
||||
|
||||
&__inner {
|
||||
//background: #000;
|
||||
.row-product {
|
||||
@include usePadding(30, 30);
|
||||
width: 100%;
|
||||
background: $white-color;
|
||||
border-radius: 15rpx;
|
||||
}
|
||||
|
||||
.product-box {
|
||||
margin-top: 30rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
//display: flex;
|
||||
//gap: 20rpx;
|
||||
|
||||
.goods-col {
|
||||
width: 49%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
flex-grow: 0;
|
||||
float: left;
|
||||
|
||||
&:nth-child(2) {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
.product {
|
||||
flex-grow: 0;
|
||||
width: 100%;
|
||||
background: $white-color;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.good-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 20rpx;
|
||||
|
||||
.price {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.sale {
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user