feat:h5领取食堂优惠券

This commit is contained in:
dzc committed 2026-01-29 11:40:42 +08:00
1 parent 29a675f533
commit a1e7774f3e
12 files changed
+581 -26

No files matched your search

+7
View File
@@ -119,6 +119,13 @@
"disableScroll": true
}
},
{
"path": "my-act-list",
"style": {
"navigationBarTitleText": "我参加的活动",
"disableScroll": true
}
},
{
"path": "merchant-activity",
"style": {
@@ -0,0 +1,198 @@
<script setup lang="ts">
import { reactive, ref, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { serviceEnum } from "@@/constant"
const { actCouponTypeEnum } = serviceEnum
const emit = defineEmits(["doVisibleQrcode"])
const props = withDefaults(defineProps<{ detail: any }>(), {
detail: {},
})
const doVisibleQrcode = () => {
emit("doVisibleQrcode", { type: 'ACT_CANCEL', detail: props.detail })
}
</script>
<template>
<view class="coupon-item">
<!-- 左侧主体 -->
<view class="coupon-left">
<view class="size-140rpx rounded-8rpx flex-row-center flex-shrink-0 bg-#f0f0f0 m-r-10rpx">
<image class="size-110rpx" src="/static/icon/act_cancel.png" mode="aspectFill" />
</view>
<view class="info-box">
<view class="price-row">
<view v-if="detail.faceValue" class="flex items-center">
<text class="symbol">¥</text>
<text class="amount">{{ detail.faceValue }}</text>
</view>
<text class="name">{{ detail.actTitlel }}</text>
</view>
<view class="condition">
活动商户:{{ detail.mchName }}
</view>
<view class="scope-text flex-col">
<text>{{ actCouponTypeEnum[detail.couponType] }}</text>
<text>有限期至 {{ detail.expireTime }}</text>
</view>
</view>
</view>
<!-- 虚线分割与缺口 -->
<view class="split-line">
<view class="circle-top"></view>
<view class="dashed-line"></view>
<view class="circle-bottom"></view>
</view>
<!-- 右侧按钮 -->
<view class="coupon-right">
<view class="action-btn" @tap="doVisibleQrcode">核销码</view>
</view>
</view>
</template>
<style scoped lang="scss">
.coupon-item {
display: flex;
align-items: stretch;
background-color: #fff;
margin-bottom: 20rpx;
border-radius: 16rpx;
overflow: hidden;
position: relative;
box-shadow: 0 4rpx 16rpx rgba(0, 148, 255, 0.08);
}
/* 左侧布局 */
.coupon-left {
flex: 1;
display: flex;
align-items: center;
padding: 24rpx 0 24rpx 24rpx;
background: radial-gradient(circle at right top, transparent 12rpx, #fff 0) top left / 100% 51% no-repeat,
radial-gradient(circle at right bottom, transparent 12rpx, #fff 0) bottom left / 100% 51% no-repeat;
/* 备用背景色,防止渐变失效 */
background-color: #fff;
}
.goods-img {
width: 140rpx;
height: 140rpx;
border-radius: 8rpx;
background-color: #f0f0f0;
margin-right: 20rpx;
flex-shrink: 0;
}
.info-box {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 140rpx;
}
.price-row {
display: flex;
align-items: baseline;
color: #0094ff;
}
.symbol {
font-size: 28rpx;
font-weight: 600;
}
.amount {
font-size: 48rpx;
font-weight: bold;
margin: 0 8rpx 0 2rpx;
line-height: 1;
}
.name {
font-size: 28rpx;
color: #333;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200rpx;
}
.condition {
font-size: 24rpx;
color: #0094ff;
opacity: 0.8;
margin-top: 4rpx;
}
.scope-text {
font-size: 22rpx;
color: #999;
margin-top: auto;
}
/* 中间分割线区域 */
.split-line {
width: 30rpx;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
}
.dashed-line {
width: 0;
height: 80%;
border-left: 2rpx dashed #0094ff44;
}
.circle-top {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -50%);
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background-color: #f5f5f5;
/* 假设页面背景接近此色,或者设为透明如果用 mask */
}
.circle-bottom {
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%, 50%);
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background-color: #f5f5f5;
}
/* 右侧按钮区 */
.coupon-right {
width: 160rpx;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(circle at left top, transparent 12rpx, #fff 0) top left / 100% 51% no-repeat,
radial-gradient(circle at left bottom, transparent 12rpx, #fff 0) bottom left / 100% 51% no-repeat;
background-color: #fff;
}
.action-btn {
background: #0094ff;
color: #fff;
font-size: 26rpx;
padding: 10rpx 24rpx;
border-radius: 30rpx;
font-weight: 500;
}
</style>
@@ -0,0 +1,197 @@
<script setup lang="ts">
import { reactive, ref, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { serviceEnum } from "@@/constant"
const { actCouponTypeEnum } = serviceEnum
const emit = defineEmits(["doVisibleQrcode"])
const props = withDefaults(defineProps<{ detail: any }>(), {
detail: {},
})
const doVisibleQrcode = () => {
emit("doVisibleQrcode", { type: 'ACT_COUPON', detail: props.detail })
}
</script>
<template>
<view class="coupon-item">
<!-- 左侧主体 -->
<view class="coupon-left">
<image class="size-140rpx rounded-8rpx flex-shrink-0 m-r-10rpx bg-#f0f0f0" src="/static/icon/act_coupon.png"
mode="aspectFill" />
<view class="info-box">
<view class="price-row">
<view v-if="detail.faceValue" class="flex items-center">
<text class="symbol">¥</text>
<text class="amount">{{ detail.faceValue }}</text>
</view>
<text class="name">{{ detail.couponName }}</text>
</view>
<view v-if="detail.minPoint" class="condition">
满{{ detail.minPoint }}可用
</view>
<view class="scope-text flex-col">
<text>{{ actCouponTypeEnum[detail.couponType] }}</text>
<text>有限期至 {{ detail.endTime }}</text>
</view>
</view>
</view>
<!-- 虚线分割与缺口 -->
<view class="split-line">
<view class="circle-top"></view>
<view class="dashed-line"></view>
<view class="circle-bottom"></view>
</view>
<!-- 右侧按钮 -->
<view class="coupon-right">
<view class="action-btn" @tap="doVisibleQrcode">核销码</view>
</view>
</view>
</template>
<style scoped lang="scss">
.coupon-item {
display: flex;
align-items: stretch;
background-color: #fff;
margin-bottom: 20rpx;
border-radius: 16rpx;
overflow: hidden;
position: relative;
box-shadow: 0 4rpx 16rpx rgba(0, 148, 255, 0.08);
}
/* 左侧布局 */
.coupon-left {
flex: 1;
display: flex;
align-items: center;
padding: 24rpx 0 24rpx 24rpx;
background: radial-gradient(circle at right top, transparent 12rpx, #fff 0) top left / 100% 51% no-repeat,
radial-gradient(circle at right bottom, transparent 12rpx, #fff 0) bottom left / 100% 51% no-repeat;
/* 备用背景色,防止渐变失效 */
background-color: #fff;
}
.goods-img {
width: 140rpx;
height: 140rpx;
border-radius: 8rpx;
background-color: #f0f0f0;
margin-right: 20rpx;
flex-shrink: 0;
}
.info-box {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 140rpx;
}
.price-row {
display: flex;
align-items: baseline;
color: #0094ff;
}
.symbol {
font-size: 28rpx;
font-weight: 600;
}
.amount {
font-size: 48rpx;
font-weight: bold;
margin: 0 8rpx 0 2rpx;
line-height: 1;
}
.name {
font-size: 28rpx;
color: #333;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200rpx;
}
.condition {
font-size: 24rpx;
color: #0094ff;
opacity: 0.8;
margin-top: 4rpx;
}
.scope-text {
font-size: 22rpx;
color: #999;
margin-top: auto;
}
/* 中间分割线区域 */
.split-line {
width: 30rpx;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
}
.dashed-line {
width: 0;
height: 80%;
border-left: 2rpx dashed #0094ff44;
}
.circle-top {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -50%);
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background-color: #f5f5f5;
/* 假设页面背景接近此色,或者设为透明如果用 mask */
}
.circle-bottom {
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%, 50%);
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background-color: #f5f5f5;
}
/* 右侧按钮区 */
.coupon-right {
width: 160rpx;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(circle at left top, transparent 12rpx, #fff 0) top left / 100% 51% no-repeat,
radial-gradient(circle at left bottom, transparent 12rpx, #fff 0) bottom left / 100% 51% no-repeat;
background-color: #fff;
}
.action-btn {
background: #0094ff;
color: #fff;
font-size: 26rpx;
padding: 10rpx 24rpx;
border-radius: 30rpx;
font-weight: 500;
}
</style>
+5 -26
View File
@@ -68,28 +68,18 @@
</view>
<template #main-bottom>
<view v-if="state.isGet" class="text-center">
<tm-button class="px-32 m-2" @click="genVerificationCode">我的核销码</tm-button>
<view class="text-center">
<tm-button class="px-32 m-2" @click="doQrcode">我的核销码</tm-button>
</view>
</template>
</crud-layout>
<tm-drawer z-index="100" v-model:show="showQrCodeDialog" title="兑换券核销二维码" :show-close="true" position="bottom"
size="60%">
<view class="flex-col-center">
<view class="h-80rpx" />
<canvas canvas-id="qrcodeId" class="size-200px" />
</view>
</tm-drawer>
</template>
<script setup lang="ts">
import { reactive, ref } from "vue"
import { onLoad } from "@dcloudio/uni-app"
import { actCancel, actComApi, actCouponApi } from "@/sheep/api"
import uqrcode from "@@/utils/uqrcode"
import * as CryptoJS from "crypto-js"
import { hooks } from "@/sheep"
import { hooks, router } from "@/sheep"
import { serviceEnum } from "@@/constant"
const { actUserReceiveEnum, actCouponTypeEnum } = serviceEnum
@@ -101,7 +91,6 @@ const state = reactive<any>({
actDetail: {}, // 优惠券详情
couponInfo: {}, // 优惠券信息
})
const showQrCodeDialog = ref(false)
onLoad((option: any) => {
const { actId, actType } = option
@@ -205,18 +194,8 @@ const getCouponReceive = async () => {
}
}
// 生成核销二维码
const genVerificationCode = async () => {
showQrCodeDialog.value = true
const words = CryptoJS.enc.Utf8.parse(JSON.stringify({ cancelId: state.couponInfo.id }))
uqrcode.make({
canvasId: "qrcodeId",
text: CryptoJS.enc.Base64.stringify(words),
size: 200,
margin: 0,
success: (res) => { },
complete: () => { },
})
const doQrcode = () => {
router.jump("/pages/merchant/my-act-list")
}
</script>
+146
View File
@@ -0,0 +1,146 @@
<script setup lang="ts">
import { reactive, ref, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import uqrcode from "@@/utils/uqrcode"
import * as CryptoJS from "crypto-js"
import { actCancel, actComApi, actCouponApi } from "@@/api"
import { hooks } from "@/sheep"
import { serviceEnum } from "@@/constant"
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
import CancelCard from "./components/cancel-card.vue"
import CouponCard from "./components/coupon-card.vue"
const { actCouponTypeEnum } = serviceEnum
const data = {
coupons: [
{ id: "c1", name: "食堂生日券", faceValue: 89, minPoint: 445, scope: "指定商品", btn: "立即领取", picUrl: "https://images.unsplash.com/photo-1511699443500-9e3e0bbfa3a6?w=240&q=80" },
{ id: "c2", name: "健康惊喜券", faceValue: 40, minPoint: 400, scope: "指定分类", btn: "立即领取", picUrl: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=240&q=80" },
{ id: "c3", name: "商品优惠券", faceValue: 72, minPoint: 399, scope: "指定商户", btn: "立即领取", picUrl: "https://images.unsplash.com/photo-1517336714731-489689fd1ca8?w=240&q=80" },
{ id: "c4", name: "店铺优惠券", faceValue: 55, minPoint: 350, scope: "指定商户", btn: "立即领取", picUrl: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1?w=240&q=80" },
],
}
const pageRef = ref<any>(null)
const showQrCodeDialog = ref(false)
const params = reactive({
})
const state = reactive({
tabs: [
{
id: 1,
name: "优惠券活动",
badge: {
count: 6
},
disabled: false
},
{
id: 2,
name: "核销券活动",
badge: {
count: 6
},
disabled: false
},
],
tabId: 1,
})
const dynamicPageHook = computed(() => {
if (state.tabId == 1) {
return hooks.usePage(pageRef, actCouponApi.userList, null, "data.records")
}
if (state.tabId == 2) {
return hooks.usePage(pageRef, actCancel.userList, null, "data.records")
}
})
onShow(() => {
getUserTotal()
})
const query = (page: number, pagesize: number) => {
dynamicPageHook.value?.startPage(page, pagesize, false, params)
getUserTotal()
}
const doTab = (e: any) => {
const target = state.tabs[e]
state.tabId = target.id
pageRef.value.reload(false)
getUserTotal()
}
const doVisibleQrcode = (detail: any) => {
const { type, detail: actDetail } = detail
console.log("生成二维码:", type, actDetail);
if (type == "ACT_COUPON") {
}
if (type == "ACT_CANCEL") {
genVerificationCode({ cancelId: actDetail.id })
}
}
// 用户参加活动的数量统计
const getUserTotal = async () => {
try {
const { data = {} }: any = await actComApi.userTotal({})
state.tabs[0].badge.count = data.couponCount || 0
state.tabs[1].badge.count = data.cancelCount || 0
} catch (error) {
console.log("|用户参加活动的数量统计|意外的错误error:", error);
}
}
// 生成核销二维码
const genVerificationCode = async (params: Record<string, any>) => {
showQrCodeDialog.value = true
const words = CryptoJS.enc.Utf8.parse(JSON.stringify(params))
uqrcode.make({
canvasId: "qrcodeId",
text: CryptoJS.enc.Base64.stringify(words),
size: 200,
margin: 0,
success: (res) => { },
complete: () => { },
})
}
</script>
<template>
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="dynamicPageHook.list" :default-page-size="10"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false"
@scroll="dynamicPageHook.scroll">
<template #top>
<view class="h-30rpx" />
<view class="bg-white rounded-16rpx">
<z-tabs :list="state.tabs" @change="doTab" />
</view>
</template>
<view class="h-20rpx" />
<view v-if="state.tabId == 1">
<coupon-card v-for="item in dynamicPageHook.list" :key="item.id" :detail="item"
@do-visible-qrcode="doVisibleQrcode" />
</view>
<view v-if="state.tabId == 2">
<cancel-card v-for="item in dynamicPageHook.list" :key="item.id" :detail="item"
@do-visible-qrcode="doVisibleQrcode" />
</view>
</z-paging>
<tm-drawer z-index="100" v-model:show="showQrCodeDialog" title="兑换券核销二维码" :show-close="true" position="bottom"
size="60%">
<view class="flex-col-center">
<view class="h-80rpx" />
<canvas canvas-id="qrcodeId" class="size-200px" />
</view>
</tm-drawer>
</crud-layout>
</template>
+1
View File
@@ -79,6 +79,7 @@ const state = reactive<any>({
userInfo: {},
menuList: [
{ text: "扫一扫", icon: "/static/icon/scan.png", path: "" },
{ text: "我参加的活动", icon: "/static/icon/act.png", path: "/pages/merchant/my-act-list" },
{ text: "我的钱包", icon: "/static/icon/service_3.png", path: "/pages/wallet/list" },
{ text: "我的人脸", icon: "/static/icon/service_5.png", path: "/pages/human-face/list" },
{ text: "车辆管理", icon: "/static/icon/service_6.png", path: "/pages/car/userCar/list" },
+9
View File
@@ -61,4 +61,13 @@ export default {
method: "GET",
params,
}),
/**
* 用户领取核销券列表
*/
userList: (params: any) =>
request({
url: "/wallet/act/cancel-item/app/user-list",
method: "GET",
params,
}),
}
+9
View File
@@ -28,4 +28,13 @@ export default {
method: "GET",
params,
}),
/**
* 用户参加活动的数量统计
*/
userTotal: (params: any) =>
request({
url: "/wallet/act/com/app/user-total",
method: "GET",
params,
}),
}
+9
View File
@@ -10,4 +10,13 @@ export default {
method: "POST",
data,
}),
/**
* 用户领取核销券列表
*/
userList: (params: any) =>
request({
url: "/wallet/act-coupon-record/app/user-list",
method: "GET",
params,
}),
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB