新增营销系统、分销系统、会员功能、门店、提现功能

This commit is contained in:
Shaw
2024-02-08 21:01:37 +08:00
parent 68b3f2dcc3
commit 17c043348a
1398 changed files with 81279 additions and 56269 deletions

View File

@ -0,0 +1,26 @@
<template>
<div class="div" :style="{backgroundColor:componentContent.bgColor,height:componentContent.height + 'rpx'}"></div>
</template>
<script setup>
import { toRefs } from 'vue';
const props = defineProps({
terminal: {
type: Number,
default: 4,
},
componentContent: {
type: Object,
default () {
return {};
}
},
})
const { componentContent } = toRefs(props)
</script>
<style scoped>
.div{
width: 100%;
}
</style>

View File

@ -0,0 +1,106 @@
<template>
<div class="banner" :class="'terminal' + terminal">
<swiper class="swiper" :circular="true" :indicator-dots="false" :autoplay="true" :style="{'height':bannerHeight + 'rpx'}" @change="swiperChange">
<swiper-item class="banner-item" v-for="(item,index) in bannerList" :key="index" :style="{backgroundImage: 'url('+ item.bannerUrl +')'}" @click="jumpLink(item.linkObj)">
<!-- <div class="a-link" @click="jumpLink(item.linkObj)"><img class="img" :src="item.bannerUrl" v-show="item.bannerUrl" mode="widthFix"></div>-->
</swiper-item>
</swiper>
<view class="swiper-dots" v-if="bannerList && bannerList.length > 1">
<text class="dot" :class="index === swiperCurrent && 'dot-active'" v-for="(dot, index) in bannerList.length"
:key="index"></text>
</view>
</div>
</template>
<script setup>
import { ref, toRefs, onMounted, computed } from 'vue';
import funMixin from '../config/mixin/funMixin'
const { jumpLink } = funMixin()
const props = defineProps({
terminal: {
type: Number,
default: 4,
},
componentContent: {
type: Object,
default () {
return {};
}
},
})
const { terminal, componentContent } = toRefs(props)
const swiperCurrent = ref(0)
const bannerHeight = ref(0)
onMounted(() => {
bannerHeight.value = componentContent.value.height
// this.$forceUpdate() // 刷新轮播图
})
function swiperChange(e) {
swiperCurrent.value = e.detail.current;
}
const bannerList = computed(() => {
return componentContent.value?.bannerData.filter(function (item) {
return item.bannerUrl
})
})
</script>
<style lang="scss" scoped>
.banner{
position: relative;
padding: 0 34rpx;
.banner-item{
border-radius: 15rpx;
width: 100%;
background-repeat: no-repeat;
background-position: center;
background-size: auto 100%;
img{
display: none;
}
}
&.terminal4{
::v-deep .el-carousel{
height: 100%;
.el-carousel__container{
height: 100%;
}
}
.banner-item{
background-repeat: no-repeat;
background-position: center;
background-size: auto 100%;
img{
display: none;
}
}
}
.swiper-dots {
display: flex;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 20upx;
z-index: 200;
.dot {
width: 12upx;
height: 12upx;
background: #FFFFFF;
border-radius: 6upx;
opacity: 0.2;
margin: 0 10upx;
}
.dot-active {
opacity: 1;
width: 24upx;
}
}
}
</style>

View File

@ -0,0 +1,148 @@
<template>
<div class="brand-list warp" :class="'terminal' + terminal">
<div class="brand-top">
<div class="hom-title">
{{ componentContent.title }}
</div>
<div v-show="componentContent.showMore" class="btn-more" @click="jumpLink(componentContent.linkObj)">查看更多</div>
</div>
<div class="content-warp">
<div class="ul clearfix">
<div class="li" :class="componentContent.imgList.length<4 && `li-${componentContent.imgList.length}`" v-for="(item, index) in componentContent.imgList" :key="index">
<a class="item a-link" @click="jumpLink(item.linkObj)">
<div class="imgBox">
<image
class="img"
v-show="item.imgData"
:src="item.imgData"
:alt="item.title"
mode="aspectFit"
/>
</div>
<h4 class="h4">{{ item.title }}</h4>
</a>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { toRefs } from 'vue';
import funMixin from '../config/mixin/funMixin.js'
const { jumpLink } = funMixin()
const props = defineProps({
terminal: {
type: Number,
default: 4,
},
componentContent: {
type: Object,
default () {
return {};
}
}
})
const { terminal, componentContent } = toRefs(props)
</script>
<style lang="scss" scoped>
.brand-list {
color: #fff;
.brand-top{
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
.hom-title {
font-size: 32rpx;
color: #333;
line-height: 48rpx;
font-weight: normal;
}
.btn-more{
font-size: 24rpx;
color: #999999;
padding-right: 30rpx;
background: url("https://b2c-pro-static-dev.zkthink.com/static/images/icon-arrow.png") no-repeat right center / 20rpx 20rpx;
}
}
.content-warp {
display: flex;
}
.ul {
width: 100%;
display: flex;
flex-wrap: wrap;
.li {
flex: 0 0 25%;
padding: 10rpx 0 0 10rpx;
box-sizing: border-box;
.item {
height: auto;
display: flex;
flex-direction: column;
justify-content: center;
.imgBox {
padding-bottom: 60%;
background-color: #e8e8e8;
position: relative;
.img{
max-width: 100%;
height: 100%;
max-height: 100%;
position: absolute;
margin: auto;
top: 0;
right: 0;
bottom: 0;
left: 0;
object-fit: contain;
}
}
.h4 {
font-size: 18rpx;
color: #333333;
text-align: center;
height: 40rpx;
line-height: 40rpx;
font-weight: normal;
}
.p {
font-size: 12rpx;
margin: 7rpx 0 12rpx;
}
}
&.li-1{
flex: 0 0 100%;
}
&.li-2{
flex: 0 0 50%;
}
&.li-3{
flex: 0 0 33.3%;
}
}
}
}
.terminal1,
.terminal2,
.terminal3 {
&.brand-list {
.content-warp {
display: block;
}
.ul {
width: auto;
margin-left: -15rpx;
.li {
padding: 15rpx 0 0 15rpx;
width: 50%;
.item {
padding-left: 0;
}
}
}
}
}
</style>

View File

@ -0,0 +1,110 @@
<template>
<div class="category-list warp" :class="'terminal' + terminal">
<h2 class="hom-title" :style="{textAlign:componentContent.textAlign}">{{componentContent.title}}</h2>
<div class="content-warp">
<div class="ul clearfix" :class="{number5: componentContent.categoryData.length === 5}">
<div class="li" v-for="(item) of componentContent.categoryData" :key="item.id">
<a class="item a-link" @click="jumpCategory(item.id)">
<div class="imgBox">
<image ref="getHeight" :src="item.img" v-show="item.img" :alt="item.name" mode="aspectFit"/>
</div>
</a>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { toRefs } from 'vue';
import funMixin from '../config/mixin/funMixin.js'
const { jumpCategory } = funMixin()
const props = defineProps({
terminal: {
type: Number,
default: 4,
},
componentContent: {
type: Object,
default () {
return {};
}
},
})
const { terminal, componentContent } = toRefs(props)
</script>
<style lang="scss" scoped>
.category-list{
.hom-title{
font-size: 32rpx;
color: #333;
line-height: 48rpx;
margin-bottom: 5rpx;
text-align: center;
font-weight: normal;
}
.content-warp{
display: flex;
.ul{
width: 100%;
display: flex;
flex-wrap: wrap;
.li{
flex: 1;
padding: 10upx 0 0 10upx;
box-sizing: border-box;
.item{
height: auto;
display: flex;
flex-direction: column;
justify-content: center;
.imgBox {
padding-bottom: 80%;
background-color: #cacaca;
position: relative;
}
image {
max-width: 100%;
height: 100%;
max-height: 100%;
position: absolute;
margin: auto;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
}
}
}
.number5 {
display: block;
.li {
width: 25%;
float: left;
}
.li:nth-child(1) {
width: 50%;
}
}
}
}
@media screen and (max-width: 768px) {
.category-list .content-warp .ul .li{
flex: 0 0 50%;
}
}
.terminal1,.terminal2,.terminal3{
&.category-list .content-warp{
display: block;
.ul{
margin: -15upx 0 0 -15upx;
width: auto;
.li{
flex: 0 0 50%;
padding: 15upx 0 0 15upx;
}
}
}
}
</style>

View File

@ -0,0 +1,216 @@
<template>
<div class="coupon-box" >
<div class="coupon-list coupon-flex coupon-swiper" v-if="componentContent.arrangeType == '横向滑动'">
<swiper class="swiper"
:disable-touch="couponsData.length < 3"
:circular="false"
:indicator-dots="false"
:autoplay="couponsData.length > 2"
:display-multiple-items="2"
:previous-margin="swiperCurrent === couponsData.length - 2?'32rpx':'0rpx'"
:next-margin="swiperCurrent === couponsData.length - 2?'0rpx':'32rpx'">
<swiper-item class="swiper-slide" v-for="(item, index) in couponsData" :key="index">
<couponItem :item="item" :componentContent="componentContent" @receive="receiveCoupon" />
</swiper-item>
</swiper>
</div>
<div class="coupon-list" v-else :class="componentContent.arrangeType === '多行多列' && 'coupon-flex'">
<couponItem class="item" v-for="(item, index) in couponsData" :key="index" :item="item" :componentContent="componentContent" @receive="receiveCoupon" />
</div>
</div>
</template>
<script setup>
import commonMixin from './mixin';
import couponItem from './item.vue';
import { toRefs } from 'vue';
const props = defineProps({
typeId: {
type: Number,
default: 1,
},
shopId: {
type: Number,
default: 0,
},
componentContent: {
type: Object,
default () {
return {}
},
},
});
const { typeId, shopId, componentContent } = toRefs(props);
const { couponsData, receiveCoupon } = commonMixin(componentContent, typeId, shopId);
</script>
<script>
export default {
options: {
styleIsolation: 'shared'
},
}
</script>
<style lang="scss" scoped>
.coupon-box{
padding: 0 35rpx;
::v-deep .coupon-list{
.item{
margin-top: 24rpx;
display: block;
&:first-child{
margin-top: 0;
}
}
.coupon-item {
width: 100%;
height: 160rpx;
background: url('https://b2c-pro-static-dev.zkthink.com/static/images/canvas/bg-coupon-l.png') no-repeat;
background-size: 100% 100%;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
.discount{
display: flex;
align-items: baseline;
font-size: 68rpx;
line-height: 64rpx;
font-weight: bold;
.unit{
font-size: 32rpx;
}
}
.info{
font-size: 38rpx;
.type{
font-size: 32rpx;
font-weight: bold;
color: #333333;
line-height: 45rpx;
}
.tip{
font-size: 24rpx;
white-space: nowrap;
}
}
.button{
width: 144rpx;
height: 64rpx;
border-radius: 8rpx;
background: #ee6d46;
font-size: 24rpx;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.disable{
background: #666666;
}
.expired{
background: #999999;
}
.coupon-left{
width: 206rpx;
display: flex;
justify-content: center;
}
.coupon-right{
flex: 1;
display: flex;
padding: 0 32rpx;
justify-content: space-between;
align-items: center;
}
}
&.coupon-flex{
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.item:nth-child(2){
margin-top: 0;
}
.coupon-item {
width: 333rpx;
height: 140rpx;
background-image: url('https://b2c-pro-static-dev.zkthink.com/static/images/canvas/bg-coupon-m.png');
.coupon-left{
width: 147rpx;
flex-direction: column;
align-items: center;
.type{
font-size: 20rpx;
font-weight: bold;
line-height: 28rpx;
}
}
.coupon-right{
flex-direction: column;
padding: 0;
}
.discount{
font-size: 32rpx;
line-height: 45rpx;
.unit{
font-size: 24rpx;
}
}
.info{
.tip{
font-size: 20rpx;
line-height: 36rpx;
margin-bottom: 9rpx;
}
}
.button{
width: 132rpx;
height: 48rpx;
border-radius: 4rpx;
font-size: 22rpx;
}
}
}
&.coupon-swiper{
.swiper{
height: 140rpx;
}
.swiper-slide{
width: 310rpx;
.coupon-item {
width: 310rpx;
background-image: url('https://b2c-pro-static-dev.zkthink.com/static/images/canvas/bg-coupon-s.png');
margin-bottom: 0;
.coupon-left{
width: 130rpx;
}
}
}
::v-deep .uni-swiper-dots{
display: flex;
justify-content: center;
bottom: 27rpx;
.uni-swiper-dot{
width: 24upx;
height: 4upx;
background: #fff;
opacity: 0.5;
border-radius: 2upx;
margin: 0 5upx;
&-active{
opacity: 1;
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,57 @@
<template>
<div class="coupon-item">
<div class="coupon-left">
<!-- 折扣力度 -->
<div class="discount">
<div class="unit pre-unit" v-if="item.couponType === 1">
</div>
{{item.couponType === 1 ? item.couponValue : item.discount}}
<div class="unit" v-if="item.couponType === 2">
</div>
</div>
<div class="type" v-if="componentContent.arrangeType === '多行多列'">
{{item.couponType === 1 ? '满减券' : '折扣券'}}
</div>
</div>
<div class="coupon-right">
<div class="info">
<div class="type" v-if="componentContent.arrangeType === '一行一个'">
{{item.couponType === 1 ? '满减券' : '折扣券'}}
</div>
<div class="tip">
{{item.threshold!==0?`(实付${item.threshold}元使用)`:'无门槛使用'}}
</div>
</div>
<!-- button -->
<div class="button get-coupon" @click="receiveCoupon(item)">
领取
</div>
</div>
</div>
</template>
<script setup>
import {toRefs} from "vue";
const emits = defineEmits(['receive'])
const props = defineProps({
item: {
type: Object,
default () {
return {}
},
},
componentContent: {
type: Object,
default () {
return {}
},
},
});
const { item, componentContent } = toRefs(props);
function receiveCoupon (item) {
emits('receive', item)
}
</script>

View File

@ -0,0 +1,122 @@
import api from '../../config/api'
import { funMixin } from '../../config/mixin'
import { ref, onMounted } from 'vue';
import cookie from '@/utils/cookie'
export default function (componentContent, typeId, shopId) {
const { sendReq, beforeGetData, afterGetData, jumpStore } = funMixin()
const couponsData = ref([])
onMounted(() => {
getData()
})
// 获取优惠券列表
function getData () {
if (
componentContent.value.selectedCoupon &&
componentContent.value.selectedCoupon.length > 0
) {
beforeGetData()
let _url = `${api.getCoupons}?isPage=2&ids=${componentContent.value.selectedCoupon}`
const params = {
method: 'GET',
url: _url,
}
sendReq(
params,
(res) => {
afterGetData()
couponsData.value = res.data.list
},
() => {
afterGetData()
}
)
} else {
couponsData.value = []
}
}
// 领取优惠券
async function receiveCoupon (item) {
var token = cookie.get('accessToken')
if (typeof uni !== 'undefined') {
if (token) {
const params = {
url: `${api.takeCoupon}${item.id}`,
method: 'GET',
}
sendReq(
params,
(res) => {
if (res.code !== 0) {
uni.showToast({
title: res.msg,
icon: 'none'
})
} else {
getData()
uni.showToast({
title: '领取成功',
icon: 'success'
})
}
},
() => {
if (res.code !== 0) {
uni.showToast({
title: res.msg,
icon: 'none'
})
}
}
)
} else {
uni.showToast({
title: '请先登录',
icon: 'none'
})
uni.navigateTo({
url: '/pages/login/guid'
})
}
} else {
if (token) {
const params = {
url: `${api.takeCoupon}${item.id}`,
method: 'GET',
}
sendReq(params).then(res => {
ElMessage({
message: '领取成功!',
type: 'success'
})
getData()
}).catch(res => {
if (res.data.code !== '200') {
ElMessage({
message: res.data.message,
type: 'success'
})
}
})
} else {
ElMessage({
message: '请先登录'
})
// 登录弹框
// store.commit('IS_LOGIN', false) // 清除顶部个人中心数据
// store.commit('SHOW_LOGIN') // 调用登录弹框
}
}
}
return {
componentContent,
couponsData,
receiveCoupon,
jumpStore
}
}

View File

@ -0,0 +1,237 @@
<template>
<div class="custom" :class="'terminal' + terminal">
<div class="rowLayout" v-if="componentContent.layoutType ==='L1' || componentContent.layoutType ==='L2' || componentContent.layoutType ==='L3' || componentContent.layoutType ==='L4'">
<div class="customLayout" :style="{'padding':'0 ' + componentContent.pageSpacing + 'px','marginLeft':(-componentContent.imgClearance) +'px'}">
<div class="ul clearfix" :class="'layout'+componentContent.layoutType">
<div class='li' v-for="(item,index) of componentContent.imgData" :key="index" :style="{'width':getItemValue(item.width) + '%','height':getItemValue(item.height) + '%','left':getItemValue(item.left) + '%','top':getItemValue(item.top) + '%','paddingLeft':componentContent.imgClearance +'px'}">
<a class="a-link" @click="jumpLink(item.linkObj)"><image class="img" :src="item.src" v-if="item.src" mode="widthFix" /></a>
</div>
</div>
</div>
</div>
<div v-else :style="{'padding':'0 ' + componentContent.pageSpacing + 'upx'}">
<div class="boxLayout" :style="{'paddingBottom':componentContent.maxH !== 0?getItemValue(componentContent.maxH) + '%': (componentContent.layoutType === 'L1T1B2' ? '50%' : '100%')}">
<div class="boxLayoutInner">
<div class="boxWarp">
<div class="customLayout" :style="{'marginLeft':(-componentContent.imgClearance) +'px','top':(-componentContent.imgClearance) +'px'}">
<div class="ul clearfix" :class="'layout'+componentContent.layoutType">
<div class='li' v-for="(item,index) of componentContent.imgData" :key="index" :style="{'width':getItemValue(item.width) + '%','height':getItemValue(item.height) + '%','left':getItemValue(item.left) + '%','top':getItemValue(item.top) + '%','padding':componentContent.imgClearance +'px 0 0 ' + componentContent.imgClearance +'px'}">
<a class="a-link" @click="jumpLink(item.linkObj)"><image class="img" :src="item.src" v-if="item.src" mode="aspectFit" /></a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { toRefs } from 'vue';
import funMixin from '../config/mixin/funMixin.js'
const { jumpLink } = funMixin()
const props = defineProps({
terminal: {
type: Number,
default: 4,
},
componentContent: {
type: Object,
default () {
return {};
}
}
})
const { terminal, componentContent } = toRefs(props)
// 计算生成格子百分比
function getItemValue (val) {
const density = parseInt(componentContent.value.density)
if (val === 0 || density === 0) {
return 0
}
return ((val / density) * 10000) / 100.0 // 小数点后两位百分比
}
</script>
<style lang="scss" scoped>
.custom{
//width: 710upx;
//margin: 0 auto;
.boxLayout{
position: relative;
.boxLayoutInner{
padding-bottom: 100%;
position: absolute;
width: 100%;
left: 0;
top: 0;
}
.boxWarp{
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
overflow: hidden;
}
}
.customLayout{
position: relative;
.ul{
display: flex;
flex-wrap: wrap;
position: relative;
}
.li{
box-sizing: border-box;
.img{
width: 100%;
display: block;
}
.a-link {
height: 100%;
}
}
.layoutL1 .li{
flex: 0 0 100%;
}
.layoutL2 .li{
flex: 0 0 50%;
}
.layoutL3 .li{
flex: 0 0 33.3%;
}
.layoutL4 .li{
flex: 0 0 25%;
}
.layoutT2B2{
padding-bottom: 100%;
.li{
width: 50%;
height: 50%;
position: absolute;
.img{
width: 100%;
height: 100%;
}
&:nth-child(1){
left: 0;
top: 0;
}
&:nth-child(2){
right: 0;
top: 0;
}
&:nth-child(3){
left: 0;
bottom: 0;
}
&:nth-child(4){
right: 0;
bottom: 0;
}
}
}
.layoutL1R2{
padding-bottom: 100%;
.li{
width: 50%;
height: 50%;
position: absolute;
.img{
width: 100%;
height: 100%;
}
&:nth-child(1){
height: 100%;
left: 0;
top: 0;
}
&:nth-child(2){
right: 0;
top: 0;
}
&:nth-child(3){
right: 0;
bottom: 0;
}
}
}
.layoutT1B2{
padding-bottom: 100%;
.li{
width: 50%;
height: 50%;
position: absolute;
.img{
width: 100%;
height: 100%;
}
&:nth-child(1){
width: 100%;
left: 0;
top: 0;
}
&:nth-child(2){
left: 0;
bottom: 0;
}
&:nth-child(3){
right: 0;
bottom: 0;
}
}
}
.layoutL1T1B2{
padding-bottom: 50%;
.li{
position: absolute;
.img{
width: 100%;
height: 100%;
}
&:nth-child(1){
width: 50%;
height: 100%;
left: 0;
top: 0;
}
&:nth-child(2){
right: 0;
top: 0;
width: 50%;
height: 50%;
}
&:nth-child(3){
left: 50%;
bottom: 0;
width: 25%;
height: 50%;
}
&:nth-child(4){
right: 0;
bottom: 0;
width: 25%;
height: 50%;
}
}
}
.layoutaverage{
padding-bottom: 100%;
.li{
position: absolute;
.img{
width: 100%;
height: 100%;
}
}
}
// #ifdef MP
.layoutaverage{
padding-bottom: 91%;
}
// #endif
}
}
</style>

View File

@ -0,0 +1,171 @@
<template>
<div class="spike">
<div class="spike-card" v-if="productList?.length>0">
<div class="spike-card-top">
<h2 class="spike-card-top-title">
限时折扣
</h2>
<div class="spike-card-top-time" v-if="activityData?.state===2">
<div class="session">活动已结束</div>
</div>
<div class="spike-card-top-time" v-else-if="count?.length > 0">
<div class="session">距活动{{count[0]}}还有</div>
<div class="time">{{count[1]}}:{{count[2]}}:{{count[3]}}</div>
</div>
<a v-show="componentContent.showMore" class="btn-more" @click="jumpDiscount(componentContent.id)">查看更多</a>
</div>
<div class="spike-card-list">
<div class="spike-card-item" v-for='item in productList.slice(0,3)' :key='item.productId' @click="jumpProductDetail(item)">
<div class="spike-card-item-img">
<image :src="item.image" alt="" />
</div>
<div class="spike-card-item-info">
<h3 class="name">
{{item.storeName}}
</h3>
<div class="price-warp">
<div class="price">¥{{item.price}}</div>
<!-- <div class="original-price">
¥ {{item.originalPrice}}
</div> -->
<div class="stock">
限量{{item.stock}}件
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import commonMixin from '../mixin'
import { toRefs } from 'vue';
const props = defineProps({
typeId: {
type: Number,
default: 1,
},
shopId: {
type: Number,
default: 0,
},
componentContent: {
type: Object,
default () {
return {};
}
}
})
const { typeId, shopId, componentContent } = toRefs(props)
const { activityData, productList, count, jumpProductDetail, jumpDiscount } = commonMixin(componentContent, typeId, shopId)
</script>
<style lang="scss" scoped>
.spike{
&-card{
border-radius: 20rpx;
margin: 0 34rpx;
&-top{
position: relative;
display: flex;
margin-bottom: 17rpx;
&-title{
font-size: 32rpx;
color: #333333;
margin-right: 25rpx;
font-weight: normal;
}
&-time{
height: 40rpx;
border-radius: 21px;
border: 1px solid #EE6D46;
display: flex;
overflow: hidden;
align-items: center;
.session{
height: 100%;
line-height: 40rpx;
background: #EE6D46;
font-size: 20rpx;
color: #FFFFFF;
padding: 0 15rpx;
border-radius: 21rpx;
}
.time{
font-size: 20rpx;
color: #EE6D46;
padding: 0 14rpx 0 10rpx;
}
}
.btn-more{
position: absolute;
right: 8rpx;
top: 0rpx;
line-height: 40rpx;
padding-right: 30rpx;
font-size: 24rpx;
color: #999;
background: url("https://b2c-pro-static-dev.zkthink.com/static/images/icon-arrow.png") no-repeat right center / 20rpx 20rpx;
}
}
&-list{
background-color: #ffffff;
display: flex;
overflow: auto;
padding: 20rpx;
border-radius: 15rpx;
}
&-item{
align-items: center;
box-sizing: border-box;
flex: 0 0 203rpx;
margin-right: 18rpx;
&-img{
width: 203rpx;
height: 203rpx;
image{
width: 100%;
height: 100%;
object-fit: contain;
}
}
&-info{
width: 203rpx;
.name{
font-size: 30rpx;
font-weight: normal;
line-height: 40rpx;
color: #333333;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
padding: 15rpx 0 6rpx;
}
.price-warp{
display: flex;
align-items: center;
}
.price{
font-size: 32rpx;
color: #EE6D46;
padding-right: 10rpx;
display: inline-block;
}
.original-price{
font-size: 20rpx;
line-height: 28rpx;
color: #CCCCCC;
display: inline-block;
}
.stock{
line-height: 1em;
display: inline-block;
font-size: 20rpx;
color: #999999;
}
}
}
}
}
</style>

View File

@ -0,0 +1,102 @@
import api from '../../config/api'
import { funMixin } from '../../config/mixin'
import { ref, onMounted, onBeforeUnmount } from 'vue';
export default function (componentContent, typeId, shopId) {
const { sendReq, beforeGetData, afterGetData, jumpProductDetail, jumpDiscount } = funMixin()
const activityData = ref({})
const productList = ref([])
const count = ref([])
const timer = ref(null)
onMounted(() => {
if (componentContent.value.id) {
getProList()
getActivit()
} else {
productList.value = []
}
})
onBeforeUnmount(() => {
clearInterval(timer.value)
})
function getProList () {
beforeGetData()
const params = {
method: 'POST',
url: api.getActivityProduct,
data: {
isPage: 2,
ids: [componentContent.value.id]
}
}
sendReq(
params,
(res) => {
afterGetData()
productList.value = res.data.list
},
() => {
afterGetData()
}
)
}
function getActivit () {
beforeGetData()
const params = {
method: 'GET',
url: `${api.getActivity}?id=${componentContent.value.id}`,
}
sendReq(
params,
(res) => {
afterGetData()
activityData.value = res.data
getTime()
},
() => {
afterGetData()
}
)
}
function getTime () {
const date = new Date().getTime()
let startTime = activityData.value.startTime
let endTime = activityData.value.endTime
let time = 0
if (activityData.value.state === 0) {
time = startTime - date // 未开始
} else if(activityData.value.state === 1) {
time = endTime - date // 进行中
}
let countDownInterval = setInterval(()=>{
countDown(time)
time -= 1000
if(time <= 0){
clearInterval(countDownInterval)
activityData.value.state ++
}
},1000)
}
function countDown (time) {
const fn = (v) => (v < 10 ? `0${v}` : v)
const t = parseInt(time / 1000)
const text = activityData.value.state === 0 ? '开始' : '结束'
const hour = parseInt(t / 3600)
const min = parseInt((t % 3600) / 60)
const s = t % 60
count.value = [text, fn(hour), fn(min), fn(s)]
}
return {
activityData,
productList,
count,
jumpProductDetail,
jumpDiscount
}
}

View File

@ -0,0 +1,210 @@
<template>
<div class="group-list">
<div class="group-warp">
<div class="header">
<div class="header-warp">
<div class="title">超值拼团</div>
<text class="sub-title">拼着买更划算</text>
</div>
<a v-show="componentContent.showMore" class="btn-all a-link" @click="jumpGroupWorks(componentContent.id)">查看更多</a>
</div>
<div v-if="productList.length > 0">
<swiper class="swiper pro-box"
:disable-touch="productList.length < 3"
:circular="false"
:indicator-dots="false"
:autoplay="productList.length > 2"
:display-multiple-items="2"
:previous-margin="swiperCurrent === productList.length - 2?'60rpx':'0rpx'"
:next-margin="swiperCurrent === productList.length - 2?'0rpx':'60rpx'"
@change="swiperChange">
<swiper-item class="swiper-slide pro-item-warp" v-for="(item,index) in productList" :key="index" @click="jumpProductDetail(item)">
<div class="pro-item-inner">
<div class="pro-item">
<div class="pro-item-img">
<image class="img default-img" :src="item.image" />
</div>
<div class="pro-item-info">
<div class="name">{{item.storeName}}</div>
<div class="price">¥{{item.price}}</div>
<div class="group-num">{{item.person||0}}人团</div>
<div class="btn-buy">立刻拼团</div>
</div>
</div>
</div>
</swiper-item>
</swiper>
</div>
</div>
</div>
</template>
<script setup>
import {ref, toRefs} from 'vue';
import commonMixin from '../mixin'
const props = defineProps({
typeId: {
type: Number,
default: 1,
},
shopId: {
type: Number,
default: 0,
},
componentContent: {
type: Object,
default () {
return {};
},
}
})
const { typeId, shopId, componentContent } = toRefs(props)
const { productList, jumpProductDetail, jumpGroupWorks } = commonMixin(componentContent, typeId, shopId)
const swiperCurrent = ref(0)
function swiperChange(e) {
swiperCurrent.value = e.detail.current;
}
</script>
<style lang="scss" scoped>
.group-list{
padding: 0 34rpx;
.header{
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 20rpx;
.header-warp{
display: flex;
align-items: flex-end;
.title{
font-size: 32rpx;
color: #333333;
margin-right: 10rpx;
}
.sub-title{
font-size: 24rpx;
color: #EE6D46;
}
}
.btn-all{
font-size: 24rpx;
color: #999999;
padding-right: 30rpx;
background: url("https://b2c-pro-static-dev.zkthink.com/static/images/icon-arrow.png") no-repeat right center / 20upx 20upx;
}
}
.pro-box{
height: 462upx;
display: flex;
&.swiper-disabled{
.uni-swiper-wrapper{
position: static;
}
}
.pro-item{
width: 290upx;
background: #FFFFFF;
.pro-item-img{
height: 290upx;
.img{
width: 100%;
height: 290upx;
}
}
.pro-item-info{
padding: 14rpx 20rpx 30rpx;
position: relative;
.name{
font-size: 28rpx;
line-height: 40rpx;
color: #333333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 5upx;
}
.price{
color: #EE6D46;
font-size: 30upx;
line-height: 42upx;
}
.group-num{
font-size: 24rpx;
line-height: 33rpx;
color: #999999;
display: block;
}
.btn-buy{
width: 140rpx;
height: 50rpx;
background: #EE6D46;
position: absolute;
right: 20rpx;
bottom: 30rpx;
font-size: 24rpx;
color: #FFFFFF;
display: flex;
justify-content: center;
align-items: center;
}
}
}
}
::v-deep .uni-swiper-dots{
display: flex;
justify-content: center;
bottom: 27px;
.uni-swiper-dot{
width: 24upx;
height: 4upx;
background: #fff;
opacity: 0.5;
border-radius: 2upx;
margin: 0 5upx;
&-active{
opacity: 1;
}
}
}
//.pagination{
// display: flex;
// justify-content: center;
// ::v-deep .swiper-pagination-bullet{
// width: 24upx;
// height: 4upx;
// background: #fff;
// opacity: 0.5;
// border-radius: 2upx;
// margin: 0 5upx;
// }
// ::v-deep .swiper-pagination-bullet-active{
// opacity: 1;
// }
//}
.swiper-dots {
display: flex;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 15rpx;
z-index: 66;
.dot {
width: 24upx;
height: 4upx;
background: #fff;
opacity: 0.5;
border-radius: 2upx;
margin: 0 10upx;
}
.dot-active {
opacity: 1;
}
}
}
</style>

View File

@ -0,0 +1,55 @@
import api from '../../config/api'
import {
funMixin
} from '../../config/mixin'
import {
ref,
onMounted
} from 'vue';
export default function (componentContent, typeId, shopId) {
const {
sendReq,
beforeGetData,
afterGetData,
jumpProductDetail,
jumpGroupWorks
} = funMixin()
const productList = ref([])
onMounted(() => {
getData()
})
function getData () {
if (componentContent.value.id) {
beforeGetData()
const params = {
method: 'POST',
url: api.getActivityProduct,
data: {
isPage: 2,
type: 1
}
}
sendReq(
params,
(res) => {
afterGetData()
productList.value = res.data.list
},
() => {
afterGetData()
}
)
} else {
productList.value = []
}
}
return {
productList,
jumpProductDetail,
jumpGroupWorks
}
}

View File

@ -0,0 +1,142 @@
<template>
<div class="header">
<div class="header-row">
<image
class="logo"
:src="componentContent.imageUrl"
mode="heightFix"
/>
<div
class="search-col"
@click="toSearch(item)">
<div class="search-input">
<div v-if="componentContent.keyList.length === 0">搜索商品</div>
<swiper
v-else
class="swiper-wrapper"
:circular="true"
:indicator-dots="false"
:autoplay="true"
:vertical="true">
<swiper-item
class="swiper-slide"
v-for="(item,index) in componentContent.keyList"
:key="index">
<div class="a-link">{{ item }}</div>
</swiper-item>
</swiper>
</div>
</div>
</div>
<div class="tabs-nav-warp">
<div
class="tabs-nav"
scroll-x="true">
<div class="ul">
<div
v-for="(item, index) in componentContent.tabs"
:key="index"
class="li"
:class="{ on: activeTab === index + 1 }"
@click="jumpLink(item.link)"
>
{{ item.name }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, toRefs } from 'vue';
import commonMixin from '../mixin';
const props = defineProps({
componentContent: {
type: Object,
default() {
return {};
},
},
});
const {componentContent} = toRefs(props);
const {jumpLink, toSearch} = commonMixin();
const activeTab = ref(0);
</script>
<style
lang="scss"
scoped>
.header {
padding: 11rpx 34rpx 0;
.logo {
// width: 280px;
height: 40rpx;
margin-bottom: 11rpx;
}
.search-col {
height: 60rpx;
overflow: hidden;
border-radius: 30rpx;
padding: 0 30rpx 0 90rpx;
background: #FFFFFF url("https://b2c-pro-static-dev.zkthink.com/static/images/icon-search.png") no-repeat 30rpx center / auto 30rpx;
font-size: 24rpx;
line-height: 60rpx;
color: #999999;
margin: 24rpx 0;
.search-input{
width: 100%;
}
.swiper-wrapper{
width: 100%;
height: 60rpx;
}
}
}
.tabs-nav-warp {
margin-top: 20rpx;
overflow: hidden;
.tabs-nav {
.ul {
width: 100%;
display: flex;
flex-wrap: nowrap;
overflow: auto;
.li {
margin-left: 48rpx;
font-size: 28rpx;
color: #262626;
position: relative;
padding-bottom: 18rpx;
white-space: nowrap;
cursor: pointer;
&:first-child {
margin-left: 0;
}
&.on, &:hover {
font-weight: bold;
font-size: 36rpx;
&:after {
content: "";
width: 48rpx;
height: 6rpx;
background: #F26E47;
position: absolute;
left: 50%;
margin-left: -24rpx;
bottom: 10rpx;
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,9 @@
import { funMixin } from '../../config/mixin'
export default function () {
const { jumpLink, toSearch } = funMixin()
return {
jumpLink,
toSearch
}
}

View File

@ -0,0 +1,116 @@
<template>
<div class="imageText warp" :class="['terminal'+terminal,'pos-' + componentContent.positionValue]">
<div class="img-box img-left">
<a class="item a-link" @click="jumpLink(componentContent.linkObj)"><image class="img" :src="componentContent.imageUrl" alt="" mode="aspectFit"/></a>
</div>
<div class="text">
<h3 class="h3">{{componentContent.title}}</h3>
<div v-html="componentContent.content"></div>
</div>
<div class="img-box img-right">
<a class="item a-link" @click="jumpLink(componentContent.linkObj)"><image class="img" :src="componentContent.imageUrl" alt="" mode="aspectFit"/></a>
</div>
</div>
</template>
<script setup>
import { toRefs } from 'vue';
import funMixin from '../config/mixin/funMixin.js'
const { jumpLink } = funMixin()
const props = defineProps({
terminal: {
type: Number,
default: 4,
},
componentContent: {
type: Object,
default () {
return {};
}
}
})
const { terminal, componentContent } = toRefs(props)
</script>
<style lang="scss" scoped>
.imageText{
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
padding: 20upx 0;
.img-box{
width: 50%;
padding-bottom: 30%;
background-color: #cacaca;
position: relative;
.img{
max-width: 100%;
height: 100%;
max-height: 100%;
position: absolute;
margin: auto;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
}
.text{
width: 40%;
.h3{
font-size: 30upx;
margin-bottom: 24upx;
}
.p{
font-size: 16upx;
}
}
&.pos-top{
display: block;
text-align: center;
.img-box{
width: 100%;
}
.text{
width: 100%;
margin-top: 30upx;
}
.img-right{
display: none;
}
}
&.pos-bottom{
display: block;
text-align: center;
.img-box{
width: 100%;
}
.text{
width: 100%;
margin-bottom: 30upx;
}
.img-left{
display: none;
}
}
&.pos-left{
.img-right{
display: none;
}
}
&.pos-right{
.text{
padding-left: 20upx;
}
.img-left{
display: none;
}
}
}
.terminal1,.terminal2,.terminal3{
width: 710upx;
margin: 0 auto;
}
</style>

View File

@ -0,0 +1,141 @@
<template>
<div class="hom-pro-list warp" :class="'terminal'+terminal">
<div class="title">
<h2 class="h2" :style="{textAlign:componentContent.textAlign}">{{componentContent.title}}</h2>
</div>
<div class="ul clearfix" :class="{imgTextNum4: componentContent.imgTextData.length === 4, imgTextNum5: componentContent.imgTextData.length === 5, imgTextStyle: componentContent.imgTextData.length >= 6 || componentContent.imgTextData.length === 3}">
<div class="li" v-for="(item,index) in componentContent.imgTextData" :key="index">
<a class="item a-link" @click="jumpLink(item.linkObj)">
<div class="itemImgBox" v-show="item.isShow">
<div class="imgBox">
<image class="img" ref="getHeight" :src="item.imgData" v-show="item.imgData" :alt="item.title" mode="aspectFit"/>
</div>
</div>
<div class="text">
<h4 class="h4">{{item.title}}</h4>
<p class="p">{{item.describe}}</p>
</div>
</a>
</div>
</div>
</div>
</template>
<script setup>
import { toRefs } from 'vue';
import funMixin from '../config/mixin/funMixin.js'
const { jumpLink } = funMixin()
const props = defineProps({
terminal: {
type: Number,
default: 4,
},
componentContent: {
type: Object,
default () {
return {};
}
}
})
const { terminal, componentContent } = toRefs(props)
</script>
<style lang="scss" scoped>
.hom-pro-list{
min-height: 450rpx;
.title {
margin-bottom: 17rpx;
position: relative;
.h2 {
font-size: 32rpx;
color: #333;
line-height: 48rpx;
font-weight: normal;
}
}
.ul{
margin: -15rpx 0 0 -15rpx;
display: flex;
flex-wrap: wrap;
.li{
width: 0;
flex: 0 0 50%;
padding: 15rpx 0 0 15rpx;
box-sizing: border-box;
.item{
.itemImgBox {
height: auto;
display: flex;
flex-direction: column;
justify-content: center;
.imgBox {
padding-bottom: 80%;
background-color: #cacaca;
position: relative;
.img {
max-width: 100%;
height: 100%;
max-height: 100%;
position: absolute;
margin: auto;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
}
}
.text{
padding:16rpx 20rpx;
text-align: center;
.h4{
//line-height: 25upx;
font-size: 28rpx;
overflow: hidden;
color: #333333;
font-weight: normal;
}
.p{
font-size: 20rpx;
color: #666666;
padding: 5rpx 0 10rpx;
}
}
}
}
}
.imgTextNum4 {
.li {
flex: 0 0 50%;
}
}
.imgTextNum5 {
.li {
flex: 0 0 33.33%;
}
.li:nth-child(1) {
flex: 0 0 50%;
}
.li:nth-child(2) {
flex: 0 0 50%;
}
}
.imgTextStyle {
.li {
flex: 0 0 33.33%;
}
}
}
@media screen and (max-width: 768px) {
.hom-pro-list .ul .li{
flex: 0 0 50%;
}
}
.terminal1,.terminal2,.terminal3{
width: 710upx;
margin: 0 auto;
&.hom-pro-list .ul .li{
flex: 0 0 50%;
}
}
</style>

View File

@ -0,0 +1,101 @@
<template>
<div class="ul image-text-nav" :class="'terminal' + terminal">
<div
v-for="(item, index) in componentContent.imgTextData"
:key="index"
class="li"
:style="{ flex: '0 0 ' + getItemValue() + '%' }"
@click="jumpLink(item.linkObj)"
>
<div class="img-box">
<div class="img-box-inner">
<image class="img" :src="item.img" fit="contain" />
</div>
</div>
<h4 class="h4">{{ item.title }}</h4>
</div>
</div>
</template>
<script setup>
import { toRefs } from 'vue';
import { funMixin } from '../config/mixin'
const { jumpLink } = funMixin()
const props = defineProps({
terminal: {
type: Number,
default: 4,
},
componentContent: {
type: Object,
default () {
return {};
}
}
})
const { terminal, componentContent } = toRefs(props)
// 计算生成格子百分比
function getItemValue () {
const len = parseInt(componentContent.value.imgTextData.length)
if (len === 0) {
return 0
} else {
return ((1 / len) * 10000) / 100.0
}
}
</script>
<style lang="scss" scoped>
.image-text-nav {
min-height: 102rpx;
width: 680rpx;
margin: 0 auto;
display: flex;
padding: 21rpx 0;
background-color: #fff;
border-radius: 15rpx;
.li {
text-align: center;
.img-box {
.img {
width: 60rpx;
height: 60rpx;
}
}
.h4 {
font-size: 24rpx;
color: #333;
line-height: 33rpx;
margin-top: 18rpx;
}
}
&.terminal4 {
width: 1000rpx;
.li {
.img-box {
display: inline-block;
width: 100rpx;
height: 100rpx;
border: 2rpx solid #f3f4f5;
border-radius: 10rpx;
&-inner {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.img {
width: 60rpx;
height: 60rpx;
}
}
.h4 {
font-size: 18rpx;
color: #ccc;
line-height: 1em;
padding-top: 20rpx;
}
}
}
}
</style>

View File

@ -0,0 +1,190 @@
<template>
<div class="hom-pro-list" v-if="productData.length>0">
<div class="product-swiper">
<div class="product-swiper-box">
<div class="product-swiper-warp" v-for="(item,index) in productData.slice(0, 3)" :key="index">
<div class=" product-swiper-item" @click="jumpProductDetail(item)">
<div class="product-swiper-img">
<image class="img pic-img default-img" :src="item.image"/>
</div>
<div class="product-swiper-info">
<label class="product-name">{{item.productName}}</label>
<div class="price-warp">
<!-- #ifdef MP-WEIXIN -->
<image class="iconImg" v-if="item.activityType == 1" src="https://ceres.zkthink.com/static/canvas-images/groupBuyIcon.png"/>
<image class="iconImg" v-if="item.activityType == 2" src="https://ceres.zkthink.com/static/canvas-images/spikeIcon.png"/>
<image class="iconImg" v-if="item.activityType == 4" src="https://ceres.zkthink.com/static/canvas-images/spikeIcon.png"/>
<image class="iconImg" v-if="item.activityType == 3" src="https://ceres.zkthink.com/static/canvas-images/discountListIcon.png"/>
<image class="iconImg" v-if="item.activityType == 5" src="https://ceres.zkthink.com/static/canvas-images/discountListIcon.png"/>
<image class="iconImg" v-if="item.activityType == 9" src="https://ceres.zkthink.com/static/canvas-images/memberCenterIcon.png"/>
<image class="iconImg" v-if="item.activityType == 8" src="https://zk-cereshop.oss-cn-shenzhen.aliyuncs.com/zkthink/2022-02-15/d0d8d96f28904167b271de4ae924d1a8_sceneMarketing.png"/>
<!-- #endif -->
<!-- #ifdef H5 || APP-PLUS -->
<image class="iconImg" v-if="item.activityType == 1" src="https://ceres.zkthink.com/static/canvas-images/groupBuyIcon.png"></image>
<image class="iconImg" v-if="item.activityType == 2" src="https://ceres.zkthink.com/static/canvas-images/spikeIcon.png"></image>
<image class="iconImg" v-if="item.activityType == 4" src="https://ceres.zkthink.com/static/canvas-images/spikeIcon.png"></image>
<image class="iconImg" v-if="item.activityType == 3" src="https://ceres.zkthink.com/static/canvas-images/discountListIcon.png"></image>
<image class="iconImg" v-if="item.activityType == 5" src="https://ceres.zkthink.com/static/canvas-images/discountListIcon.png"></image>
<image class="iconImg" v-if="item.activityType == 9" src="https://ceres.zkthink.com/static/canvas-images/memberCenterIcon.png"></image>
<image class="iconImg" v-if="item.activityType == 8" src="https://zk-cereshop.oss-cn-shenzhen.aliyuncs.com/zkthink/2022-02-15/d0d8d96f28904167b271de4ae924d1a8_sceneMarketing.png"></image>
<!-- #endif -->
<div class="price">
¥ {{item.price}}
</div>
<!-- <div class="original-price">-->
<!-- ¥ {{item.originalPrice}}-->
<!-- </div>-->
</div>
</div>
</div>
</div>
</div>
<div class="pagination new-pagination"></div>
</div>
<button v-show="componentContent.showMore" class="btn-more" @click="jumpProList(componentContent.productData)">查看全部 <span class="icon iconfont icon-arrow-right"></span></button>
</div>
</template>
<script setup>
import { toRefs } from 'vue';
import commonMixin from '../mixin';
const props = defineProps({
componentContent: {
type: Object,
default () {
return {};
},
},
});
const { componentContent } = toRefs(props);
const { productData, jumpProductDetail, jumpProList } = commonMixin(componentContent);
</script>
<style lang="scss" scoped>
.hom-pro-list{
::v-deep .swiper-wrapper{
position: static;
}
/**横向滑动**/
.product-swiper{
width: 100%;
height: 454upx;
padding: 90upx 34upx 0;
background: url("https://ceres.zkthink.com/static/canvas-images/newProduct/bg-product-card.png") no-repeat center;
background-size: 710upx 454upx;
box-sizing: border-box;
position: relative;
&+.btn-more{
margin-top: 20upx;
}
.title{
padding: 22upx 0upx 0 0;
label{
background-image: none;
color: #A56C4C;
font-style: italic;
padding: 0;
}
}
&-box {
padding-bottom: 20upx;
display: flex;
}
&-warp{
padding: 0 5upx;
}
&-item {
width: 220upx;
position: relative;
background-color: #FFFFFF;
}
&-img {
width: 220upx;
height: 220upx;
position: relative;
&:after{
content: '';
display: block;
width: 54upx;
height: 54upx;
background: url("https://ceres.zkthink.com/static/canvas-images/newProduct/flag-new.png") no-repeat;
background-size: 100% 100%;
position: absolute;
top: 0;
left: 0;
}
.img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
&-info {
background-color: #FFFFFF;
padding: 10upx;
text-align: center;
.product-name{
font-size: 20upx;
color: #333;
display: block;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
margin-bottom: 6upx;
line-height: 28upx;
}
.price-warp{
display: flex;
justify-content: center;
align-items: center;
line-height: 28upx;
.iconImg {
width: 58rpx;
height: 36rpx;
margin-right: 10rpx;
}
.price{
color: #C83732;
font-size: 20upx;
margin-right: 10upx;
}
.original-price{
font-size: 16upx;
color: #ccc;
text-decoration: line-through;
}
}
}
}
}
.pagination{
display: flex;
justify-content: center;
width: 100%;
bottom: 0;
::v-deep .swiper-pagination-bullet{
width: 24upx;
height: 4upx;
background: #FFFFFF;
opacity: 0.5;
border-radius: 2upx;
margin: 0 10upx;
}
::v-deep .swiper-pagination-bullet-active{
opacity: 1;
}
}
.btn-more {
width: 170upx;
height: 54upx;
border: 2upx solid #C5AA7B;
color: #C5AA7B;
font-size: 24upx;
background-color: transparent;
margin: 20upx auto 0;
display: flex;
align-items: center;
}
</style>

View File

@ -0,0 +1,63 @@
import api from '../../config/api'
import {
funMixin
} from '../../config/mixin'
import {
ref,
onMounted,
} from 'vue';
export default function (componentContent) {
const {
sendReq,
jumpProductDetail,
jumpProList
} = funMixin()
const productData = ref([])
onMounted(() => {
getData(true)
})
function getData(isFirst) {
if (componentContent.value.productData.sourceType === '1') {
if(componentContent.value.productData.productIdList && componentContent.value.productData.productIdList.length>0){
sendReq({
url: `${api.getProducts}?page=1&pageSize=99&ids=${componentContent.value.productData.productIdList}`,
method: 'GET'
}, (proRes) => {
productData.value = proRes.data.list
if(isFirst){
componentContent.value.productData.imgTextData = productData.value
}
// _.$forceUpdate() // 刷新轮播图
})
} else {
productData.value = []
}
} else if(componentContent.value.productData.sourceType === '2'){
if(componentContent.value.productData.categoryId) {
sendReq({
url: `${api.getProducts}?page=1&pageSize=99&classifyId=${componentContent.value.productData.categoryId}`,
method: 'GET'
}, (proRes) => {
productData.value = proRes.data.list
if(isFirst){
componentContent.value.productData.imgTextData = productData.value
}
// _.$forceUpdate() // 刷新轮播图
})
} else {
productData.value = []
}
}
}
return {
productData,
jumpProductDetail,
jumpProList
}
}

View File

@ -0,0 +1,90 @@
<template>
<div class="notice-list" :class="'terminal'+terminal" :style="{backgroundColor:componentContent.bgColor}">
<swiper class="swiper-wrapper" :circular="true" :indicator-dots="false" :autoplay="true" :vertical="true">
<swiper-item class="swiper-slide" v-for="(item,index) in noticesList" :key="index">
<div class="a-link" @click="jumpNoticeDetail(item)" :style="{color:componentContent.titColor}"><span>{{item.noticeTitle}}</span></div>
</swiper-item>
</swiper>
</div>
</template>
<script setup>
import api from '../config/api'
import { ref, toRefs, onMounted } from 'vue';
import funMixin from '../config/mixin/funMixin.js'
const { jumpNoticeDetail, sendReq, beforeGetData, afterGetData } = funMixin()
const props = defineProps({
terminal: {
type: Number,
default: 4,
},
componentContent: {
type: Object,
default () {
return {};
}
}
})
const { terminal, componentContent } = toRefs(props)
const noticesList = ref([])
onMounted(() => {
getData()
})
function getData() {
let _url = `${api.getNotices}`
const params = {
method: 'GET',
url: _url,
}
sendReq(params, (res) => {
noticesList.value = res.data
})
}
</script>
<style lang="scss" scoped>
.notice-list{
height: 60upx;
line-height: 60upx;
overflow: hidden;
background: url("https://b2c-pro-static-dev.zkthink.com/static/images/icon-notice.png") no-repeat 20rpx center / 30upx 30upx;
.a-link{
// display: block;
cursor: pointer;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
margin: 0 20upx;
span{
display: inline-block;
padding-left: 50upx;
font-size: 24upx;
}
}
&.terminal4{
height: 50upx;
line-height: 50upx;
padding: 0;
.swiper-container{
height: 100%;
width: 1200upx;
max-width: 100%;
margin: 0 auto;
}
.a-link{
cursor: pointer;
display: block;
text-align: left;
margin: 0 20upx;
span{
display: block;
padding-left: 25upx;
font-size: 14upx;
}
}
}
}
</style>

View File

@ -0,0 +1,226 @@
<template>
<div class="group-list" v-if="productData&&productData.composeProducts&&productData.composeProducts.length>0">
<div class="group-warp">
<div class="title">
<label>
<!-- #ifdef MP-WEIXIN -->
<image class="title-img" src="https://ceres.zkthink.com/static/canvas-images/price/img-title.png" alt="组合优惠" mode="widthFix"/>
<!-- #endif -->
<!-- #ifdef H5 || APP-PLUS -->
<image class="title-img" src="https://ceres.zkthink.com/static/canvas-images/price/img-title.png" alt="组合优惠" mode="widthFix"/>
<!-- #endif -->
</label>
<div class="price-text">
<swiper class="swiper" :autoplay="true" :vertical="true">
<swiper-item v-for="(item,index) in productData.rules" :key="index">
{{item.price}}元任选{{item.number}}件
</swiper-item>
</swiper>
</div>
<a v-show="componentContent.showMore" class="btn-all a-link" @click="jumpCombination(productData)">更多<i class="iconfont icon-arrow-right"></i></a>
</div>
<div>
<swiper class="swiper pro-box" :indicator-dots="false" :autoplay="true" :display-multiple-items="2" @change="swiperChange">
<swiper-item class="pro-item-warp" v-for="(item,index) in productData.composeProducts" :key="index" @click="jumpProductDetail(item)">
<div class="pro-item-inner">
<div class="pro-item">
<div class="pro-item-img">
<image class="img default-img" :src="item.image"/>
</div>
<div class="pro-item-info">
<h3 class="name">
{{item.productName}}
</h3>
<div class="stock">
还剩{{item.stockNumber}}件
</div>
<div class="price-warp">
<div class="price">
¥ {{item.price}}
</div>
<div class="original-price">
¥ {{item.originalPrice}}
</div>
</div>
</div>
</div>
</div>
</swiper-item>
</swiper>
<view class="swiper-dots" v-if="productData.composeProducts && productData.composeProducts.length > 2">
<text class="dot" :class="index - swiperCurrent <= 1 && index - swiperCurrent >=0 && 'dot-active'" v-for="(dot, index) in productData.composeProducts.length"
:key="index"></text>
</view>
</div>
</div>
</div>
</template>
<script setup>
import {ref, toRefs} from 'vue';
import commonMixin from '../mixin';
const props = defineProps({
shopId: {
type: Number,
default: 0,
},
componentContent: {
type: Object,
default () {
return {};
},
},
});
const { shopId, componentContent } = toRefs(props);
const { productData, jumpProductDetail, jumpCombination} = commonMixin(componentContent, shopId);
const swiperCurrent = ref(0)
function swiperChange(e) {
swiperCurrent.value = e.detail.current;
}
</script>
<style lang="scss" scoped>
.group-list{
padding: 30upx 20upx 60upx;
.group-warp{
height: 544upx;
padding: 0 10upx;
background: #333333;
box-shadow: 0 20upx 30upx rgba(0, 0, 0, 0.3);
opacity: 1;
border-radius: 20upx;
position: relative;
}
.title{
display: flex;
align-items:center;
position: relative;
padding: 32upx 0 20upx 20upx;
.title-img{
width: 203upx;
}
.price-text{
width: 300upx;
height: 50upx;
background: linear-gradient(90deg, #C83732 0%, #E25C44 100%);
box-shadow: 0 6upx 12upx rgba(233, 0, 0, 0.3);
border-radius: 26upx;
font-size: 24upx;
color: #fff;
text-align: center;
line-height: 50upx;
margin-left: 20upx;
.swiper{
height: 50upx;
}
}
.btn-all{
position: absolute;
right: 8upx;
top: 40upx;
line-height: 33upx;
padding-right: 25upx;
font-size: 24upx;
color: #FFEBC4;
.iconfont{
content: '';
font-size: 26upx;
position: absolute;
right: 0;
top: 0;
}
}
}
.pro-box{
padding-bottom: 20upx;
height: 450upx;
.pro-item-inner{
padding: 0 10upx;
}
.pro-item{
width: 100%;
height: 412upx;
background: #FFFFFF;
.pro-item-img{
.img{
width: 100%;
height: 236upx;
}
}
.pro-item-info{
padding: 0 20upx;
.name{
font-size: 24upx;
line-height: 40upx;
color: #333333;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
.stock{
padding: 0 8upx;
height: 40upx;
border: 2upx solid #E4E5E6;
line-height: 40upx;
margin: 10upx 0;
display: inline-block;
font-size: 20upx;
color: #C5AA7B;
}
.price{
font-size: 32upx;
font-weight: bold;
line-height: 44upx;
color: #C83732;
padding-right: 10upx;
display: inline-block;
}
.original-price{
font-size: 20upx;
line-height: 28upx;
color: #CCCCCC;
display: inline-block;
}
}
}
}
//.pagination{
// display: flex;
// justify-content: center;
// ::v-deep .swiper-pagination-bullet{
// width: 24upx;
// height: 4upx;
// background: #fff;
// opacity: 0.5;
// border-radius: 2upx;
// margin: 0 5upx;
// }
// ::v-deep .swiper-pagination-bullet-active{
// opacity: 1;
// }
//}
.swiper-dots {
display: flex;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 15rpx;
z-index: 66;
.dot {
width: 24upx;
height: 4upx;
background: #fff;
opacity: 0.5;
border-radius: 2upx;
margin: 0 10upx;
}
.dot-active {
opacity: 1;
}
}
}
</style>

View File

@ -0,0 +1,54 @@
import api from '../../config/api'
import {
funMixin
} from '../../config/mixin'
const { sendReq} = funMixin()
import {
ref,
onMounted,
} from 'vue';
export default function (componentContent, shopId) {
const {
jumpProductDetail,
jumpCombination
} = funMixin()
const productData = ref({
composeProducts: [],
rules: [{
price: null,
number: null,
}],
})
onMounted(() => {
getData(true)
})
function getData() {
if(componentContent.value.priceId){
const params = {
method: 'GET',
url: `${api.getPrices}?shopId=${shopId.value}&ids=${componentContent.value.priceId}&page=1&pageSize=10`,
}
sendReq(params, (res) => {
if( res.data.length > 0){
productData.value = res.data[0]
}
})
} else {
productData.value = {
composeProducts: [],
rules: []
}
}
}
return {
getData,
productData,
jumpProductDetail,
jumpCombination
}
}

View File

@ -0,0 +1,500 @@
<template>
<global-loading />
<div class="hom-pro-list">
<div class="header">
<div class="header-warp">
<div class="title">商品推荐</div>
<!-- <text class="sub-title">拼着买更划算</text>-->
</div>
<a
v-show="componentContent.showMore"
class="btn-all a-link"
@click="jumpProList(productData)">查看更多<i class="iconfont icon-arrow-right"></i></a>
</div>
<div
v-if="componentContent.arrangeType === '横向滑动' && productData.length > 2"
class="product-list"
>
<swiper
ref="mySwiper"
class="swiper product-list-box"
:circular="true"
:indicator-dots="false"
:autoplay="true"
:display-multiple-items="2"
@change="swiperChange"
>
<swiper-item
class="product-list-item-warp"
v-for="(item,index) in productData"
:key="index"
>
<div
class="product-list-item full-height"
v-if="JSON.stringify(item)!=='{}'"
@click="jumpProductDetail(item)">
<div class="product-list-img">
<image
class="img pic-img default-img"
:src="item.image"
/>
</div>
<div class="product-list-info">
<label class="product-name">{{ item.storeName }}</label>
<div
v-if="item.campaignType === 1"
class="box-group">
<div class="price">¥{{ item.price }}</div>
<label class="group-num">{{ item.person || 0 }}人团</label>
<div class="btn">立刻拼团</div>
</div>
<div
v-else-if="[2,3].includes(item.campaignType)"
class="box-spike">
<div class="price">¥{{ item.campaignPrice }}</div>
<view class="quantity-warp">
<view class="quantity">
限量{{ item.campaignTotal }}
</view>
<uv-line-progress
activeColor="#EE6D46"
inactiveColor="#E6E6E6"
height="14rpx"
:percentage="getPercentageNum(item)"
:showText="false"
/>
</view>
<div class="btn">立即抢购</div>
</div>
<div
v-else
class="box-default">
<div class="price">¥{{ item.price }}</div>
<div class="quantity">限量{{ item.stock }}</div>
</div>
</div>
</div>
<!-- 自定义骨架屏 -->
<div
class="product-list-item ske-loading"
v-else
>
<div class="product-list-img child-loading">
</div>
<div class="product-list-info">
<label class="product-name child-loading"></label>
<div
class="price-warp child-loading"
style="padding: 5px 0">
</div>
<div
class="price-warp child-loading"
style="padding: 5px 0">
</div>
</div>
</div>
</swiper-item>
</swiper>
<view
class="swiper-dots"
v-if="productData && productData.length > 2"
>
<text
class="dot"
:class="index - swiperCurrent <= 1 && index - swiperCurrent >=0 && 'dot-active'"
v-for="(dot, index) in productData.length"
:key="index"
></text>
</view>
</div>
<div
v-else
class="product-list"
>
<div
class="product-list-box"
v-if="productData.length>0">
<div
class="product-list-item-warp"
v-for="(col,i) in colList"
:key="i"
>
<div
v-for="(item,index) in col.data"
:key="index"
@click="jumpProductDetail(item)"
class="product-list-item"
>
<div class="product-list-img">
<image
class="img pic-img default-img"
:src="item.image"
/>
</div>
<div class="product-list-info">
<label class="product-name">{{ item.storeName }}</label>
<div
v-if="item.campaignType === 1"
class="box-group">
<div class="price">¥{{ item.campaignPrice }}</div>
<label class="group-num">{{ item.person || 0 }}人团</label>
<div class="btn">立刻拼团</div>
</div>
<div
v-else-if="[2,3].includes(item.campaignType)"
class="box-spike">
<div class="price">¥{{ item.campaignPrice }}</div>
<view class="quantity-warp">
<view class="quantity">
限量{{ item.campaignTotal }}
</view>
<uv-line-progress
activeColor="#EE6D46"
inactiveColor="#E6E6E6"
height="14rpx"
:percentage="getPercentageNum(item)"
:showText="false"
/>
</view>
<div class="btn">立即抢购</div>
</div>
<div
v-else
class="box-default">
<div class="price">¥{{ item.price }}</div>
<div class="quantity">限量{{ item.stock }}</div>
</div>
</div>
</div>
</div>
</div>
<!-- 自定义骨架屏 -->
<div
class="product-list-box"
v-else>
<div
class="product-list-item-warp"
v-for="(item,i) in loadNum"
:key="i"
>
<div
class="product-list-item ske-loading"
>
<div class="product-list-img child-loading"></div>
<div class="product-list-info">
<label class="product-name child-loading"></label>
<div
class="price-warp child-loading"
style="padding: 5px 0"></div>
<div
class="price-warp child-loading"
style="padding: 5px 0"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { computed, ref, toRefs } from 'vue';
import productMixin from '../mixin.js';
const props = defineProps({
typeId: {
type: Number,
default: 1,
},
componentContent: {
type: Object,
default() {
return {};
},
},
});
const {typeId, componentContent} = toRefs(props);
const {productData, jumpProductDetail, jumpProList, loadNext, getPercentageNum} = productMixin(componentContent);
const swiperCurrent = ref(0)
const colList = computed(() => [
{
data: productData.value.filter((item, index) => index % 2 === 0)
},
{
data: productData.value.filter((item, index) => index % 2 !== 0)
}
])
const loadNum = computed(() => {
if (componentContent.value.productData.productIdList && componentContent.value.productData.productIdList.length > 0) {
return componentContent.value.productData.productIdList.length
} else {
return 8
}
})
function swiperChange(e) {
swiperCurrent.value = e.detail.current;
}
// 跳转到店铺主页
function jumpStore(item) {
uni.navigateTo({
url: `/pages_category_page1/store/index?storeId=${ item.shopId }`
})
}
defineExpose({loadNext})
</script>
<style
lang="scss"
scoped
>
.hom-pro-list {
padding: 0 34rpx;
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 20rpx;
.header-warp {
display: flex;
align-items: flex-end;
.title {
font-size: 32rpx;
color: #333333;
margin-right: 10rpx;
}
.sub-title {
font-size: 24rpx;
color: #EE6D46;
}
}
.btn-all {
font-size: 24rpx;
color: #999999;
padding-right: 30rpx;
background: url("https://b2c-pro-static-dev.zkthink.com/static/images/icon-arrow.png") no-repeat right center / 20upx 20upx;
}
}
.product-list {
position: relative;
&-box {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
flex-direction: row;
&.swiper {
height: 500rpx;
}
}
&.product-swiper .product-list-box {
padding-left: 0;
}
&-item {
width: 331rpx;
margin-bottom: 20rpx;
box-sizing: content-box;
border-radius: 15rpx;
overflow: hidden;
background-color: #fff;
}
&-img {
width: 331rpx;
height: 331rpx;
background-color: #f5f5f5;
.img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
&-info {
background-color: #FFFFFF;
//box-shadow: 0px 0px 15px 0px rgba(52, 52, 52, 0.15);
border-radius: 0 0 10rpx 10rpx;
padding: 20rpx;
label {
font-weight: normal;
}
.product-name {
font-size: 28rpx;
color: #333;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 7rpx;
line-height: 40rpx;
}
.box-group {
position: relative;
.price {
color: #EE6D46;
font-size: 30upx;
line-height: 42upx;
}
.group-num {
font-size: 24rpx;
line-height: 33rpx;
color: #999999;
display: block;
}
.btn {
width: 140rpx;
height: 50rpx;
background: #EE6D46;
position: absolute;
right: 0rpx;
bottom: 0rpx;
font-size: 24rpx;
color: #FFFFFF;
display: flex;
justify-content: center;
align-items: center;
}
}
.box-spike {
position: relative;
.price {
font-size: 30rpx;
color: #EE6D46;
}
.quantity-warp {
width: 130rpx;
.quantity {
font-size: 24rpx;
color: #999999;
}
::v-deep .uv-line-progress {
border-radius: 0;
&__background {
border-radius: 0;
}
&__line {
border-radius: 0;
}
}
}
.btn {
width: 140rpx;
height: 50rpx;
background: #EE6D46;
position: absolute;
right: 0rpx;
bottom: 0rpx;
font-size: 24rpx;
color: #FFFFFF;
display: flex;
justify-content: center;
align-items: center;
}
}
.box-default {
display: flex;
align-items: center;
justify-content: space-between;
.price {
font-size: 34rpx;
color: #EE6D46;
}
.quantity {
font-size: 24rpx;
color: #999999;
}
}
}
}
}
.swiper-dots {
display: flex;
justify-content: center;
padding: 20rpx 0;
.dot {
width: 10rpx;
height: 10rpx;
background: #333333;
opacity: 0.3;
border-radius: 5rpx;
margin: 0 10rpx;
}
.dot-active {
width: 20rpx;
opacity: 1;
}
}
.btn-more {
width: 170rpx;
height: 54rpx;
line-height: 54rpx;
border: 2rpx solid #C5AA7B;
color: #C5AA7B;
font-size: 24rpx;
background-color: transparent;
margin: 20rpx auto 0;
display: flex;
align-items: center;
}
.child-loading {
background: linear-gradient(90deg, #f0f2f5 25%, #e6e8eb 37%, #f0f2f5 63%);
background-size: 400% 100%;
animation: el-skeleton-loading 1.4s ease infinite;
}
@keyframes el-skeleton-loading {
0% {
background-position: 100% 50%
}
to {
background-position: 0 50%
}
}
.full-height {
height: 100%
}
</style>

View File

@ -0,0 +1,78 @@
import api from '../../config/api'
import { funMixin } from '../../config/mixin'
import { ref, onMounted } from 'vue';
export default function (componentContent) {
const { sendReq, beforeGetData, afterGetData, jumpProductDetail, jumpProList } = funMixin()
const productData = ref([])
onMounted(() => {
getData(true)
})
function getData (isFirst) {
if (componentContent.value.productData.sourceType === '1') {
if (
componentContent.value.productData.productIdList?.length > 0
) {
beforeGetData()
sendReq(
{
url: `${api.getProducts}?page=1&isPage=2&ids=${componentContent.value.productData.productIdList}`,
method: 'GET',
},
(proRes) => {
afterGetData()
productData.value = proRes.data.list
if (isFirst) {
componentContent.value.productData.imgTextData = productData.value
}
},
() => {
afterGetData()
}
)
} else {
productData.value = []
}
} else if (componentContent.value.productData.sourceType === '2') {
if (componentContent.value.productData?.categoryId) {
beforeGetData()
sendReq(
{
url: `${api.getProducts}?page=1&isPage=2&cateId=${componentContent.value.productData.categoryId}`,
method: 'GET',
},
(proRes) => {
afterGetData()
productData.value = proRes.data.list
if (isFirst) {
componentContent.value.productData.imgTextData = productData.value
}
// _.swiper.update()
},
() => {
afterGetData()
}
)
} else {
productData.value = {
products: [],
}
}
}
}
// 获取进度条数字
function getPercentageNum(item){
const total = item.campaignTotal || item.total
const stock = item.campaignStock || item.stock
return (total-stock)/total
}
return {
productData,
jumpProductDetail,
jumpProList,
getPercentageNum
}
}

View File

@ -0,0 +1,145 @@
<template>
<div class="shop" :class="'terminal' + terminal">
<swiper class="swiper" :indicator-dots="false" :autoplay="true" @change="swiperChange">
<swiper-item class="shop-item" v-for="(item,index) in imgList" :key="index">
<div class="shop-item-warp">
<image class="img" :src="item.img" mode="widthFix" />
<div class="a-link" @click="jumpLink(item.linkObj)">
进店逛逛<i class="iconfont icon-arrow-right"></i>
</div>
</div>
</swiper-item>
</swiper>
<view class="swiper-dots" v-if="imgList && imgList.length">
<text class="dot" :class="index === swiperCurrent && 'dot-active'" v-for="(dot, index) in imgList.length"
:key="index"></text>
</view>
</div>
</template>
<script setup>
import {toRefs, onMounted, computed, ref} from 'vue';
import funMixin from '../config/mixin/funMixin.js'
const { jumpLink } = funMixin()
const props = defineProps({
terminal: {
type: Number,
default: 4,
},
componentContent: {
type: Object,
default () {
return {};
}
},
})
const { terminal, componentContent } = toRefs(props)
const swiperCurrent = ref(0)
function swiperChange(e) {
swiperCurrent.value = e.detail.current;
}
onMounted(() => {
// this.$forceUpdate() // 刷新轮播图
})
const imgList = computed(() => {
return componentContent.value.imgTextData.filter(function (item) {
return item.img
})
})
</script>
<style lang="scss" scoped>
.shop{
position: relative;
.swiper{
height: 460upx;
}
&-item{
&-warp{
position: relative;
padding: 0 20upx;
.img{
width: 100%;
height: 420upx;
}
.a-link{
width: 220upx;
height: 80upx;
line-height: 80upx;
background: linear-gradient(225deg, #585858 0%, #333333 100%);
box-shadow: 0px 20upx 40upx rgba(0, 0, 0, 0.3);
display: block;
color: #fff;
font-size: 28upx;
text-align: center;
position: absolute;
right: 0;
bottom: 30upx;
.icon{
margin-left: 20upx;
}
}
}
}
//.pagination{
// display: flex;
// justify-content: center;
// padding:20upx 0;
// ::v-deep .swiper-pagination-bullet{
// width: 12upx;
// height: 12upx;
// background: #333333;
// border-radius: 50%;
// opacity: 0.2;
// margin: 0 10upx;
// }
// ::v-deep .swiper-pagination-bullet-active{
// width: 24upx;
// height: 12upx;
// background: #333333;
// opacity: 1;
// border-radius: 8upx;
// }
//}
//::v-deep .uni-swiper-dots{
// display: flex;
// justify-content: center;
// padding: 0upx 0;
// .uni-swiper-dot{
// width: 10upx;
// height: 10upx;
// background: #333333;
// opacity: 0.3;
// border-radius: 5upx;
// margin: 0 5upx;
// &-active{
// width: 20upx;
// height: 10upx;
// opacity: 1;
// }
// }
//}
.swiper-dots {
display: flex;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 15rpx;
z-index: 66;
.dot {
width: 10upx;
height: 10upx;
background: #333333;
opacity: 0.3;
border-radius: 5upx;
margin: 0 10upx;
}
.dot-active {
width: 20upx;
opacity: 1;
}
}
}
</style>

View File

@ -0,0 +1,171 @@
<template>
<div class="spike">
<div class="spike-card" v-if="productList?.length>0">
<div class="spike-card-top">
<h2 class="spike-card-top-title">
限时秒杀
</h2>
<div class="spike-card-top-time" v-if="activityData?.state===2">
<div class="session">活动已结束</div>
</div>
<div class="spike-card-top-time" v-else-if="count?.length > 0">
<div class="session">距活动{{count[0]}}还有</div>
<div class="time">{{count[1]}}:{{count[2]}}:{{count[3]}}</div>
</div>
<a v-show="componentContent.showMore" class="btn-more" @click="jumpSeckills(componentContent.id)">查看更多</a>
</div>
<div class="spike-card-list">
<div class="spike-card-item" v-for='item in productList.slice(0,4)' :key='item.productId' @click="jumpProductDetail(item)">
<div class="spike-card-item-img">
<image :src="item.image" alt="" />
</div>
<div class="spike-card-item-info">
<h3 class="name">
{{item.storeName}}
</h3>
<div class="price-warp">
<div class="price">¥{{item.price}}</div>
<!-- <div class="original-price">
¥ {{item.originalPrice}}
</div> -->
<div class="stock">
限量{{item.stock}}件
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import commonMixin from '../mixin'
import { toRefs } from 'vue';
const props = defineProps({
typeId: {
type: Number,
default: 1,
},
shopId: {
type: Number,
default: 0,
},
componentContent: {
type: Object,
default () {
return {};
}
}
})
const { typeId, shopId, componentContent } = toRefs(props)
const { activityData, productList, count, jumpProductDetail, jumpSeckills } = commonMixin(componentContent, typeId, shopId)
</script>
<style lang="scss" scoped>
.spike{
&-card{
border-radius: 20rpx;
margin: 0 34rpx;
&-top{
position: relative;
display: flex;
margin-bottom: 17rpx;
&-title{
font-size: 32rpx;
color: #333333;
margin-right: 25rpx;
font-weight: normal;
}
&-time{
height: 40rpx;
border-radius: 21rpx;
border: 1rpx solid #EE6D46;
display: flex;
overflow: hidden;
align-items: center;
.session{
height: 100%;
line-height: 40rpx;
background: #EE6D46;
font-size: 20rpx;
color: #FFFFFF;
padding: 0 15rpx;
border-radius: 21rpx;
}
.time{
font-size: 20rpx;
color: #EE6D46;
padding: 0 14rpx 0 10rpx;
}
}
.btn-more{
position: absolute;
right: 8rpx;
top: 0rpx;
line-height: 40rpx;
padding-right: 30rpx;
font-size: 24rpx;
color: #999;
background: url("https://b2c-pro-static-dev.zkthink.com/static/images/icon-arrow.png") no-repeat right center / 20rpx 20rpx;
}
}
&-list{
background-color: #ffffff;
display: flex;
overflow: auto;
padding: 20rpx;
border-radius: 15rpx;
}
&-item{
align-items: center;
box-sizing: border-box;
flex: 0 0 203rpx;
margin-right: 18rpx;
&-img{
width: 203rpx;
height: 203rpx;
image{
width: 100%;
height: 100%;
object-fit: contain;
}
}
&-info{
width: 203rpx;
.name{
font-size: 30rpx;
font-weight: normal;
line-height: 40rpx;
color: #333333;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
padding: 15rpx 0 6rpx;
}
.price-warp{
display: flex;
align-items: center;
}
.price{
font-size: 32rpx;
color: #EE6D46;
padding-right: 10rpx;
display: inline-block;
}
.original-price{
font-size: 20rpx;
line-height: 28rpx;
color: #CCCCCC;
display: inline-block;
}
.stock{
line-height: 1em;
display: inline-block;
font-size: 20rpx;
color: #999999;
}
}
}
}
}
</style>

View File

@ -0,0 +1,102 @@
import api from '../../config/api'
import { funMixin } from '../../config/mixin'
import { ref, onMounted, onBeforeUnmount } from 'vue';
export default function (componentContent, typeId, shopId) {
const { sendReq, beforeGetData, afterGetData, jumpProductDetail, jumpSeckills } = funMixin()
const activityData = ref({})
const productList = ref([])
const count = ref([])
const timer = ref(null)
onMounted(() => {
if (componentContent.value.id) {
getProList()
getActivit()
} else {
productList.value = []
}
})
onBeforeUnmount(() => {
clearInterval(timer.value)
})
function getProList () {
beforeGetData()
const params = {
method: 'POST',
url: api.getActivityProduct,
data: {
isPage: 2,
ids: [componentContent.value.id]
}
}
sendReq(
params,
(res) => {
afterGetData()
productList.value = res.data.list
},
() => {
afterGetData()
}
)
}
function getActivit () {
beforeGetData()
const params = {
method: 'GET',
url: `${api.getActivity}?id=${componentContent.value.id}`,
}
sendReq(
params,
(res) => {
afterGetData()
activityData.value = res.data
getTime()
},
() => {
afterGetData()
}
)
}
function getTime () {
const date = new Date().getTime()
let startTime = activityData.value.startTime
let endTime = activityData.value.endTime
let time = 0
if (activityData.value.state === 0) {
time = startTime - date // 未开始
} else if(activityData.value.state === 1) {
time = endTime - date // 进行中
}
let countDownInterval = setInterval(()=>{
countDown(time)
time -= 1000
if(time <= 0){
clearInterval(countDownInterval)
activityData.value.state ++
}
},1000)
}
function countDown (time) {
const fn = (v) => (v < 10 ? `0${v}` : v)
const t = parseInt(time / 1000)
const text = activityData.value.state === 0 ? '开始' : '结束'
const hour = parseInt(t / 3600)
const min = parseInt((t % 3600) / 60)
const s = t % 60
count.value = [text, fn(hour), fn(min), fn(s)]
}
return {
activityData,
productList,
count,
jumpProductDetail,
jumpSeckills
}
}

View File

@ -0,0 +1,76 @@
<template>
<div class="text warp" :class="['text-'+componentContent.textPos,{'show-more':componentContent.showMore},'terminal' + terminal]" :style="{backgroundColor:componentContent.bgColor}">
<div class="line-warp" :class="{'borderBot':componentContent.showLine}">
<h3 class="h3" :style="{fontSize:componentContent.fontSizeNum+'rpx',fontWeight:componentContent.textFontW,color:componentContent.titColor}">{{componentContent.title}}</h3>
<p class="p" :style="{fontSize:componentContent.describeSizeNum+'rpx',fontWeight:componentContent.describeFontW,color:componentContent.describeColor}">{{componentContent.describe}}</p>
<div class="btn-more" v-show="componentContent.showMore" :class="'style'+componentContent.styleValue" @click="jumpLink(item.linkObj)"><span>查看更多</span><i class="iconfont icon-arrow-right"></i></div>
</div>
</div>
</template>
<script setup>
import { toRefs } from 'vue';
const props = defineProps({
terminal: {
type: Number,
default: 4,
},
componentContent: {
type: Object,
default () {
return {};
}
},
})
const { terminal, componentContent } = toRefs(props)
</script>
<style lang="scss" scoped>
.text{
position: relative;
.line-warp{
padding: 10upx 0;
}
.borderBot{
border-bottom: 1upx solid #cccc;
}
.h3{
line-height: 1.5;
}
.p{
line-height: 1.5;
margin-top: 5upx;
}
.style1{
}
.style2{
.iconfont{
display: none;
}
}
.style3{
span{
display: none;
}
}
&.text-left{
text-align: left;
&.show-more{
position: relative;
padding-right: 20upx;
.btn-more{
position: absolute;
right: 0;
top: 10upx;
}
}
}
&.text-center{
text-align: center;
}
&.text-right{
text-align: right;
}
}
</style>

View File

@ -0,0 +1,97 @@
<template>
<div class="videoBox" :class="'terminal' + terminal">
<div class="videoLeftBox">
<h3 v-if="componentContent.title">{{componentContent.title}}</h3>
<div class="content" v-if="removeTags(componentContent.mainBody)" v-html="componentContent.mainBody"></div>
</div>
<div class="videoRightBox">
<video class="myVideo" id="myVideo" :poster="componentContent.coverImg" :src="componentContent.videoUrl" enable-danmu danmu-btn controls></video>
</div>
<div class="clear"></div>
</div>
</template>
<script setup>
import { toRefs, ref, onMounted } from 'vue';
const props = defineProps({
terminal: {
type: Number,
default: 4,
},
componentContent: {
type: Object,
default () {
return {};
}
},
})
const { terminal, componentContent } = toRefs(props)
const isPlay = ref(false)
const videoContext = ref(null)
onMounted(()=>{
videoContext.value = uni.createVideoContext('myVideo',this)
})
function handlePlayVideo(){
isPlay.value = true
setTimeout(()=>{
videoContext.value.play()
},500)
}
// 清除html标签
const removeTags = (html)=>{
return html.replace(/<[^>]*>/g, '');
}
</script>
<style lang="scss" scoped>
.videoBox {
margin: 0 35rpx;
display: flex;
justify-content: flex-start;
align-items: center;
.videoLeftBox {
h3 {
font-size: 28upx;
color: #333333;
margin-bottom: 10upx;
font-weight: normal;
}
p {
color: #333333;
font-size: 14upx;
line-height: 30upx;
}
}
.videoRightBox {
width: 50%;
video {
width: 100%;
}
}
.clear {
clear: both;
}
}
.terminal1,.terminal2,.terminal3{
&.videoBox{
display: block;
.videoLeftBox{
width: 100%;
text-align: center;
.content{
margin-bottom: 20px;
}
}
.videoRightBox {
width: 100%;
}
}
}
.myVideo{
aspect-ratio: 16/9;
height: auto;
}
</style>

View File

@ -0,0 +1,323 @@
<template>
<div class="vip" v-if="productData.length >0">
<div class="vip-card">
<div class="vip-title">
<!-- #ifdef MP-WEIXIN -->
<image class="title-img" src="https://ceres.zkthink.com/static/canvas-images/vip/img-title.png" alt="会员专区" mode="widthFix"/>
<!-- #endif -->
<!-- #ifdef H5 || APP-PLUS -->
<image class="title-img" src="https://ceres.zkthink.com/static/canvas-images/vip/img-title.png" alt="会员专区" mode="widthFix"/>
<!-- #endif -->
<a v-show="componentContent.showMore" class="btn-more a-link" @click="jumpVip">更多<i class="iconfont icon-arrow-right"></i></a>
</div>
<div>
<div v-if="productData.length > 2">
<swiper class="swiper vip-list" :circular="true" :indicator-dots="false" :autoplay="true" @change="swiperChange">
<swiper-item class="vip-item-warp" v-for="(itemJ,indexJ) in listTemp" :key="indexJ">
<div class="vip-item" v-for="(item,index) in itemJ" :key="index" @click="jumpProductDetail(item)">
<div class="vip-item-img">
<image class="img default-img" :src="item.image"></image>
</div>
<div class="vip-item-info">
<h3 class="name">
{{item.productName}}
</h3>
<div class="stock">
还剩{{item.stockNumber}}件
</div>
<div class="original-price">
¥ {{item.originalPrice}}
</div>
<div class="price-warp">
<div class="flag">
<!-- #ifdef MP-WEIXIN -->
<image src="https://ceres.zkthink.com/static/canvas-images/vip/flag-vip.png" alt="会员价" class="flagImg"/>
<!-- #endif -->
<!-- #ifdef H5 || APP-PLUS -->
<image class="flagImg" src="https://ceres.zkthink.com/static/canvas-images/vip/flag-vip.png" alt="会员专区" mode="widthFix"/>
<!-- #endif -->
</div>
<div class="price">
¥ {{item.price}}
</div>
</div>
<div class="btn-buy">
<span>去购买</span>
<div class="progress">
<i></i>
</div>
</div>
</div>
</div>
</swiper-item>
</swiper>
<view class="swiper-dots" v-if="listTemp && listTemp.length > 1">
<text class="dot" :class="swiperCurrent === index && 'dot-active'" v-for="(dot, index) in listTemp.length"
:key="index"></text>
</view>
</div>
<div class="swiper vip-list" v-else>
{{ productData.length }}
<div class="vip-item-warp" v-for="(itemJ,indexJ) in listTemp" :key="indexJ">
<div class="vip-item" v-for="(item,index) in itemJ" :key="index" @click="jumpProductDetail(item)">
<div class="vip-item-img">
<image class="img default-img" :src="item.image" />
</div>
<div class="vip-item-info">
<h3 class="name">
{{item.productName}}
</h3>
<div class="stock">
还剩{{item.stockNumber}}
</div>
<div class="original-price">
¥ {{item.originalPrice}}
</div>
<div class="price-warp">
<div class="flag">
<!-- #ifdef MP-WEIXIN -->
<image src="https://ceres.zkthink.com/static/canvas-images/vip/flag-vip.png" alt="会员价" class="flagImg"/>
<!-- #endif -->
<!-- #ifdef H5 || APP-PLUS -->
<image class="flagImg" src="https://ceres.zkthink.com/static/canvas-images/vip/flag-vip.png" alt="会员专区" mode="widthFix"/>
<!-- #endif -->
</div>
<div class="price">
¥ {{item.price}}
</div>
</div>
<div class="btn-buy">
<span>去购买</span>
<div class="progress">
<i></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- <div class="pagination vip-pagination"></div>-->
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, toRefs } from 'vue';
import vipMixin from '../mixin'
const { productData, jumpVip, jumpProductDetail } = vipMixin()
const props = defineProps({
componentContent: {
type: Object,
default () {
return {};
}
}
})
const { componentContent } = toRefs(props)
const swiperCurrent = ref(0)
function swiperChange(e) {
swiperCurrent.value = e.detail.current;
}
const listTemp = computed(() => {
var list = productData.value
var arrTemp = []
var index = -1
var count = 2 // 每组多少条
for (var i = 0; i < list.length; i++) {
if (i % count === 0) {
arrTemp.push([])
index++
}
arrTemp[index].push(list[i])
}
return arrTemp
})
</script>
<style lang="scss" scoped>
.vip{
&-card{
background: #333333;
padding: 0 20upx 20upx;
position: relative;
}
&-title{
padding: 42upx 0 28upx 30upx;
position: relative;
.title-img{
display: block;
width: 197upx;
}
.btn-more{
position: absolute;
right: 8upx;
top: 40upx;
line-height: 33upx;
padding-right: 25upx;
font-size: 24upx;
color: #FFEBC4;
.iconfont{
content: '';
font-size: 26upx;
position: absolute;
right: 0;
top: 0;
}
}
}
&-list{
height: 562upx;
}
&-item{
display: flex;
background-color: #fff;
margin-top: 20upx;
&:first-child{
margin-top: 0upx;
}
&-img{
width: 260upx;
height: 260upx;
margin-right: 20upx;
background-color: #ececec;
.img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}
}
&-info{
flex: 1;
position: relative;
.name{
font-size: 28upx;
//height: 75rpx;
line-height: 40upx;
color: #333333;
margin: 30upx 0 10upx;
overflow:hidden;
text-overflow:ellipsis;
display:-webkit-box;
-webkit-box-orient:vertical;
-webkit-line-clamp:1;
}
// #ifdef H5 || APP-PLUS
.name{
height: 40rpx;
}
// #endif
.stock{
color: #C5AA7B;
font-size: 20upx;
border: 2upx solid #E4E5E6;
line-height: 40upx;
padding: 0 5upx;
display: inline-block;
margin-bottom: 30upx;
}
.original-price{
font-size: 24upx;
line-height: 34upx;
color: #CCCCCC;
text-decoration: line-through;
}
.flag{
float: left;
padding: 12upx 10upx 0 0;
.flagImg {
width: 58upx;
height: 36upx;
display: block;
}
}
.price{
font-size: 40upx;
font-weight: bold;
line-height: 56upx;
color: #C83732;
}
.btn-buy{
position: absolute;
right: 20upx;
bottom: 20upx;
width: 160upx;
height: 84upx;
background: linear-gradient(90deg, #C83732 0%, #E25C44 100%);
box-shadow: 0px 6upx 12upx rgba(233, 0, 0, 0.3);
border-radius: 10upx;
text-align: center;
padding: 16upx 20upx 0;
span{
font-size: 24upx;
line-height: 34upx;
color: #FFFFFF;
margin-bottom: 10upx;
display: block;
}
.progress{
height: 8upx;
background: rgba(255,255,255,0.5);
border-radius: 4upx;
position: relative;
i{
position: absolute;
left: 0;
top: 0;
width: 50%;
height: 8upx;
background-color: #fff;
border-radius: 4upx;
}
}
}
}
}
.vip-item-info {
.price-warp {
display: flex;
align-items: center;
}
}
.swiper-dots {
display: flex;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 15upx;
z-index: 66;
.dot {
width: 24upx;
height: 4upx;
background: #fff;
opacity: 0.5;
border-radius: 2upx;
margin: 0 10upx;
}
.dot-active {
opacity: 1;
}
}
//.pagination{
// display: flex;
// justify-content: center;
// padding-top:20upx;
// ::v-deep .swiper-pagination-bullet{
// width: 24upx;
// height: 4upx;
// background: #fff;
// opacity: 0.5;
// border-radius: 2upx;
// margin: 0 5upx;
// }
// ::v-deep .swiper-pagination-bullet-active{
// opacity: 1;
// }
//}
}
</style>

View File

@ -0,0 +1,29 @@
import api from '../../config/api'
import { funMixin } from '../../config/mixin'
import { ref, onMounted } from 'vue';
const vipMixin = () => {
const { sendReq, jumpVip, jumpProductDetail } = funMixin()
const productData = ref([])
onMounted(() => {
getData()
})
function getData() {
sendReq({
url: `${api.getMemberProducts}?page=1&pageSize=20`,
method: 'GET'
}, (proRes) => {
productData.value = proRes.data.list
console.log(productData.value)
})
}
return {
productData,
jumpVip,
jumpProductDetail
}
}
export default vipMixin

View File

@ -0,0 +1,103 @@
<template>
<div class="layout hom-layout">
<div class="list-group-item"
v-for="(item,index) in componentsData"
:key="index">
<!-- <component :is="componentMap[terminal-1].get(item.type)" :componentContent="item.componentContent" :terminal="terminal" :typeId="typeId" :shopId="shopId"></component>-->
<com-header v-if="item.type==='header'" :componentContent="item.componentContent" :terminal="terminal"></com-header>
<com-banner v-if="item.type==='banner'" :componentContent="item.componentContent" :terminal="terminal"></com-banner>
<com-text v-if="item.type==='text'" :componentContent="item.componentContent" :terminal="terminal"></com-text>
<com-image-text v-if="item.type==='imageText'" :componentContent="item.componentContent" :terminal="terminal"></com-image-text>
<com-brand-list v-if="item.type==='brandList'" :componentContent="item.componentContent" :terminal="terminal"></com-brand-list>
<com-category-list v-if="item.type==='categoryList'" :componentContent="item.componentContent" :terminal="terminal"></com-category-list>
<com-image-text-list v-if="item.type==='imageTextList'" :componentContent="item.componentContent" :terminal="terminal"></com-image-text-list>
<com-assist-div v-if="item.type==='assistDiv'" :componentContent="item.componentContent" :terminal="terminal"></com-assist-div>
<com-image-text-nav v-if="item.type==='imageTextNav'" :componentContent="item.componentContent" :terminal="terminal"></com-image-text-nav>
<com-product v-if="item.type==='productList'" :componentContent="item.componentContent" :terminal="terminal"></com-product>
<com-video-box v-if="item.type==='videoBox'" :componentContent="item.componentContent" :terminal="terminal"></com-video-box>
<com-coupon v-if="item.type==='coupon'" :componentContent="item.componentContent" :terminal="terminal"></com-coupon>
<com-custom v-if="item.type==='custom'" :componentContent="item.componentContent" :terminal="terminal"></com-custom>
<com-notice v-if="item.type==='notice'" :componentContent="item.componentContent" :terminal="terminal"></com-notice>
<com-vip v-if="item.type==='vip'" :componentContent="item.componentContent" :terminal="terminal"></com-vip>
<com-group v-if="item.type==='groupList'" :terminal="terminal" :componentContent="item.componentContent"></com-group>
<com-discount v-if="item.type==='discountList'" :componentContent="item.componentContent" :terminal="terminal"></com-discount>
<com-spike v-if="item.type==='spikeList'" :componentContent="item.componentContent" :terminal="terminal"></com-spike>
<com-price v-if="item.type==='priceList'" :terminal="terminal"></com-price>
<com-new-product v-if="item.type==='newProduct'" :componentContent="item.componentContent" :terminal="terminal"></com-new-product>
<com-shop v-if="item.type==='shop'" :componentContent="item.componentContent" :terminal="terminal"></com-shop>
</div>
</div>
</template>
<script setup>
import { onLoad } from '@dcloudio/uni-app'
import comHeader from '@/components/canvasShow/basics/header/app/index.vue'
import comBanner from '@/components/canvasShow/basics/banner.vue'
import comText from '@/components/canvasShow/basics/text.vue'
import comImageText from '@/components/canvasShow/basics/imageText.vue'
import comBrandList from '@/components/canvasShow/basics/brandList.vue'
import comCategoryList from '@/components/canvasShow/basics/categoryList.vue'
import comImageTextList from '@/components/canvasShow/basics/imageTextList.vue'
import comAssistDiv from '@/components/canvasShow/basics/assistDiv.vue'
import comImageTextNav from '@/components/canvasShow/basics/imageTextNav.vue'
import comProduct from '@/components/canvasShow/basics/product/app/index.vue'
import comVideoBox from '@/components/canvasShow/basics/video.vue'
import comCoupon from '@/components/canvasShow/basics/coupon/index.vue'
import comCustom from '@/components/canvasShow/basics/custom.vue'
import comNotice from '@/components/canvasShow/basics/notice.vue'
import comVip from '@/components/canvasShow/basics/vip/app/index.vue'
import comGroup from '@/components/canvasShow/basics/group/app/index.vue'
import comDiscount from '@/components/canvasShow/basics/discount/app/index.vue'
import comSpike from '@/components/canvasShow/basics/spike/app/index.vue'
import comPrice from '@/components/canvasShow/basics/price/app/index.vue'
import comNewProduct from '@/components/canvasShow/basics/newProduct/app/index.vue'
import comShop from '@/components/canvasShow/basics/shop.vue'
import config from '@/components/canvasShow/config/config'
import sendReqMixin from './config/mixin/sendReqMixin'
import { ref } from 'vue';
import api from "@/components/canvasShow/config/api";
const terminal = ref(config.terminal)
const { sendReq } = sendReqMixin()
const componentsData = ref([])
const getCanvasData = ()=>{
const params = {
method: 'GET',
url: `${api.getCanvas}?terminal=${terminal.value}`,
}
sendReq(params, (res) => {
if (res.data.json) {
componentsData.value = JSON.parse(res.data.json)
}
})
}
onLoad(() => {
getCanvasData()
})
</script>
<style lang="scss"
scoped>
.hom-layout {
width: 100%;
overflow: hidden;
}
</style>
<style lang="scss">
.warp {
width: 680upx;
margin: 0 auto;
max-width: 100%;
&.terminal4 {
width: 1200px;
max-width: 100%;
}
}
.flex-box {
display: flex;
}
</style>

View File

@ -0,0 +1,102 @@
export const componentMap = [
// 小程序
new Map([
['header', () => import('./basics/header/app/index.vue')], // 首页头部
['banner', () => import('./basics/banner.vue')], // 轮播图
['text', () => import('./basics/text.vue')], // 文本
['imageText', () => import('./basics/imageText.vue')], // 图文
['brandList', () => import('./basics/brandList.vue')], // 品牌列表
['categoryList', () => import('./basics/categoryList.vue')], // 品牌列表
['imageTextList', () => import('./basics/imageTextList.vue')], // 图文列表
['assistDiv', () => import('./basics/assistDiv.vue')], // 铺助分割
['imageTextNav', () => import('./basics/imageTextNav.vue')], // 图文导航
['productList', () => import('./basics/product/app/index.vue')], // 商品列表
['videoBox', () => import('./basics/video.vue')], // 视频
['coupon', () => import('./basics/coupon/index.vue')], // 优惠券
['custom', () => import('./basics/custom.vue')], // 自定义
['notice', () => import('./basics/notice.vue')], // 公告
['vip', () => import('./basics/vip/app/index.vue')], // 会员专区
['groupList', () => import('./basics/group/app/index.vue')], // 拼团专区
['spikeList', () => import('./basics/spike/app/index.vue')], // 秒杀专区
['priceList', () => import('./basics/price/app/index.vue')], // 定价捆绑
['discountList', () => import('./basics/discount/app/index.vue')], // 限时折扣
['newProduct', () => import('./basics/newProduct/app/index.vue')], // 每日上新
['live', () => import('./basics/live/app/index.vue')], // 直播
['shop', () => import('./basics/shop.vue')], // 每日好店
]),
// H5
new Map([
['header', () => import('./basics/header/app/index.vue')], // 首页头部
['banner', () => import('./basics/banner.vue')], // 轮播图
['text', () => import('./basics/text.vue')], // 文本
['imageText', () => import('./basics/imageText.vue')], // 图文
['brandList', () => import('./basics/brandList.vue')], // 品牌列表
['categoryList', () => import('./basics/categoryList.vue')], // 品牌列表
['imageTextList', () => import('./basics/imageTextList.vue')], // 图文列表
['assistDiv', () => import('./basics/assistDiv.vue')], // 铺助分割
['imageTextNav', () => import('./basics/imageTextNav.vue')], // 图文导航
['productList', () => import('./basics/product/app/index.vue')], // 商品列表
['videoBox', () => import('./basics/video.vue')], // 视频
['coupon', () => import('./basics/coupon/app/index.vue')], // 优惠券
['custom', () => import('./basics/custom.vue')], // 自定义
['notice', () => import('./basics/notice.vue')], // 公告
['vip', () => import('./basics/vip/app/index.vue')], // 会员专区
['groupList', () => import('./basics/group/app/index.vue')], // 拼团专区
['spikeList', () => import('./basics/spike/app/index.vue')], // 秒杀专区
['priceList', () => import('./basics/price/app/index.vue')], // 定价捆绑
['discountList', () => import('./basics/discount/app/index.vue')], // 限时折扣
['newProduct', () => import('./basics/newProduct/app/index.vue')], // 每日上新
['live', () => import('./basics/live/app/index.vue')], // 直播
['shop', () => import('./basics/shop.vue')], // 每日好店
]),
// APP
new Map([
['header', () => import('./basics/header/app/index.vue')], // 首页头部
['banner', () => import('./basics/banner.vue')], // 轮播图
['text', () => import('./basics/text.vue')], // 文本
['imageText', () => import('./basics/imageText.vue')], // 图文
['brandList', () => import('./basics/brandList.vue')], // 品牌列表
['categoryList', () => import('./basics/categoryList.vue')], // 品牌列表
['imageTextList', () => import('./basics/imageTextList.vue')], // 图文列表
['assistDiv', () => import('./basics/assistDiv.vue')], // 铺助分割
['imageTextNav', () => import('./basics/imageTextNav.vue')], // 图文导航
['productList', () => import('./basics/product/app/index.vue')], // 商品列表
['videoBox', () => import('./basics/video.vue')], // 视频
['coupon', () => import('./basics/coupon/app/index.vue')], // 优惠券
['custom', () => import('./basics/custom.vue')], // 自定义
['notice', () => import('./basics/notice.vue')], // 公告
['vip', () => import('./basics/vip/app/index.vue')], // 会员专区
['groupList', () => import('./basics/group/app/index.vue')], // 拼团专区
['spikeList', () => import('./basics/spike/app/index.vue')], // 秒杀专区
['priceList', () => import('./basics/price/app/index.vue')], // 定价捆绑
['discountList', () => import('./basics/discount/app/index.vue')], // 限时折扣
['newProduct', () => import('./basics/newProduct/app/index.vue')], // 每日上新
['live', () => import('./basics/live/app/index.vue')], // 直播
['shop', () => import('./basics/shop.vue')], // 每日好店
]),
// PC
new Map([
['header', () => import('./basics/header/pc/index.vue')], // 首页头部
['banner', () => import('./basics/banner.vue')], // 轮播图
['text', () => import('./basics/text.vue')], // 文本
['imageText', () => import('./basics/imageText.vue')], // 图文
['brandList', () => import('./basics/brandList.vue')], // 品牌列表
['categoryList', () => import('./basics/categoryList.vue')], // 品牌列表
['imageTextList', () => import('./basics/imageTextList.vue')], // 图文列表
['assistDiv', () => import('./basics/assistDiv.vue')], // 铺助分割
['imageTextNav', () => import('./basics/imageTextNav.vue')], // 图文导航
['productList', () => import('./basics/product/pc/index.vue')], // 商品列表
['videoBox', () => import('./basics/video.vue')], // 视频
['coupon', () => import('./basics/coupon/pc/index.vue')], // 优惠券
['custom', () => import('./basics/custom.vue')], // 自定义
['notice', () => import('./basics/notice.vue')], // 公告
['vip', () => import('./basics/vip/pc/index.vue')], // 会员专区
['groupList', () => import('./basics/group/pc/index.vue')], // 拼团专区
['spikeList', () => import('./basics/spike/pc/index.vue')], // 秒杀专区
['priceList', () => import('./basics/price/pc/index.vue')], // 定价捆绑
['discountList', () => import('./basics/discount/pc/index.vue')], // 限时折扣
['newProduct', () => import('./basics/newProduct/app/index.vue')], // 每日上新
['shop', () => import('./basics/shop.vue')], // 每日好店
]),
]
export default componentMap

View File

@ -0,0 +1,19 @@
// 导入api接口模块
import { VUE_APP_API_URL } from '@/config'
// 获取当前环境变量 true => 生产环境 false => 开发环境
// const BASEURL = process.env.VUE_APP_DOMAIN_PREFIX
const BASEURL = VUE_APP_API_URL
// const BASEURL = (process.env.NODE_ENV === 'production') ? 'http://127.0.0.1:9007' : 'http://127.0.0.1:9007'
export const api = {
// 画布模块
getCanvas: BASEURL + '/shop/canvas/canvas-json', // 读取画布
getProducts: BASEURL + '/product/canvas/page', // 选择商品查询
getCoupons: BASEURL + '/product/coupon/canvas-list', // 优惠券查询
takeCoupon: BASEURL + '/product/coupon/relation/receive/', // 领取优惠券
getActivityProduct: `${BASEURL}/product/campaign/product-page`, // 获取活动商品
getActivity: `${BASEURL}/product/campaign/get`, // 获取单个活动
}
export default api

View File

@ -0,0 +1,24 @@
// 画布配置
// import Cookies from 'js-cookie'
const config = {
terminal: 4, // 画布设备 1 小程序2 H53 App 4 电脑
typeId: 0, // 页面类型 0 C端 1 平台画布2 自定义页面3 商家店铺装修
getToken: function(){
return uni.getStorageSync('storage_key').token
}
}
// #ifdef H5
config.terminal = 2
// #endif
// #ifdef APP-PLUS || APP-NVUE
config.terminal = 3
// #endif
// #ifdef MP
config.terminal = 1
// #endif
export default config

View File

@ -0,0 +1,196 @@
import sendReqMixin from './sendReqMixin'
import { useRouter } from "@/hooks/useRouter";
export default function () {
const {sendReq} = sendReqMixin()
const {push} = useRouter()
// 请求数据前 请求完再显示所有组件
function beforeGetData() {
if (typeof uni !== 'undefined') {
uni.getStorage({
key: 'sendNum',
success: function (res) {
const sendNum = res.data
uni.setStorage({key: 'sendNum', data: parseInt(sendNum) + 1})
},
})
} else {
const sendNum = localStorage.getItem('sendNum')
localStorage.setItem('sendNum', parseInt(sendNum) + 1)
}
}
// 请求数据后
function afterGetData() {
if (typeof uni !== 'undefined') {
uni.getStorage({
key: 'sendNum',
success: function (res) {
const sendNum = res.data
uni.setStorage({key: 'sendNum', data: parseInt(sendNum) - 1})
},
})
} else {
const sendNum = localStorage.getItem('sendNum')
localStorage.setItem('sendNum', parseInt(sendNum) - 1)
}
}
// 判断url
function jumpLink(linkObj) {
console.log(linkObj, 'linkObj')
var link = ''
if (linkObj?.typeText) {
switch (linkObj.typeText) {
case '类别':
jumpCategory(linkObj.data.id)
break
case '店辅':
jumpStore(linkObj.data)
break
case '商品':
jumpProductDetail(linkObj.data)
break
case '自定义':
// router.push("/category");
case '公告':
jumpNoticeDetail(linkObj.data)
break
case '产品':
jumpProList()
break
case '秒杀':
jumpSeckills()
break
case '拼团':
jumpGroupWorks()
break
case '折扣':
jumpDiscount()
break
}
} else if (linkObj?.selsectValue === '/index') {
uni.navigateTo({
url: `/root/index/index`
})
}
return link
}
// 跳转到搜索
function toSearch(key) {
push({
url: '/pages/search/search'
}, {
data: {key}
})
}
// 跳转到类别主页
function jumpCategory(id) {
push({
url: '/pages/goodsList/goodsList'
}, {
data: {sid: id}
})
}
// 跳转到产品列表
function jumpProList(item) {
push({
url: '/pages/goodsList/goodsList'
})
}
// 跳转到店铺主页
function jumpStore(item) {
uni.navigateTo({
url: `/pages_category_page1/store/index?storeId=${ item.shopId }`
})
}
// 跳转到商品详情
function jumpProductDetail(item) {
push({url: '/pages/goodsDetail/goodsDetail'}, {data: {id:item.id,skuId:item.skuId}})
}
// 跳转到秒杀专区
function jumpSeckills() {
push({url: '/pages/seckilling/seckilling'})
}
// 跳转到拼团专区
function jumpGroupWorks() {
push({url: '/pages/groupBuy/groupBuy'})
}
// 跳转到折扣专区
function jumpDiscount() {
push({url: '/pages/discount/discount'})
}
// 跳转到会员专区
function jumpVip() {
uni.navigateTo({
url: '/pages_category_page1/memberCenter/activityList',
success: res => {
}, fail: () => {
}, complete: () => {
}
})
}
// 跳转组合支付
function jumpCombination(item) {
if (item.priceId) {
uni.navigateTo({
url: '/pages_category_page1/goodsModule/combination?priceId=' + item.priceId
})
} else {
uni.showToast({
title: '暂无活动',
icon: "none"
});
}
}
// 跳转到公告详情
function jumpNoticeDetail(item) {
uni.navigateTo({
url: '/pages_category_page2/userModule/messageDetail?noticeId=' + item.noticeId
})
}
// 跳转到直播列表
function jumpLive() {
uni.navigateTo({
url: '/pages_category_page2/livePage/index'
})
}
// 加入购物车
function addCart(id) {
console.log(id)
}
return {
beforeGetData,
afterGetData,
toSearch,
sendReq,
jumpLink,
jumpCategory,
jumpStore,
jumpProductDetail,
jumpSeckills,
jumpGroupWorks,
jumpDiscount,
jumpVip,
jumpNoticeDetail,
addCart,
jumpProList,
jumpLive,
jumpCombination
}
}

View File

@ -0,0 +1,4 @@
import fun from './funMixin.js'
import sendReq from './sendReqMixin.js'
export const funMixin = fun
export const sendReqMixin = sendReq

View File

@ -0,0 +1,25 @@
import { ref } from 'vue';
import request from './server'
export default function () {
const loading = ref(false)
/*
* 发送请求
*/
function sendReq (params, callback) {
request(params.url, params.data || {},params.method || 'POST',{
'Content-type': params.contentType || 'application/json;charset=utf-8'
}).then((res) => {
if (res && res.data) {
callback && callback(res.data)
}
}, (error) => {
console.log(error)
})
}
return {
loading,
sendReq
}
}

View File

@ -0,0 +1,234 @@
// 引入axios
// import router from './../../router'
// import * as Vue from 'vue'
// import promise from 'es6-promise'
import axios from 'axios'
import canvasConfig from '../config'
import cookie from '@/utils/cookie'
// import Cookies from 'js-cookie'
// import localStorage from '../storage/localStorage'
// promise.polyfill()
/*
const service = axios.create({
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
withCredentials: true,
timeout: 20000 // 请求超时 20s
})
// 请求拦截器
service.interceptors.request.use(config => {
// 是否为当前的请求加上请求头 token
const token = canvasConfig.getToken()
if (token) {
if(canvasConfig.typeId === 1){
config.headers['Authorization-admin'] = token
} else if(canvasConfig.typeId === 3){
config.headers['Authorization-business'] = token
} else {
config.headers['Authorization'] = token
}
}
return config
}, error => {
return Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(
(response) => {
console.log(response)
if (response.data.code && response.data.code !=='200' && response.data.message) {
// Vue.prototype.$message.error(response.data.message)
uni.showToast({
title:response.data.message,
icon:"none"
})
}
return response
},
err => {
console.log(err)
// 失败响应
if (err && err.response) {
switch (err.response.status) {
case 400:
err.message = '请求无效,请检查参数是否正确!'
break
case 401:
err.message = '未经授权,访问被拒!'
break
case 403:
err.message = '拒绝访问!'
break
case 404:
err.message = `地址不存在!`
break
case 408:
err.message = '请求超时!'
break
case 500:
err.message = '系统错误!'
break
case 501:
err.message = '该方法未实现!'
break
case 502:
err.message = '网关出错!'
break
case 503:
err.message = '服务不可用!'
break
case 504:
err.message = '网关请求超时'
break
case 505:
err.message = 'HTTP版本不受支持'
break
default:
}
if (err.response.data.error) {
err.message = err.response.data.error
}
// Vue.prototype.$message.closeAll()
// Vue.prototype.$message.error(err.message)
uni.showToast({
title:err.message,
icon:"none"
})
// router.push({name: 'error', params: {message: err.message, status: err.response.status}})
}
}
)
*/
const request = (url,data,method='GET',header={})=>new Promise((resolve, reject)=>{
// 是否为当前的请求加上请求头 token
const token = cookie.get('accessToken')
const headers = {...header}
if (token.accessToken) {
headers['Authorization'] = 'Bearer ' + token.accessToken
}
uni.request({
url: url,
data: data,
method: method,
header: headers,
success: response => {
if (response.data.code && response.data.code !=='200' && response.data.message) {
uni.showToast({
title:response.data.message,
icon:"none"
})
}else{
resolve(response)
}
},
fail: err => {
// 失败响应
if (err && err.response) {
switch (err.response.status) {
case 400:
err.message = '请求无效,请检查参数是否正确!'
break
case 401:
err.message = '未经授权,访问被拒!'
break
case 403:
err.message = '拒绝访问!'
break
case 404:
err.message = `地址不存在!`
break
case 408:
err.message = '请求超时!'
break
case 500:
err.message = '系统错误!'
break
case 501:
err.message = '该方法未实现!'
break
case 502:
err.message = '网关出错!'
break
case 503:
err.message = '服务不可用!'
break
case 504:
err.message = '网关请求超时'
break
case 505:
err.message = 'HTTP版本不受支持'
break
default:
}
if (err.response.data.error) {
err.message = err.response.data.error
}
// Vue.prototype.$message.closeAll()
// Vue.prototype.$message.error(err.message)
uni.showToast({
title:err.message,
icon:"none"
})
reject(err)
// router.push({name: 'error', params: {message: err.message, status: err.response.status}})
}
},
})
})
//真机获取
// service.defaults.adapter = function (config) {
// return new Promise((resolve, reject) => {
// console.log(config)
// var settle = require('axios/lib/core/settle');
// var buildURL = require('axios/lib/helpers/buildURL');
// uni.request({
// method: config.method.toUpperCase(),
// url: buildURL(config.url, config.params, config.paramsSerializer),
// header: config.headers,
// data: config.data,
// dataType: config.dataType,
// responseType: config.responseType,
// sslVerify: config.sslVerify,
// complete:function complete(response){
// response = {
// data: response.data,
// status: response.statusCode,
// errMsg: response.errMsg,
// header: response.header,
// config: config
// };
//
// settle(resolve, reject, response);
// }
// })
// })
// }
export default request