SFH5/components/coupon.vue
2026-03-16 11:10:28 +08:00

820 lines
23 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="coupon" :class="[`${themeInfo.theme}-theme`, `${themeInfo.language}`]">
<myPopup v-model="modelValue" mode='bottom' bgColor='none' customStyle='height:70vh;border-radius: 18px 18px 0 0;' :closeOnClickOverlay='true'>
<view class="couponContent">
<view class="header">
<view class="line"></view>
<view class="title">
{{ $t('coupon.coupon') }} {{dataList.length + unusableDataList.length}}
</view>
</view>
<view class="content">
<view class="ortherCoupon" v-if="isKingKong && props.siteData?.siteId">
<view class="" v-if="storeState.userInfo?.phone">
<uv-divider :text="$t('coupon.meituanOrdazhongdianpingCoupon')"></uv-divider>
</view>
<view v-else>
<button @click="bindPhonePopup.open()"> {{ $t('coupon.queryMeituanDazhongdianpingCoupon') }} </button>
</view>
</view>
<view class="couponBox" v-if="isKingKong">
<view class="couponUl couponUl2">
<view v-if="state.ortherCouponLoading" style="display: flex;justify-content: center;">
<uv-loading-icon></uv-loading-icon>
</view>
<uv-checkbox-group style="width: 100%;" v-model="state.checkboxOrtherCouponValue" @change="ortherCouponChange">
<template v-for="(item,index) in state.ortherCoupon" :key="index">
<view class="couponLi">
<view class="left">
<view class="name">
{{ item.title }}
</view>
<view class="desc">
{{ $t('coupon.validityPeriod') }}{{ item.endTime.substr(0,10) }}
</view>
</view>
<view class="right">
<view style="display: flex;justify-content: center;margin-top: 4px;">
<uv-checkbox
:key="index"
:name="item.number"
@click.stop=""
></uv-checkbox>
<!-- <button class="btn" 0">
{{ $t('coupon.apply') }}
</button> -->
</view>
</view>
</view>
</template>
</uv-checkbox-group>
</view>
<uv-divider></uv-divider>
</view>
<view class="couponBox">
<view class="couponUl">
<view class="couponLi">
<view class="left">
<view class="name">
{{ $t('coupon.coupon') }}
</view>
<view class="desc">
{{ $t('coupon.useTips') }}
</view>
<view class="input">
<input v-model="couponCode" :placeholder="$t('coupon.enterCode')"></input>
</view>
</view>
<view class="right">
<view class="desc">
{{ $t('coupon.storewide') }}
</view>
<view class="desc">
{{ $t('coupon.limitedtimeoffer') }}
</view>
<view>
<button class="btn" @click="getCouponCode">
{{ $t("coupon.redeemNow") }}
</button>
</view>
</view>
</view>
</view>
<view class="couponUl couponUl2">
<uv-checkbox-group style="width: 100%;" v-model="state.checkboxCouponValue" @change="couponChange">
<view class="couponLi" v-for="(item,index) in dataList" :key="index" @click="chooseCoupon(item)" :style="{opacity:disabledFunc(item)?0.5:1}">
<view class="left">
<view class="name">
{{ item.couponCode }} {{ couponTitle(item) }}
<!-- <text style="letter-spacing: 0;">{{ item.discountLimit }}</text> -->
</view>
<view class="desc">
{{ $t('coupon.validityPeriod') }}{{ item.startDate.substr(0,10) }} ~ {{ item.endDate.substr(0,10)}}
</view>
<view class="desc">
{{ $t('coupon.instructions') }}{{ couponDesc(item) }} {{ item.siteName.length ?`${item.siteName.toString()}`: $t('coupon.storewide')}} {{ item.renewUsable?$t('coupon.renewable'):$t('coupon.noRenewable') }} {{ item.unitTypeName.length ? `(${item.unitTypeName.toString()})` : `(${$t("coupon.all")})`}}
</view>
</view>
<view class="right">
<view class="desc">
{{ item.siteName.length ?`${item.siteName.length>1?$t('coupon.multiStoreUse'):item.siteName[0]}`: $t('coupon.storewide') }}
</view>
<view class="desc">
{{ $t('coupon.limitedtimeoffer') }}
</view>
<view style="display: flex;justify-content: center;margin-top: 4px;">
<uv-checkbox
v-if="props.siteData?.lockerId"
:key="index"
:name="item.couponDispositionId"
:disabled="disabledFunc(item)"
@click.stop=""
></uv-checkbox>
<!-- <button class="btn" :disabled="props.disabledFunc(item)" @click="chooseCoupon(item)">
{{ $t('coupon.apply') }}
</button> -->
</view>
</view>
</view>
</uv-checkbox-group>
</view>
</view>
<view class="unusableCoupons couponUl">
<view v-if="unusableDataList.length" @click="state.showUnusableCoupons = !state.showUnusableCoupons" class="showUnusableCoupons">显示不可用优惠卷>></view>
<uv-divider v-if="unusableDataList.length && state.showUnusableCoupons" :text="$t('coupon.unusableCoupons')"></uv-divider>
<view v-show="state.showUnusableCoupons" class="couponLi" v-for="(item,index) in unusableDataList" :key="index">
<view class="left">
<view class="name">
{{ item.couponCode }} {{ couponTitle(item) }}
<!-- <text style="letter-spacing: 0;">{{ item.discountLimit }}</text> -->
</view>
<view class="desc">
{{ $t('coupon.validityPeriod') }}{{ item.startDate.substr(0,10) }} ~ {{ item.endDate.substr(0,10)}}
</view>
<view class="desc">
{{ $t('coupon.instructions') }}{{ couponDesc(item) }} {{ item.siteName.length ?`${item.siteName.toString()}`: $t('coupon.storewide')}} {{ item.renewUsable?$t('coupon.renewable'):$t('coupon.noRenewable') }} {{ item.unitTypeName.length ? `(${item.unitTypeName.toString()})` : `(${$t("coupon.all")})`}}
</view>
</view>
<view class="right">
<view class="desc">
{{ item.siteName.length ?`${item.siteName.length>1?$t('coupon.multiStoreUse'):item.siteName[0]}`: $t('coupon.storewide') }}
</view>
<view class="desc">
{{ $t('coupon.limitedtimeoffer') }}
</view>
<view>
<button class="btn" :disabled="true">
{{ $t('coupon.apply') }}
</button>
</view>
</view>
</view>
</view>
</view>
<view class="confirm" v-if="props.siteData?.lockerId">
<!-- @click="submit" -->
<button class="next-btn" @click="confirm">
{{ $t("common.confirm") }}
</button>
</view>
</view>
</myPopup>
<uv-popup
ref="bindPhonePopup"
customStyle="width: 80%; height: 400rpx; padding: 50rpx 0; border-radius: 32rpx; display: flex; flex-direction: column; justify-content: center; align-items: center;"
>
<text>{{ $t("common.bindPhone") }}</text>
<text style="padding: 0 40rpx; margin-top: 20rpx; text-align: center;"> {{ $t("common.bindPhoneUnlock") }}</text>
<!-- #ifdef MP-WEIXIN -->
<button
style="width: 80%; margin-top: 20px; background: #5BBC6B; color: #FFFFFF; line-height: 80rpx;"
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
>
{{ $t("common.QuickBind") }}
</button>
<!-- #endif -->
</uv-popup>
</view>
</template>
<script setup>
import myPopup from './myPopup.vue';
import { ref,computed,watch } from 'vue';
import { couponApi } from '@/Apis/coupon.js';
import { useSiteApi } from "@/Apis/site.js";
import { AppId } from '@/config/index.js'
// 国际化配置
import { useI18n } from 'vue-i18n';
import { useMainStore } from "@/store/index.js";
import { useLoginApi } from "@/Apis/login.js";
import { useOrderApi } from "@/Apis/order.js";
const { t } = useI18n();
const getOrderApi = useOrderApi();
const getApi = couponApi();
const getSiteApi = useSiteApi();
const popup = ref();
const { themeInfo,storeState } = useMainStore();
const getLoginApi = useLoginApi();
// 接收 v-model
const props = defineProps({
modelValue:{
type:Boolean,
default:false
},
disabledFunc:{
type:Function,
default: ()=> false,
},
siteData:{
type:Object,
default:()=>{}
},
priceData:{
type:Object,
default:()=>{}
},
month:{
type:Number,
default:24
},
couponItem:{
type:Array,
default:()=>[]
},
couponOtherItem:{
type:Array,
default:()=>[]
},
isRenew:{
type:Boolean,
default:false
}
});
const isKingKong = (AppId === 'wxb20921dfdd0b94f4' || AppId === 'wx3c4ab696101d77d1') // 是否是金刚 如果是金刚则显示美团和大众点评优惠卷
const bindPhonePopup = ref()
const couponCode = ref('');
const dataList = ref([]);
const unusableDataList = ref([]); // 不可用的优惠卷
const state = ref({
ortherCoupon:[],
ortherCouponLoading:false,
showUnusableCoupons: false,
checkboxCouponValue: [],
checkboxOrtherCouponValue: [], // 其他优惠卷
canComfirm: false,
})
const isShowOrtherCoupon = (item)=>{
return !props.couponOtherItem.map(x=>x.number).includes(item.number)
}
const emit = defineEmits(['close', 'confirm', 'update:modelValue','chooseCoupon']);
const modelValue = computed({
get() {
return props.modelValue;
},
set(value) {
emit('update:modelValue', value);
}
})
// watch(() => modelValue.value, (value) => {
// state.value.ortherCoupon = []
// value && getDataList()
// })
watch(() => storeState.token, (value) => {
state.value.ortherCoupon = []
value && getDataList()
})
const open = ()=>{
modelValue.value = true;
state.value.checkboxCouponValue = props.couponItem.map(x=>x.couponDispositionId);
state.value.checkboxOrtherCouponValue = props.couponOtherItem.map(x=>x.number);
getDataList();
}
const close = ()=>{
modelValue.value = false;
}
// 根据优惠卷类型 返回优惠卷 作用范围 优惠多少钱 打多少折 首页多少钱
const couponTitle = (item)=>{
let detail = ''
switch(item.couponType){
case 1:
detail = `${t('discountMomey',{discount:item.discountLimit})}`
break
case 2:
detail = `${t('couponDiscount',{percent: 100 - item.discountRange * 100,discount: (item.discountRange * 100) / 10,})}`
break
case 3:
detail = `${t('firstMonthRent',{discount:item.firstMonthAmount})}`
break
case 4:
detail = `${t('couponDiscount',{percent: 100 - item.monthDiscount * 100,discount: (item.monthDiscount * 100) / 10})}`
break
case 5:
detail = `${t('freeMonth',{discount:item.freeMonth})}`
break
case 6:
detail = `${t('BonusMonth',{discount:item.freeMonth})}`
break
default:
detail = `${t('discountMomey',{discount:item.discountLimit})}`
break
}
return `${detail}`
}
const couponDesc = (item)=>{
let detail = ''
switch(item.couponType){
case 1:
detail = `${t('requiredMomey',{momey: item.satisfyAmount})}`
break
case 2:
detail = `${t('requiredMomey',{momey: item.satisfyAmount})}`
break
case 3:
detail = `${t('fullMonths',{count: item.fullMonth})}`
break
case 4:
detail = `${t('fullMonths',{count: item.fullMonth})}`
break
case 5:
detail = `${t('fullMonths',{count: item.fullMonth})}`
break
case 6:
detail = `${t('fullMonths',{count: item.fullMonth})}`
break
default:
detail = `${t('requiredMomey',{momey: item.satisfyAmount})}`
break
}
return `${detail}`
}
// 区分可用和不可用优惠卷
function splitUnitTypeIdsByCoupon(coupon, siteId, unitTypeId) {
const usable = []
const unusable = []
coupon.forEach((item) => {
const siteUsable = item.siteIds.length === 0 || item.siteIds.includes(siteId)
const unitUsable = item.unitTypeIds.length === 0 || item.unitTypeIds.includes(unitTypeId)
if (siteUsable && unitUsable) {
usable.push(item)
} else {
unusable.push(item)
}
})
return { usable, unusable }
}
const getCouponCode = ()=>{
if(couponCode.value !== ''){
uni.showLoading({
mark: true
})
getApi.DrawDownCoupon({couponCode:couponCode.value}).then(res=>{
if(res.code == 200){
uni.showToast({
title: t('coupon.redemptionuccessful'),
icon: 'none'
})
setTimeout(()=>{
getDataList()
},1000)
}else{
uni.showToast({
title: res.msg,
icon: 'none'
})
}
}).finally(()=>{
uni.hideLoading()
})
}
}
const getDataList = ()=>{
uni.showLoading()
getApi.GetCouponList({pageIndex:1,pageSize:10000}).then(res=>{
uni.hideLoading()
if(res.code == 200){
dataList.value = res.data.result;
res.data.result.forEach(item=>{
item.startDate = item.startDate.replace(/-/g,'/')
item.endDate = item.endDate.replace(/-/g,'/')
})
if(props.siteData?.siteId){
const { usable, unusable} = splitUnitTypeIdsByCoupon(res.data.result, props.siteData?.siteId, props.siteData?.unitTypeId)
dataList.value = usable;
unusableDataList.value = unusable;
}else{
dataList.value = res.data.result;
unusableDataList.value = [];
}
getOtherCoupon()
}else{
setTimeout(()=>{
modelValue.value = false;
},500)
}
}).finally(()=>{
uni.hideLoading()
})
}
const chooseCoupon = (item)=>{
if(disabledFunc(item)){
uni.showToast({
title: t('coupon.currentConditionsNotMet'),
icon: 'none'
})
return
}
if(state.value.checkboxCouponValue.includes(item.couponDispositionId)){
state.value.checkboxCouponValue = state.value.checkboxCouponValue.filter(x=>x !== item.couponDispositionId)
}else{
state.value.checkboxCouponValue.push(item.couponDispositionId)
}
if(!props.siteData){
emit('chooseCoupon',item)
modelValue.value = false;
}
}
const chooseOtherCoupon = (item)=>{
emit('chooseOtherCoupon',item)
modelValue.value = false;
}
// 获取其他(美团)优惠卷
const getOtherCoupon = ()=>{
const phone = storeState.userInfo?.phone;
const siteId = props.siteData?.siteId;
if(siteId && phone && isKingKong){
state.value.ortherCouponLoading = true;
getApi.GetMeiTuanCodeByPhone({phone,siteId}).then(res=>{
state.value.ortherCouponLoading = false;
if(res.code == 200){
state.value.ortherCoupon = res.data;
}
// state.value.ortherCoupon = [
// {
// number: "NO20240711001",
// endTime: "2025-08-31 23:59:59",
// platform: "大众点评",
// title: "夏日特惠套餐",
// price: "88.00",
// dealId: "D123456",
// dealGroupId: "G987654",
// marketPrice: "128.00",
// receiptBeginDate: "2025-08-01",
// receiptEndDate: "2025-08-31"
// },
// {
// number: "NO20240711002",
// endTime: "2025-09-15 23:59:59",
// platform: "美团",
// title: "双人豪华套餐",
// price: "168.00",
// dealId: "D654321",
// dealGroupId: "G123789",
// marketPrice: "218.00",
// receiptBeginDate: "2025-09-01",
// receiptEndDate: "2025-09-15"
// }
// ];
})
}
return
}
const getPhoneNumber = (e) => {
uni.showLoading();
if (e.detail.code) {
getLoginApi.GetPhoneNumber({code:e.detail.code}).then((res) => {
if (res.code == 200) {
uni.hideLoading();
uni.showToast({
title: "获取手机号成功",
icon: "none",
duration: 2000,
});
bindPhonePopup.value.close();
storeState.userInfo.phone = res.data;
getOtherCoupon();
} else {
uni.hideLoading();
uni.showToast({
title: "获取手机号失败",
icon: "none",
duration: 2000,
});
}
});
} else {
uni.hideLoading();
uni.showToast({
title: "获取手机号失败",
icon: "none",
duration: 2000,
});
}
}
// 新单
const disabledFunc = (item) => {
if(!props.siteData?.siteId) return false
if (!item) {
return true;
}
// 已经选中的
const couponItem = dataList.value.filter(
(x) => state.value.checkboxCouponValue.includes(x.couponDispositionId)
);
// 如果是它已经被选中 就直接返回 false
if(state.value.checkboxCouponValue.includes(item.couponDispositionId)) return false;
// 判断是否存在同类型的优惠卷 不然选
const isExistSameType = couponItem.some(
(x) => x.couponType === item.couponType
);
if(isExistSameType) return true;
// item.siteIds 空的时候 包括全部
const isSite = item.siteIds.length
? item.siteIds.includes(props.siteData.siteId)
: true;
if(!isSite) return true;
const isUnit = item.unitTypeIds.length
? item.unitTypeIds.includes(props.siteData.unitTypeId)
: true;
if(!isUnit) return true;
let isPrice = item.satisfyAmount
? props.priceData.discountExpense >= item.satisfyAmount
: true;
if([3,4,5,6].includes(item.couponType)){
isPrice = props.month>=item.fullMonth
}
if(!isPrice) return true;
// 将日期字符串转换为 Date 对象
const start = new Date(item.startDate);
const end = new Date(item.endDate);
// 获取当前日期和时间
const now = new Date();
// 判断当前日期是否在开始日期和结束日期之间
const isDate = now >= start && now <= end;
if(!isDate) return true;
// 判断是否可以添加
const canAdd = canAddValue(couponItem.map(x=>x.couponType),item.couponType)
if(!canAdd) return true;
// 能否用在续仓
let canReNew = true;
if(props.isRenew){
canReNew = item.renewUsable
}
if(!canReNew) return true;
return !(isSite && isUnit && isPrice && isDate&& canAdd && canReNew);
};
function canAddValue(arr, value) {
// 定义可以共存的数字
const allowedNumbers = new Set([1, 3, 4]);
// 如果数组为空,可以添加任意值
if (arr.length === 0) {
return true;
}
// 检查数组中是否已经存在非1、3、4的值
const hasInvalidNumber = arr.some(num => !allowedNumbers.has(num));
if (hasInvalidNumber) {
return false; // 如果数组中已经有非1、3、4的值不能添加任何值
}
// 如果要添加的值是1、3、4且没有重复则可以添加
if (allowedNumbers.has(value) && !arr.includes(value)) {
return true;
}
// 其他情况不能添加
return false;
}
const ortherCouponChange = (e)=>{
const couponItem = dataList.value.filter(
(x) => state.value.checkboxCouponValue.includes(x.couponDispositionId)
);
const couponOtherItem = state.value.ortherCoupon.filter(
(x) => state.value.checkboxOrtherCouponValue.includes(x.number)
);
getLockerExpense(couponItem,couponOtherItem)
}
const couponChange = (e)=>{
const couponItem = dataList.value.filter(
(x) => state.value.checkboxCouponValue.includes(x.couponDispositionId)
);
const couponOtherItem = state.value.ortherCoupon.filter(
(x) => state.value.checkboxOrtherCouponValue.includes(x.number)
);
getLockerExpense(couponItem,couponOtherItem)
}
const getLockerExpense = async (couponItem,couponOtherItem) => {
uni.showLoading({
mask: true,
});
const LockerExpenseApi = props.isRenew ? getOrderApi.ContinuationOrderPricePost : getSiteApi.GetLockerExpense;
await LockerExpenseApi({
lockerId: props.siteData.lockerId,
orderId: props.siteData.orderId,
month: props.month,
couponIds: couponItem.map((item) => item.couponDispositionId),
serialNumber: couponOtherItem.map(x => {
return {
dealGroupId: x.dealGroupId,
number: x.number,
marketPrice: x.marketPrice,
purchasePrice: x.price
}
})
})
.then((res) => {
uni.hideLoading();
if (res.code === 200) {
state.value.canComfirm = true
state.value.feeData = res.data;
}else{
state.value.canComfirm = false
}
if(res.code === 1002){
uni.showModal({
title: t('common.title'),
content: t('common.ORDER_AMOUNT_ERROR'),
showCancel: false,
success: function () {},
});
}
});
};
const confirm = async ()=>{
const couponItem = dataList.value.filter(
(x) => state.value.checkboxCouponValue.includes(x.couponDispositionId)
);
const couponOtherItem = state.value.ortherCoupon.filter(
(x) => state.value.checkboxOrtherCouponValue.includes(x.number)
);
await getLockerExpense(couponItem,couponOtherItem)
if(!state.value.canComfirm) return
emit('confirm',{couponItem,couponOtherItem})
modelValue.value = false;
}
defineExpose({
getOtherCoupon,
open,
close
})
</script>
<style lang="scss" scoped>
.coupon{
.couponContent{
.confirm{
height: 100rpx;
width: 100%;
background-color: #fff;
display: flex;
justify-content: center;
.next-btn{
position: absolute;
bottom:30upx;
width: 90%;
padding: 4px 0;
font-size: 28rpx;
font-weight: bold;
color: var(--text-color);
background: var(--active-color);
border-radius: 16rpx;
}
}
height: 100%;
background-color: #fff;
border-radius: 18upx 18upx 0 0;
padding-bottom: 40upx;
display: flex;
flex-direction: column;
.header{
height: 100rpx;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.line{
width: 80rpx;
height: 10rpx;
background-color:#CBCDCC80;
margin: 0 auto;
border-radius: 99rpx;
}
.title{
font-size: 36rpx;
align-self: self-start;
margin-left: 40rpx;
}
}
.content{
flex: 1;
padding: 0 20upx 100upx 20upx;
overflow-y: auto;
display: flex;
flex-direction: column;
position: relative;
.couponUl{
.couponLi{
position: relative;
display: flex;
padding: 40upx;
width: 100%;
margin: 20upx 0;
border-radius: 18upx;
color: #fff;
background: linear-gradient(90deg, var(--left-linear), var(--right-linear));
box-shadow: 0px -2px 5px 0px rgba(0, 0, 0, 0.13);
font-size: 22upx;
&::after{
content: "";
position: absolute;
top: 2px;
right: -7px;
width: 14px; /* 调整宽度 */
height: 100%; /* 调整高度以适应圆形的垂直排列 */
background: radial-gradient(#ffffff 0px, #ffffff 5px, transparent 5px, transparent);
background-size: 14px 14px; /* 保持圆形的大小 */
background-repeat: repeat-y; /* 垂直重复 */
z-index: 9;
}
.left{
width: 68%;
display: flex;
flex-direction: column;
position: relative;
.name{
font-size: 36upx;
letter-spacing: 4upx;
font-weight: bold;
}
.desc{
margin-top: 10upx;
}
.input{
background-color: var(--bg-popup);
text-align: center;
width: 220upx;
border-radius: 12upx;
color: var(--text-color);
margin-top: 20upx;
}
&::before{
content: ' ';
display: block;
height: 32upx;
width: 32upx;
border-radius: 99rpx;
background-color: #fff;
position: absolute;
bottom: 0;
top: -56upx;
right: -18upx;
}
&::after{
content: ' ';
display: block;
height: 32upx;
width: 32upx;
border-radius: 99rpx;
background-color: #fff;
position: absolute;
bottom: -56upx;
right: -18upx;
}
border-right: 1px solid #fff;
}
.right{
width: 32%;
display: flex;
position: relative;
flex-direction: column;
justify-content: center;
padding-left: 5%;
.btn{
border-radius: 99rpx;
background-color: #fff;
color: var(--stress-color2);
font-size: 24upx;
line-height: 60upx;
margin-top: 30upx;
font-weight: bold;
&[disabled]{
opacity: 0.5;
}
}
.desc{
margin-top: 10upx;
text-align: center;
letter-spacing: 4upx;
}
}
}
}
}
}
}
</style>