Files
cashier_wx/pages/order/coupon.vue

1063 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="container">
<view class="header-wrap">
<!-- <view class="search-wrap">
<view class="input-wrap">
<view class="icon left">
<u-icon name="search" size="26"></u-icon>
</view>
<input v-model="querForm.searchValue" class="ipt left" type="text" placeholder="搜索" @confirm="searchHandle" />
</view>
<view class="input-wrap" @click="show = true">
<view class="icon right">
<u-icon name="arrow-right" size="16"></u-icon>
</view>
<input v-model="querForm.shopName" class="ipt right" type="text" placeholder="请选择" disabled />
</view>
</view> -->
<view class="status-wrap">
<view
class="item"
:class="{ active: querForm.statusActiveIndex == 0 }"
@click="tabChange(0)"
>
<text class="t">商品兑换券 {{ returnSelNumber(0) }}</text>
</view>
<view
class="item"
:class="{ active: querForm.statusActiveIndex == 1 }"
@click="tabChange(1)"
>
<text class="t">折扣优惠券{{ returnSelNumber(1) }}</text>
</view>
<view
class="icon-wrap"
:style="{
width: `${100 / statusList.length}%`,
left: `${(100 / statusList.length) * querForm.statusActiveIndex}%`,
}"
>
<image
class="active-icon"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAMCAYAAAB4MH11AAACKUlEQVR4AaRTS2sTURT+MpkxUzNtRpuYpBsrqbgQH6BYRHGllloQN+5cuNeV6MKfoj9BwYUgIsXHRqE+SluliFLbBqmENJNMMpMm87qecw1NglJse+HMPeee833nce8oIgpFq7wsGt/fCb9eFrtdfmNd1L+9Jc4Vog6E4jcqqM0/R+3zNOwv09jtshdfSx7mDJwKqAMfIvAAEaFVWkLgWjvOEbVdtNa+Epf4wxmFUNTkfqiDGUkqogDu8icKiKS9nQ9jnR8fqM5AwuJJE/G9JhRF05E8dAqIKeDlrsyiWVxg9f+Fum+uzsFZmtnEGIVxKHsGIFn1bAF6ZlQ6I28D9uIrmYSrkodbfETgw6GubZo9Yzk0kR4Fc7IuE8QUFebJKSTSB/kM4UYd1scnKL18QInm4Vk/6W6qiNoOwpZDuoX2elFWXHrzELW5Z+RzJVbPHca+09doIKq0ZQLW+C44iWbm2ZQSNMqozj5F5f1jWCSVGd4fwerstYUXCOplGcsfxponJqHS7Nlm2UyAWAza0AFkzt1A6uhFqEYaoHsR9BLCpg2vukZVr5IU4dV+yU7oRikkTrHDMI9PIHPhJrhQ9Kxugs6hkkhi8Mh5DI9flyCep6ImOt7uxnF6dgypYxMy1hg7S0/yH3FdSL+mpbIw6CVwVSNX7yM/eQfZS7eQu3wb+St3MTJ1D2nq1iicgZbK9YN7rL866PH1qfGBIWj0v/Do4rrR59vK+A0AAP//GfTndQAAAAZJREFUAwCu+SjIaSGpLwAAAABJRU5ErkJggg=="
></image>
</view>
</view>
</view>
<view class="list-wrap">
<view class="tips">
<text class="t"
>使用商品兑换券的商品不再计入同享优惠券门槛和折扣计算</text
>
</view>
<view class="title-wrap">
<text class="t">可用红包</text>
<text class="n">{{ list.canUseCoupons.length }}</text>
</view>
<view
class="item"
v-for="item in list.canUseCoupons"
:key="item.id"
@click="changeSelCoupon(item)"
>
<view class="top">
<view class="icon">
<couponIcon :item="item" />
</view>
<view class="info">
<view class="view name">
<text class="t">{{ item.name }}</text>
</view>
<view class="view time">
<text class="t"
>{{ dayjs(item.effectStartTime).format("YYYY.M.D") }} -
{{ dayjs(item.effectEndTime).format("YYYY.M.D") }}</text
>
</view>
</view>
<view class="btn">
<view class="active" v-if="isActive(item)">
<up-icon
name="checkmark-circle-fill"
size="24"
color="#FF3232"
></up-icon>
</view>
<view class="round" v-else></view>
</view>
</view>
<view class="btm">
<view class="left"
>1可适用门店{{ item.useShops }} 2可适用商品{{
item.foods
}}3可使用类型{{ convertValuesToLabels(item.useType) }}</view
>
<view class="right" @click.stop="showDetailHandle(item)">
<text class="t">查看详情</text>
</view>
</view>
</view>
<view class="title-wrap">
<text class="t">不可用红包</text>
<text class="n">{{ list.noCanUseCoupons.length }}</text>
</view>
<view
class="item disabled"
v-for="item in list.noCanUseCoupons"
:key="item.id"
>
<view class="top">
<view class="icon">
<couponIcon :item="item" />
</view>
<view class="info">
<view class="view name">
<text class="t">{{ item.name }}</text>
</view>
<view class="view time">
<text class="t"
>{{ dayjs(item.effectStartTime).format("YYYY.M.D") }} -
{{ dayjs(item.effectEndTime).format("YYYY.M.D") }}</text
>
</view>
</view>
<view class="btn">
<view class="round"></view>
</view>
</view>
<view class="btm">
<!-- <view class="left">1可适用门店{{ item.useShops }} 2可适用商品{{ item.foods }}3可使用类型{{ convertValuesToLabels(item.useType) }}</view>
<view class="right" @click.stop="showDetailHandle(item)">
<text class="t">查看详情</text>
</view> -->
<view class="error">
<text class="t t1">不可用原因</text>
<text class="t t2">{{ returnNoUseRestrictions(item) }}</text>
</view>
</view>
</view>
</view>
<u-loadmore :status="list.status"></u-loadmore>
<u-popup :show="show" round="20" closeable @close="show = false">
<view class="shoplist-popup">
<view class="title">
<text class="t">店铺列表</text>
</view>
<scroll-view
class="popup-list"
direction="vertical"
@scrollend="scrollBottom"
>
<view
class="item"
v-for="item in shopList"
:key="item.shopId"
@click="selectShopHandle(item)"
>
<text class="t">{{ item.shopName }}</text>
<text class="intro">地址{{ item.shopAddress }}</text>
</view>
<u-loadmore status="nomore"></u-loadmore>
</scroll-view>
</view>
</u-popup>
<u-popup
:show="showDetail"
round="20"
closeable
@close="showDetail = false"
>
<view class="shoplist-popup">
<view class="title">
<text class="t">详情说明</text>
</view>
<scroll-view class="popup-list" direction="vertical">
<view class="ul">
<view
class="li"
v-for="(item, index) in selectListItemDetails"
:key="index"
>{{ index + 1 }}{{ item }}</view
>
</view>
</scroll-view>
</view>
</u-popup>
</view>
</template>
<script setup>
import dayjs from "dayjs";
import { ref, reactive, onMounted, computed, watch } from "vue";
import {
onLoad,
onReady,
onShow,
onPageScroll,
onReachBottom,
onBackPress,
} from "@dcloudio/uni-app";
import {
APIcouponfindByUserId,
APIfindCoupon,
getCouponShops,
} from "@/common/api/member.js";
import { findCoupon } from "@/common/api/market/coupon.js";
import couponIcon from "@/pages/user/components/coupon-icon.vue";
// import * as UTILS from '@/utils/goods-utils.js';
import yskUtils from "ysk-utils";
const UTILS = yskUtils.couponUtils;
import { useCartsStore } from "@/stores/carts.js";
const cartStore = useCartsStore();
//返回不可用原因
function returnNoUseRestrictions(item) {
if (item.noUseRestrictions) {
return item.noUseRestrictions;
}
if (item.canuseResult) {
return item.canuseResult.reason;
}
return "";
}
const show = ref(false);
const querForm = ref({
searchValue: "",
shopId: "",
shopName: "",
statusActiveIndex: 0,
});
const statusList = ref([
{
value: 0,
label: "商品兑换券",
bg: "#333333",
color: "#ffffff",
},
{
value: 1,
label: "折扣优惠券",
bg: "#F8F8F8",
color: "#999999",
},
]);
const list = reactive({
page: 1,
size: 10,
status: "nomore",
data: [],
noCanUseCoupons: [],
canUseCoupons: [],
});
const showDetail = ref(false);
const selectListItem = ref("");
const selectListItemDetails = ref([]);
function showDetailHandle(item) {
showDetail.value = true;
// 1. 定义每个规则的独立描述仅“其他优惠券”需判断item.type
const ruleList = [];
// 规则1限时折扣同享始终显示
const discountRule = item.discountShare
? "与限时折扣同享"
: "不与限时折扣同享";
ruleList.push(discountRule);
// 规则2会员价/会员折扣同享(始终显示)
const vipRule = item.vipPriceShare
? "与会员价/会员折扣同享"
: "不与会员价/会员折扣同享";
ruleList.push(vipRule);
// 规则3其他优惠券同享仅item.type=2时显示
if (item.type === 2) {
const otherCouponRule = item.otherCouponShare
? "与其他优惠券同享"
: "不与其他优惠券同享";
ruleList.push(otherCouponRule);
}
const shareRuleText = `${ruleList.join("、")}`;
let foods = item.foods;
selectListItemDetails.value = [
`可适用门店:${item.useShops}`,
`可适用商品:${foods}`,
`可使用类型:${convertValuesToLabels(item.useType)}`,
`可用时间段:${
item.useTimeType == "all"
? "全段时间可用"
: `${item.useStartTime} - ${item.useEndTime}`
}`,
`限量规则:每人限领${
item.getLimit == -10086 ? `无限张` : `${item.getLimit}`
},每日最多可使用${
item.useLimit == -10086 ? `无限张` : `${item.useLimit}`
}`,
`同享规则:${shareRuleText}`,
`其它说明:${item.ruleDetails || "无"}`,
];
if (item.type == 2 || item.type == 4 || item.type == 6) {
selectListItemDetails.value.splice(
2,
0,
`使用规则:${
item.useRule == "price_asc" ? "从最低价开始抵扣" : "从最高价开始抵扣"
}`
);
}
if (item.type == 3) {
selectListItemDetails.value.unshift(`最高抵扣${item.maxDiscountAmount}`);
}
}
function returnSelNumber(index) {
if (index) {
if (couponSel.value.id) {
return "(1)";
} else {
return "(0)";
}
} else {
if (goodsCouponSel.value.id) {
return "(1)";
} else {
return "(0)";
}
}
}
// 搜索
function searchHandle() {
list.page = 1;
list.status = "nomore";
getCouponList();
}
// 切换类型
function tabChange(index) {
querForm.value.statusActiveIndex = index;
list.page = 1;
list.status = "nomore";
formatCoupon();
}
function changeSelCoupon(item) {
if (querForm.value.statusActiveIndex) {
if (couponSel.value.id == item.id) {
couponSel.value = {
id: "",
};
} else {
couponSel.value = item;
}
} else {
if (goodsCouponSel.value.id == item.id) {
goodsCouponSel.value = {
id: "",
};
} else {
goodsCouponSel.value = item;
}
}
}
const couponSel = ref({
id: "",
});
const goodsCouponSel = ref({
id: "",
});
const quansSelArr = computed(() => {
return [couponSel.value, goodsCouponSel.value].filter((v) => v.id);
});
watch(
() => couponSel.value.id,
(newval) => {
formatCoupon();
}
);
watch(
() => goodsCouponSel.value.id,
(newval) => {
formatCoupon();
}
);
function isActive(item) {
if (querForm.value.statusActiveIndex) {
return couponSel.value.id == item.id;
} else {
return goodsCouponSel.value.id == item.id;
}
}
const couponList = ref([]);
function formatCoupon() {
let canUseGoodsCoupon = [];
let canUseDiscountCoupon = [];
let noUseGoodsCoupon = [];
let noUseDiscountCoupon = [];
const user = uni.cache.get("shopUserInfo");
let shopInfo = uni.cache.get("shopInfo") || {};
if (!shopInfo.isMemberPrice) {
shopInfo = {};
}
const goodsOrderPrice = uni.getStorageSync("goodsOrderPrice") || 0;
const dinnerType = cartStore.dinnerType;
const canDikouGoodsArr = UTILS.returnCanDikouGoods(
cartStore.allGoods,
[],
user
);
const shopId = uni.cache.get("shopId");
for (let i = 0; i < couponList.value.length; i++) {
const coupon = couponList.value[i];
const selCoupon =
querForm.value.statusActiveIndex != 1
? quansSelArr.value.filter((v) => v.type != 2)
: quansSelArr.value.filter((v) => v.type == 2);
const canuseResult = UTILS.returnCouponCanUse({
canDikouGoodsArr,
coupon,
goodsOrderPrice,
user,
selCoupon: selCoupon,
shopInfo,
limitTimeDiscount: cartStore.limitTimeDiscount,
});
const { canUse, reason } = canuseResult;
if (coupon.type == 2) {
if (canUse || goodsCouponSel.value.id == coupon.id) {
canUseGoodsCoupon.push(coupon);
} else {
noUseGoodsCoupon.push({
...coupon,
canuseResult,
});
}
} else {
if (canUse || couponSel.value.id == coupon.id) {
canUseDiscountCoupon.push(coupon);
} else {
noUseDiscountCoupon.push({
...coupon,
canuseResult,
});
}
}
}
//商品券
canUseGoodsCoupon = canUseGoodsCoupon.map((v) => {
const discount = UTILS.returnCouponDiscount(
canDikouGoodsArr,
v,
user,
goodsOrderPrice,
quansSelArr.value,
shopInfo,
cartStore.limitTimeDiscount
);
return {
...v,
discount,
discountAmount: discount ? discount.discountPrice : v.discountAmount,
};
});
//非商品券
canUseDiscountCoupon = canUseDiscountCoupon.map((v) => {
const discount = UTILS.returnCouponDiscount(
canDikouGoodsArr,
v,
user,
goodsOrderPrice,
quansSelArr.value,
shopInfo,
cartStore.limitTimeDiscount
);
return {
...v,
discount,
discountAmount: discount ? discount.discountPrice : v.discountAmount,
};
});
if (querForm.value.statusActiveIndex == 0) {
list.noCanUseCoupons = noUseGoodsCoupon;
list.canUseCoupons = canUseGoodsCoupon;
} else {
list.noCanUseCoupons = noUseDiscountCoupon;
list.canUseCoupons = canUseDiscountCoupon;
}
console.log("canUseGoodsCoupon", canUseGoodsCoupon);
console.log("noUseGoodsCoupon", noUseGoodsCoupon);
console.log("canUseDiscountCoupon", canUseDiscountCoupon);
console.log("noUseDiscountCoupon", noUseDiscountCoupon);
}
// 获取优惠券列表
async function getCouponList() {
try {
uni.showLoading({
title: "加载中...",
mask: true,
});
const res = await findCoupon({
shopUserId: uni.cache.get("shopUserInfo").id,
});
couponList.value = res;
formatCoupon();
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
// 店铺列表滚动到底部了
function scrollBottom() {
console.log("店铺列表滚动到底部了");
}
/**
* 将value数组字符串转换为对应的label拼接字符串
* @param {Array} options - 包含value和label的选项数组格式如[{value: 'xxx', label: 'xxx'}, ...]
* @param {string} valueStr - 包含value的数组字符串格式如'["dine","pickup"]'
* @param {string} separator - 标签拼接分隔符,默认值为'、'
* @returns {string} 拼接后的label字符串如"堂食、自取"
*/
function convertValuesToLabels(valueStr, options, separator = "、") {
try {
options = [
{
value: "dine",
label: "堂食",
},
{
value: "pickup",
label: "自取",
},
{
value: "deliv",
label: "配送",
},
{
value: "express",
label: "快递",
},
];
// 验证输入参数
if (!Array.isArray(options)) {
throw new Error("options必须是数组");
}
if (typeof valueStr !== "string") {
throw new Error("valueStr必须是字符串");
}
// 解析value数组字符串
const values = JSON.parse(valueStr);
if (!Array.isArray(values)) {
throw new Error("解析后的valueStr必须是数组");
}
// 构建value到label的映射表
const valueLabelMap = new Map();
options.forEach((item) => {
if (
item &&
typeof item.value !== "undefined" &&
typeof item.label !== "undefined"
) {
valueLabelMap.set(item.value, item.label);
}
});
// 匹配并收集label
const labels = values
.map((value) => valueLabelMap.get(value))
.filter(Boolean); // 过滤未匹配到的项
// 拼接结果
return labels.join(separator);
} catch (error) {
console.error("转换失败:", error.message);
return ""; // 出错时返回空字符串
}
}
// 选择店铺
function selectShopHandle(item) {
querForm.value.shopId = item.shopId;
querForm.value.shopName = item.shopName;
list.page = 1;
show.value = false;
getCouponList();
}
// 获取当前店铺会员信息
const shopList = ref([]);
async function getCouponShopsAjax() {
try {
const res = await getCouponShops();
shopList.value = res;
} catch (error) {
console.log(error);
}
}
onShow(() => {
const couponArr = cartStore.backendCoupons.filter((v) => v.type != 2);
const goodsCouponArr = cartStore.backendCoupons.filter((v) => v.type == 2);
if (couponArr.length) {
couponSel.value = couponArr[0];
}
if (goodsCouponArr.length) {
goodsCouponSel.value = goodsCouponArr[0];
}
getCouponList();
});
onLoad(() => {
getCouponShopsAjax();
});
watch(
() => quansSelArr.value,
(newval) => {
// getCouponList()
const user = uni.cache.get("shopUserInfo");
let shopInfo = uni.cache.get("shopInfo") || {};
if (!shopInfo.isMemberPrice) {
shopInfo = {};
}
const goodsOrderPrice = uni.getStorageSync("goodsOrderPrice") || 0;
const dinnerType = cartStore.dinnerType;
const canDikouGoodsArr = UTILS.returnCanDikouGoods(
cartStore.allGoods,
[],
user
);
const shopId = uni.cache.get("shopId");
let goodsCoupon = newval.filter((v) => v.type == 2);
let otherCoupon = newval.filter((v) => v.type != 2);
goodsCoupon = goodsCoupon.map((v) => {
const discount = UTILS.returnCouponDiscount(
canDikouGoodsArr,
v,
user,
goodsOrderPrice,
[],
shopInfo,
cartStore.limitTimeDiscount
);
return {
...v,
discount,
discountAmount: discount ? discount.discountPrice : v.discountAmount,
};
});
otherCoupon = otherCoupon.map((v) => {
const discount = UTILS.returnCouponDiscount(
canDikouGoodsArr,
v,
user,
goodsOrderPrice,
goodsCoupon,
shopInfo,
cartStore.limitTimeDiscount
);
return {
...v,
discount,
discountAmount: discount ? discount.discountPrice : v.discountAmount,
};
});
uni.$emit("selCoupon", [...goodsCoupon, ...otherCoupon]);
},
{
deep: true,
}
);
</script>
<style>
page {
background-color: #f7f7f7;
}
</style>
<style scoped lang="scss">
.container {
padding: 136upx 28upx 28upx;
}
.header-wrap {
width: 100%;
background-color: #fff;
position: fixed;
top: 0;
left: 0;
z-index: 99;
padding: 28upx;
.search-wrap {
display: flex;
gap: 28upx;
.input-wrap {
flex: 1;
height: 70upx;
border: 1px solid #ececec;
border-radius: 8upx;
position: relative;
&:first-child {
flex: 1.5;
}
.icon {
position: absolute;
top: 50%;
transform: translateY(-50%);
&.left {
left: 14upx;
}
&.right {
right: 14upx;
}
}
.ipt {
width: 100%;
height: 100%;
&.left {
padding-left: 68upx;
}
&.right {
padding: 0 56upx 0 28upx;
}
}
}
}
.status-wrap {
display: flex;
// padding-top: 28upx;
position: relative;
.icon-wrap {
height: 12upx;
position: absolute;
bottom: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease-in-out;
padding-right: 28upx;
.active-icon {
width: 24upx;
height: 12upx;
z-index: 9;
}
}
.item {
flex: 1;
height: 80upx;
display: flex;
align-items: center;
justify-content: center;
.t {
font-size: 32upx;
color: #666666;
transition: all 0.3s ease-in-out 0.1s;
}
&.active {
.t {
color: #e3ad7f;
}
}
}
}
}
.list-wrap {
padding-top: 28upx;
.tips {
.t {
color: #666;
font-size: 24upx;
}
}
.title-wrap {
display: flex;
align-items: center;
gap: 10upx;
padding-top: 28upx;
.t {
font-size: 32upx;
color: #333;
}
.n {
color: #666;
font-size: 24upx;
}
}
.item {
border-radius: 18upx;
background-color: #fff;
padding: 28upx;
&:not(:first-child) {
margin-top: 28upx;
}
&.disabled {
.top {
position: relative;
&::after {
content: "";
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 10;
background-color: rgba(255, 255, 255, 0.6);
}
.btn {
.round {
background-color: #f0f0f0;
}
}
}
}
.top {
display: flex;
align-items: center;
padding-bottom: 28upx;
.icon {
$size: 140upx;
width: $size;
height: $size;
margin-right: 28upx;
}
.info {
flex: 1;
display: flex;
justify-content: center;
flex-direction: column;
gap: 8upx;
padding: 0 28upx;
position: relative;
&::after {
$height: 100upx;
content: "";
height: $height;
border-left: 1upx solid #f7f7f7;
position: absolute;
top: 50%;
margin-top: $height * 0.5 * -1;
left: 0;
}
.view {
flex: 1;
&.name {
font-size: 32upx;
color: #333;
}
&.time {
.t {
font-size: 24upx;
color: #999;
}
}
}
}
.btn {
width: 40upx;
height: 40upx;
display: flex;
align-items: center;
justify-content: center;
.round {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #fff;
border: 1px solid #d9d9d9;
}
}
}
.btm {
display: flex;
align-items: center;
padding: 28upx 0 14upx;
border-top: 1upx solid #f7f7f7;
.left {
flex: 1;
height: 40upx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 24upx;
color: #999;
}
.right {
flex: 1;
display: flex;
align-items: center;
padding-left: 28upx;
justify-content: flex-end;
.t {
font-size: 24upx;
color: #333;
}
}
.error {
flex: 1;
.t {
font-size: 24upx;
&.t1 {
color: #ff1c1c;
}
&.t2 {
color: #333;
}
}
}
}
}
}
.shoplist-popup {
padding: 0 28upx 28upx;
.title {
padding: 28upx 0;
display: flex;
align-items: center;
justify-content: center;
.t {
font-size: 32upx;
font-weight: bold;
color: #333;
}
}
.popup-list {
max-height: 50vh;
.item {
padding: 28upx;
border-radius: 12upx;
background-color: #f7f7f7;
margin-bottom: 28upx;
display: flex;
flex-direction: column;
padding: 28upx;
.t {
font-size: 28upx;
font-weight: bold;
color: #333;
/* 必须设置宽度 */
width: 600upx;
/* 或具体像素值 */
/* 关键属性 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
/* 显示2行 */
overflow: hidden;
/* 文本溢出省略号 */
text-overflow: ellipsis;
/* 可选:防止行高度,确保计算准确 */
line-height: 1.5;
word-break: break-all;
/* 允许在单词内换行 */
word-wrap: break-word;
/* 允许长单词或URL换行 */
}
.intro {
font-size: 28upx;
color: #999;
/* 必须设置宽度 */
width: 600upx;
/* 或具体像素值 */
/* 关键属性 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
/* 显示2行 */
overflow: hidden;
/* 文本溢出省略号 */
text-overflow: ellipsis;
/* 可选:防止行高度,确保计算准确 */
line-height: 1.5;
word-break: break-all;
/* 允许在单词内换行 */
word-wrap: break-word;
/* 允许长单词或URL换行 */
}
}
.ul {
.li {
color: #999;
font-size: 28upx;
padding: 8upx 0;
.t {
color: #999;
font-size: 28upx;
}
}
}
}
}
</style>