feat:支付选择优惠券

This commit is contained in:
dzc committed 2026-05-26 18:24:57 +08:00
1 parent bce6da0a0f
commit 150b8b3ac7
15 files changed
+591 -106

No files matched your search

+2 -2
View File
@@ -17,9 +17,9 @@ VITE_PWD_ENC_KEY='pigxpigxpigxpigx'
VITE_OAUTH2_APP_CLIENT='mini:mini'
# --------------------------- start 开发环境 start ----------------------------------
VITE_APP_DEV_SERVICE = https://stgl.pj.gov.cn/api
# VITE_APP_DEV_SERVICE = https://stgl.pj.gov.cn/api
# VITE_APP_DEV_SERVICE = http://localhost:9999
# VITE_APP_DEV_SERVICE = https://dzc-test-1.fy.takin.cc
VITE_APP_DEV_SERVICE = https://dzc-test-1.fy.takin.cc
# VITE_IMAGE_DEV_PATH = http://localhost:9999
# VITE_IMAGE_DEV_PATH = https://dzc-test-1.fy.takin.cc/api
+37 -13
View File
@@ -2,19 +2,25 @@
import { reactive, ref, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import * as hooks from "@@/hooks"
import { serviceEnum } from "@@/constant"
const { actCouponTypeEnum } = serviceEnum
const emit = defineEmits(["doVisibleQrcode"])
const emit = defineEmits(["doVisibleQrcode", "doUse"])
const props = withDefaults(defineProps<{ detail: any }>(), {
const props = withDefaults(defineProps<{ mode?: 'list' | 'select', detail: any }>(), {
mode: "list",
detail: {},
})
const doVisibleQrcode = () => {
emit("doVisibleQrcode", { type: 'ACT_COUPON', detail: props.detail })
}
const doUse = () => {
emit("doUse", { type: 'ACT_COUPON', detail: props.detail })
}
</script>
<template>
@@ -31,20 +37,28 @@ const doVisibleQrcode = () => {
class="iconfont icon-weishiyong1 absolute font-size-80rpx! color-#cccccc" />
<!-- 已过期 -->
<view v-if="detail.status == 3" class="iconfont icon-yiguoqi absolute font-size-80rpx! color-#cccccc" />
<!-- 已核销 -->
<view v-if="detail.status == 4" class="iconfont icon-yiguoqi absolute font-size-80rpx! color-#cccccc" />
</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 v-if="detail.faceValue">
<view v-if="detail.couponType == '2'" class="flex items-center">
<text class="symbol">立减</text>
<text class="amount">{{ detail.faceValue }}%</text>
</view>
<view v-else class="flex items-center">
<text class="symbol">¥</text>
<text class="amount">{{ hooks.useMoney(detail.faceValue) }}</text>
</view>
</view>
<text class="name">{{ detail.couponName }}</text>
</view>
<view v-if="detail.minPoint" class="condition">
满{{ detail.minPoint }}可用
<view>满{{ hooks.useMoney(detail.minPoint) }}可用</view>
<view class="ml-10rpx">{{ actCouponTypeEnum[detail.couponType] }}</view>
</view>
<view class="scope-text flex-col">
<text>{{ actCouponTypeEnum[detail.couponType] }}</text>
<text>在{{ detail.startTime }}至{{ detail.endTime }}时间段使用</text>
</view>
</view>
@@ -56,10 +70,18 @@ const doVisibleQrcode = () => {
<view class="circle-bottom"></view>
</view>
<!-- 右侧按钮 -->
<view class="coupon-right">
<view v-if="detail.status == 1" class="action-btn" @tap="doVisibleQrcode">核销码</view>
<view v-if="detail.status == 2" class="action-btn disabled">已使用</view>
<view v-if="detail.status == 3" class="action-btn disabled">已过期</view>
<view class="flex-row-center">
<view v-if="mode == 'list'" class="coupon-right">
<view v-if="detail.status == 1">
<view v-if="detail.couponType == '4'" class="action-btn" @tap="doVisibleQrcode">核销码</view>
<view v-else class="action-btn" @tap="doUse">去使用</view>
</view>
<view v-if="detail.status == 2" class="action-btn disabled">已使用</view>
<view v-if="detail.status == 3" class="action-btn disabled">已过期</view>
</view>
<view v-else-if="mode == 'select'">
<view class="action-btn">使用</view>
</view>
</view>
</view>
</template>
@@ -117,7 +139,7 @@ const doVisibleQrcode = () => {
}
.amount {
font-size: 48rpx;
font-size: 28rpx;
font-weight: bold;
margin: 0 8rpx 0 2rpx;
line-height: 1;
@@ -134,6 +156,8 @@ const doVisibleQrcode = () => {
}
.condition {
display: flex;
align-items: center;
font-size: 24rpx;
color: #0094ff;
opacity: 0.8;
@@ -188,7 +212,7 @@ const doVisibleQrcode = () => {
/* 右侧按钮区 */
.coupon-right {
width: 160rpx;
width: 140rpx;
display: flex;
align-items: center;
justify-content: center;
+79 -31
View File
@@ -1,23 +1,23 @@
<template>
<crud-layout title="活动详情" mainIsScroll :bg="filePath(SERVICE_IMAGE.blueBg)">
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
<view v-if="state.actId" class="px-16rpx py-32rpx">
<view v-if="state.actType == 'CANCEL_ACT'"
class="card rounded-lb-0! rounded-rb-0! w-full h-400rpx relative activity-card overflow-hidden">
<view class="ribbon">兑换券</view>
<view class="px-64rpx pt-40rpx flex justify-between items-center font-bold">
<text class="text-32 c-#FF4F3F">限时领取</text>
<text class="text-30 c-#FF4F3F px-8rpx rounded-full bg-#eeeeee44">
<text class="text-32rpx c-#FF4F3F">限时领取</text>
<text class="text-30rpx c-#FF4F3F px-8rpx rounded-full bg-#eeeeee44">
{{ (state.actDetail.startTime?.split(" ")[0] || "") + "~" +
(state.actDetail.expireTime?.split(" ")[0] || "") }}
</text>
</view>
<view class="w-4/5 px-64rpx pt-32rpx text-28 c-#fff">{{ state.actDetail.actTitle }}</view>
<view class="w-4/5 px-64rpx pt-32rpx text-28rpx c-#fff">{{ state.actDetail.actTitle }}</view>
<view class="absolute bottom-100 left-60 text-40 italic">
<text class="color-red mr-16rpx">¥{{ hooks.useMoney(state.actDetail.activeCancelPrice) }}</text>
<text class="text-30 line-through color-red">¥{{ hooks.useMoney(state.actDetail.activeOrgPrice)
<text class="text-30rpx line-through color-red">¥{{ hooks.useMoney(state.actDetail.activeOrgPrice)
}}</text>
</view>
<view class="absolute bottom-40 left-60 text-28 bg-#eeeeee44 c-#fff px-16rpx rounded-full">限量领取 先领先得
<view class="absolute bottom-40 left-60 text-28rpx bg-#eeeeee44 c-#fff px-16rpx rounded-full">限量领取 先领先得
</view>
</view>
<view class="card relative rounded-lb-0! rounded-rb-0! w-full activity-sr-card overflow-hidden"
@@ -31,31 +31,46 @@
class="card relative rounded-lb-0! rounded-rb-0! w-full activity-card overflow-hidden">
<view class="ribbon">{{ actCouponTypeEnum[state.actDetail.type] }}</view>
<view class="px-64rpx pt-40rpx flex justify-between items-center font-bold">
<text class="text-32 c-#FF4F3F">{{ state.actDetail.name }}</text>
<text class="text-30 c-#FF4F3F px-8rpx rounded-full bg-#eeeeee44">
<view v-if="state.actDetail.validType == 1" class="flex items-center">
<text>{{ state.actDetail.validStartTime }}</text>
<text>~</text>
<text>{{ state.actDetail.validEndTime }}</text>
</view>
<text class="text28 c-#FF4F3F">{{ state.actDetail.name }}</text>
<view v-if="state.actDetail.totalCount" class="coupon-stock-badge">
<view class="coupon-stock-dot" />
<text class="coupon-stock-label">剩余</text>
<text class="coupon-stock-value">{{ state.actDetail.surplusCount }}</text>
<text class="coupon-stock-split">/</text>
<text class="coupon-stock-total">{{ state.actDetail.totalCount }}</text>
<text class="coupon-stock-unit">张</text>
</view>
</view>
<view class="px-32rpx pt-32rpx text-26rpx c-#fff indent-1em">
<view v-if="state.actDetail.minPoint">
<text>满{{ hooks.useMoney(state.actDetail.minPoint) }}元,</text>
<text v-if="['2'].includes(state.actDetail.type)">
立减{{ state.actDetail.faceValue }}%
</text>
</view>
<text v-else-if="['1'].includes(state.actDetail.type)">
满减{{ hooks.useMoney(state.actDetail.faceValue) }}元
</text>
<text v-else-if="['3'].includes(state.actDetail.type)">
立减{{ hooks.useMoney(state.actDetail.faceValue) }}元
</text>
</view>
<view class="w-4/5 px-64rpx pt-32rpx text-28 c-#fff">
<view v-if="state.actDetail.validType == '1'">
<view class="px-32rpx pb-32rpx pt-8rpx text-26rpx c-#fff">
<view v-if="state.actDetail.validType == '1'" class="indent-1em">
<text>领取后请在</text>
<text>{{ state.actDetail.validStartTime }}</text>
<text>~</text>
<text>{{ state.actDetail.validEndTime }}</text>
<text>时间段内使用</text>
</view>
<view v-if="state.actDetail.validType == '2'">
<view v-if="state.actDetail.validType == '2'" class="indent-1em">
<text>领取后请在</text>
<text>{{ state.actDetail.validDays }}</text>
<text>天内使用</text>
</view>
</view>
<view v-if="state.actDetail.description"
class="m-l-20rpx m-y-20rpx w-55% text-28 bg-#eeeeee44 c-#fff p-16rpx rounded-16rpx">
class="m-l-20rpx m-y-20rpx w-55% text-28rpx bg-#eeeeee44 c-#fff p-16rpx rounded-16rpx">
{{ state.actDetail.description }}
</view>
</view>
@@ -75,7 +90,7 @@
<template #main-bottom>
<view class="w-full flex-col-center">
<tm-button class="px-256rpx m-16rpx" width="90%" @click="doQrcode">我的礼券</tm-button>
<view class="h-10rpx"></view>
<view class="h-30rpx" />
</view>
</template>
</crud-layout>
@@ -87,10 +102,8 @@ import { reactive, ref, nextTick } from "vue"
import { onLoad } from "@dcloudio/uni-app"
import { actCancel, actComApi, actCouponApi, userApi } from "@/sheep/api"
import * as hooks from "@@/hooks"
import * as router from "@@/router"
import { serviceEnum, storageConstant, SERVICE_IMAGE } from "@@/constant"
import { filePath } from "@@/utils/file"
import { hooks, router } from "@/sheep"
import { serviceEnum, storageConstant } from "@@/constant"
import ModifyUser from "@@/components/modify-user.vue"
@@ -249,18 +262,10 @@ const modifyUserClose = () => {
background-image: url("../../static/page/activity-card-bg.png");
background-repeat: no-repeat;
background-size: contain;
background-position: 150%;
background-position: 130%;
background-color: #1879ff77;
}
.activity-sr-card {
/* background-image: url("../../static/page/act_li_quan.jpg");
background-repeat: no-repeat;
background-size: contain;
background-position: 150%;
background-color: red; */
}
.ribbon {
width: 160rpx;
height: 40rpx;
@@ -273,4 +278,47 @@ const modifyUserClose = () => {
color: #fff;
font-size: 24rpx;
}
.coupon-stock-badge {
display: flex;
align-items: center;
padding: 8rpx 18rpx;
border-radius: 9999rpx;
background: rgba(238, 238, 238, 0.28);
backdrop-filter: blur(2px);
color: #fff;
}
.coupon-stock-dot {
width: 12rpx;
height: 12rpx;
margin-right: 10rpx;
border-radius: 9999rpx;
background: #0094ff;
}
.coupon-stock-label,
.coupon-stock-unit,
.coupon-stock-split,
.coupon-stock-total {
font-size: 24rpx;
line-height: 1;
}
.coupon-stock-value {
margin: 0 4rpx 0 8rpx;
font-size: 24rpx;
font-weight: 700;
line-height: 1;
}
.coupon-stock-total {
margin-left: 4rpx;
opacity: 0.9;
}
.coupon-stock-unit {
margin-left: 6rpx;
opacity: 0.9;
}
</style>
+22 -6
View File
@@ -2,15 +2,13 @@
import { reactive, ref, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import * as router from "@@/router"
import { SERVICE_IMAGE } from "@@/constant/index"
import { filePath } from "@@/utils/file"
import { router } from "@/sheep"
import uqrcode from "@@/utils/uqrcode"
import * as CryptoJS from "crypto-js"
import { actCancel, actComApi, actCouponApi, applyApi } from "@@/api"
import * as hooks from "@@/hooks"
import { hooks } from "@/sheep"
import { serviceEnum } from "@@/constant"
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
@@ -20,6 +18,8 @@ import CouponCard from "./components/coupon-card.vue"
const { actCouponTypeEnum } = serviceEnum
const { scanCodeAction, processUrlScanResult, processBase64ScanResult } = hooks.useScanCode(false)
const pageRef = ref<any>(null)
const showQrCodeDialog = ref(false)
@@ -72,6 +72,22 @@ const doTab = (e: any) => {
getUserTotal()
}
// 去使用
const doUse = async (detail: any) => {
const { type, detail: actDetail } = detail
console.log("去使用:", type, actDetail);
const scanResult = await scanCodeAction()
if (!scanResult) return uni.showToast({ title: "扫码错误", icon: "none" })
// 判断扫码结果
if (scanResult.startsWith("http")) {
// 扫码结果是url链接
processUrlScanResult(scanResult.replace("/#", ""))
} else {
// 扫码结果是base64字符串
processBase64ScanResult(scanResult)
}
}
const doVisibleQrcode = async (detail: any) => {
const { type, detail: actDetail } = detail
console.log("生成二维码:", type, actDetail);
@@ -143,7 +159,7 @@ const genVerificationCode = async (params: Record<string, any>) => {
</script>
<template>
<crud-layout title="我参加的活动" mainIsScroll :bg="filePath(SERVICE_IMAGE.blueBg)">
<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"
@@ -157,7 +173,7 @@ const genVerificationCode = async (params: Record<string, any>) => {
<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" />
@do-visible-qrcode="doVisibleQrcode" @doUse="doUse" />
</view>
<view v-if="state.tabId == 2">
<cancel-card v-for="item in dynamicPageHook.list" :key="item.id" :detail="item"
@@ -0,0 +1,193 @@
<script setup lang="ts">
import * as hooks from "@@/hooks"
const emit = defineEmits(["doSelect"])
const props = withDefaults(defineProps<{ selectId?: any, detail: any, disabled?: boolean }>(), {
selectId: null,
detail: {},
disabled: false,
})
const doSelect = () => {
if (props.disabled) return
emit("doSelect", { type: 'ACT_COUPON', detail: props.detail })
}
</script>
<template>
<view class="border-4rpx border-solid border-#efefef coupon-item"
:class="{ 'border-4rpx border-solid border-blue!': selectId == detail.id && !disabled, 'coupon-item-disabled': disabled }"
@tap="doSelect">
<!-- 左侧主体 -->
<view class="coupon-left relative">
<image class="size-60rpx rounded-8rpx flex-shrink-0 m-r-10rpx bg-#f0f0f0" src="/static/icon/act_coupon.png"
mode="aspectFill" />
<view class="absolute top-0rpx right-80rpx">
<!-- 未使用 -->
<view v-if="detail.status == 1" class="iconfont icon-weishiyong absolute font-size-80rpx! color-blue" />
<!-- 已使用 -->
<view v-if="detail.status == 2"
class="iconfont icon-weishiyong1 absolute font-size-80rpx! color-#cccccc" />
<!-- 已过期 -->
<view v-if="detail.status == 3" class="iconfont icon-yiguoqi absolute font-size-80rpx! color-#cccccc" />
<!-- 已核销 -->
<view v-if="detail.status == 4" class="iconfont icon-yiguoqi absolute font-size-80rpx! color-#cccccc" />
</view>
<view class="info-box">
<view class="price-row">
<view v-if="detail.faceValue">
<view v-if="detail.couponType == '2'" class="flex items-center">
<text class="symbol">立减</text>
<text class="amount">{{ detail.faceValue }}%</text>
</view>
<view v-else class="flex items-center">
<text class="symbol">¥</text>
<text class="amount">{{ hooks.useMoney(detail.faceValue) }}</text>
</view>
</view>
<text class="name">{{ detail.couponName }}</text>
</view>
<view v-if="detail.minPoint" class="condition">
满{{ hooks.useMoney(detail.minPoint) }}可用
</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="flex-row-center">
<view class="action-btn" :class="{ disabled }">{{ disabled ? "不可用" : "使用" }}</view>
</view>
</view>
</template>
<style scoped lang="scss">
.coupon-item {
display: flex;
align-items: stretch;
margin: 8rpx 0;
border-radius: 16rpx;
overflow: hidden;
position: relative;
}
.coupon-item-disabled {
opacity: 0.6;
}
/* 左侧布局 */
.coupon-left {
flex: 1;
display: flex;
align-items: center;
padding: 12rpx 0 12rpx 12rpx;
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: 60rpx;
height: 60rpx;
border-radius: 8rpx;
background-color: #f0f0f0;
margin-right: 20rpx;
flex-shrink: 0;
}
.info-box {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
height: 80rpx;
}
.price-row {
display: flex;
align-items: center;
color: #0094ff;
}
.symbol {
font-size: 28rpx;
font-weight: 600;
}
.amount {
font-size: 28rpx;
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;
}
/* 右侧按钮区 */
.coupon-right {
width: 140rpx;
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;
&.disabled {
background: #cccccc;
}
}
</style>
+132 -13
View File
@@ -1,19 +1,32 @@
<template>
<crud-layout :title="scanPayState.mchName" :bg="filePath(SERVICE_IMAGE.blueBg)" ref="crudLayoutRef">
<crud-layout mainIsScroll :title="scanPayState.mchName" :bg="filePath(SERVICE_IMAGE.blueBg)" ref="crudLayoutRef">
<view class="h-screen flex flex-col">
<tm-sheet :margin="[0, 10]" class="flex flex-row">
<span class="text-2xl text-[#333] mr-8rpx line-height-normal">¥</span>
<span class="text-3xl font-medium text-[#333]">{{ displayAmount }}</span>
<tm-sheet :margin="[0, 10]" class="flex flex-col ">
<view class="flex flex-row items-center">
<span class="text-48rpx text-[#333] mr-8rpx line-height-normal">¥</span>
<span class="text-72rpx font-medium text-[#333]">{{ displayAmount }}</span>
</view>
<view v-if="couponDiscountAmount > 0" class="mt-10rpx flex items-center text-24rpx">
<view class="px-12rpx py-4rpx rounded-full bg-#0094ff1a c-#0094ff font-600">
已优惠 ¥{{ couponDiscountDisplay }}
</view>
<view class="ml-12rpx c-#666">
券后实付
<text class="c-#0094ff font-600">¥{{ couponPayableDisplay }}</text>
</view>
</view>
<view v-else-if="selectCoupon.id && couponUnavailableText" class="mt-10rpx text-24rpx c-#999">
{{ couponUnavailableText }}
</view>
</tm-sheet>
<tm-sheet v-if="walletList.length > 0" :margin="[0, 10]" class="flex flex-col">
<view class="text-gray-700 font-bold text-28rpx mb-8rpx">选择支付钱包:</view>
<view v-for="(v, index) in walletList" :key="index"
class="border-6 border-solid border-#efefef my-16rpx duration-200 flex justify-between items-center p-24rpx rounded-2"
class="border-6 border-solid border-#efefef my-8rpx duration-200 flex justify-between items-center p-18rpx rounded-2"
:class="{ 'border-6 border-solid border-blue!': selectWallet.id == v.id }"
@click="selectWallet = v">
<view class="flex items-center">
<image class="size-70rpx mr-16rpx" :src="filePath(v.walletTypeIcon)" />
<image class="size-50rpx mr-8rpx" :src="filePath(v.walletTypeIcon)" />
<view class="text-32rpx">{{ v.walletTypeName }}</view>
</view>
<view class="text-28rpx flex items-center">
@@ -22,29 +35,36 @@
</view>
</view>
</tm-sheet>
<tm-sheet v-if="actCouponList.length > 0" :margin="[0, 10]" class="flex flex-col">
<view class="text-gray-700 font-bold text-28rpx mb-8rpx">选择优惠券:</view>
<select-coupon-card :selectId="selectCoupon.id" v-for="item in actCouponList" :key="item.id"
:detail="item" :disabled="!isCouponAvailable(item)" @do-select="doSelectActCoupon" />
</tm-sheet>
<view class="h-30rpx" />
<tm-button class="mt-30rpx" :block="true" @click="handlePay()" :disabled="loading">支 付</tm-button>
<view class="h-30rpx" />
<number-keyboard v-if="numberKeyboardShow" v-model="amount" />
</view>
</crud-layout>
</template>
<script setup lang="ts">
import { ref, computed, reactive, onMounted, nextTick } from "vue"
import { ref, computed, reactive, onMounted, nextTick, watch } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import payApi from "@@/api/pay"
import { merchantApi, orderApi, walletApi } from "@/sheep/api"
import { merchantApi, orderApi, walletApi, actCouponApi } from "@/sheep/api"
import * as hooks from "@@/hooks"
import * as router from "@@/router"
import { filePath } from "@/sheep/utils/file"
import { displayPrice, storePrice } from "@/sheep/utils/format-money"
import { usePayStore } from "@/sheep/stores/pay"
import { isEmpty } from "radash"
import NumberKeyboard from "@/sheep/components/number-keyboard.vue"
import { SERVICE_IMAGE } from "@@/constant/index"
import NumberKeyboard from "@/sheep/components/number-keyboard.vue"
import SelectCouponCard from "./components/select-coupon-card.vue"
const crudLayoutRef = ref<any>(null)
const loading = ref(false)
const scanPayState = reactive({
@@ -54,7 +74,9 @@ const scanPayState = reactive({
action: "", // action === "harQrCodeScanPay":扫收款机上生成的码支付
})
const walletList = ref<any>([]) // 钱包列表
const actCouponList = ref<any>([]) // 优惠券列表
const selectWallet = ref<any>({}) // 当前选中的钱包
const selectCoupon = ref<any>({}) // 当前选中的优惠券
const orderInfo = ref<any>({}) // 订单信息
const amount = ref("") // 键盘输入的金额
const numberKeyboardShow = ref(true)
@@ -87,6 +109,7 @@ onLoad(async (options: any) => {
onShow(async () => {
try {
await getWalletList()
await getActCouponApi()
} finally {
uni.hideLoading()
}
@@ -104,6 +127,36 @@ const getMchDetail = async (mchId: string) => {
scanPayState.mchName = data.name
}
const doSelectActCoupon = (e: any) => {
const { detail } = e
if (!isCouponAvailable(detail)) {
uni.showToast({ title: `当前优惠券满${priceCentToYuan(detail.minPoint).toFixed(2)}元可用`, icon: "none" })
return
}
// 如果id一样就取消选择
if (selectCoupon.value?.id == detail.id) {
selectCoupon.value = {}
return
}
selectCoupon.value = detail
}
// 获取优惠券
const getActCouponApi = async () => {
try {
const { code, data }: any = await actCouponApi.mchList({
scopeId: scanPayState.mchId
})
if (code !== 0) return
actCouponList.value = data ?? []
if (actCouponList.value.length > 0) {
selectCoupon.value = actCouponList.value.find((item: any) => isCouponAvailable(item)) || {}
}
} catch (error) {
console.log("获取优惠券意外的错误error:", error);
}
}
// 获取钱包列表
const getWalletList = async () => {
const { code, data } = await walletApi.all()
@@ -127,6 +180,71 @@ const displayAmount = computed(() => {
return amount.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
})
const amountNumber = computed(() => {
const value = Number(String(amount.value || 0).replace(/,/g, ""))
return Number.isNaN(value) ? 0 : value
})
const priceCentToYuan = (price: any) => {
return Number(displayPrice(Number(price || 0), "", 2))
}
const isCouponAvailable = (coupon: any) => {
if (!coupon) return false
const minPoint = priceCentToYuan(coupon.minPoint)
if (minPoint > 0 && amountNumber.value < minPoint) return false
return true
}
const couponPayableAmount = computed(() => {
if (!selectCoupon.value?.id || amountNumber.value <= 0) return amountNumber.value
const couponType = String(selectCoupon.value.couponType || "")
const minPoint = priceCentToYuan(selectCoupon.value.minPoint)
const useAmount = priceCentToYuan(selectCoupon.value.useAmount)
// 满减券: 不足门槛不优惠, 否则按 useAmount 作为券后金额
if (couponType === "1") {
const faceValue = priceCentToYuan(selectCoupon.value.faceValue)
if (minPoint > 0 && amountNumber.value < minPoint) return amountNumber.value
const payable = useAmount > 0 ? useAmount : amountNumber.value - faceValue
return Math.max(0, Number(payable.toFixed(2)))
}
// 折扣券: 不足门槛不优惠, totalPrice * faceValue / 100
if (couponType === "2") {
const faceValue = Number(selectCoupon.value.faceValue || 0)
if (minPoint > 0 && amountNumber.value < minPoint) return amountNumber.value
const _p = Math.max(0, Number(((amountNumber.value * (100 - faceValue)) / 100).toFixed(2)))
return _p
}
// 立减券: 按 useAmount 作为券后金额
if (couponType === "3") {
const faceValue = priceCentToYuan(selectCoupon.value.faceValue)
const payable = useAmount > 0 ? useAmount : amountNumber.value - faceValue
return Math.max(0, Number(payable.toFixed(2)))
}
return amountNumber.value
})
const couponDiscountAmount = computed(() => {
return Math.max(0, Number((amountNumber.value - couponPayableAmount.value).toFixed(2)))
})
const couponDiscountDisplay = computed(() => couponDiscountAmount.value.toFixed(2))
const couponPayableDisplay = computed(() => couponPayableAmount.value.toFixed(2))
const couponUnavailableText = computed(() => {
if (!selectCoupon.value?.id || amountNumber.value <= 0) return ""
const minPoint = priceCentToYuan(selectCoupon.value.minPoint)
if (minPoint > 0 && amountNumber.value < minPoint) {
return `当前优惠券满${minPoint.toFixed(2)}元可用`
}
return ""
})
watch(amountNumber, () => {
if (selectCoupon.value?.id && !isCouponAvailable(selectCoupon.value)) {
selectCoupon.value = {}
}
})
// 点击支付
const handlePay = () => {
if (!scanPayState.mchId) return uni.showToast({ title: "扫码参数不存在", icon: "none" })
@@ -158,6 +276,7 @@ const pay = async () => {
merchantId: scanPayState.mchId,
customPrice: storePrice(Number(amount.value)),
walletId: selectWallet.value.id,
actCouponRecordId: selectCoupon.value?.id || "",
})
if (code != 0) return uni.showToast({ title: msg || "支付失败", icon: "error" })
}
@@ -166,7 +285,7 @@ const pay = async () => {
usePayStore().setScanPayData({
mchId: scanPayState.mchId,
mchName: scanPayState.mchName,
payPrice: Number(amount.value).toFixed(2),
payPrice: couponPayableDisplay.value ?? Number(amount.value).toFixed(2),
})
router.jump("/pages/pay/payComplete", {}, 2)
} catch (error: any) {
-1
View File
@@ -40,7 +40,6 @@
<view class="font-bold mt-48rpx">
<tm-button block @click="confirmTransfer" v-if="payStore.sourceWalletData.walletTypeId">确定转账</tm-button>
</view>
<number-keyboard v-model="amount" />
</crud-layout>
+8 -1
View File
@@ -123,7 +123,14 @@ const doRefund = (v: any) => {
class="second-text-30 mr-16rpx">
共 {{ v.carList.length }} 件
</text>
<text class="main-text-30 color-red!">¥ {{ hooks.useMoney(v.totalPrice) }}</text>
<view class="flex-col items-center">
<text v-if="v.couponPrice || v.extraPrice" class="main-text-26 mr-10rpx line-through">
¥{{ hooks.useMoney(v.totalPrice) }}
</text>
<text class="main-text-30 color-red!">
¥{{ hooks.useMoney(v.payPrice) }}
</text>
</view>
</view>
</view>
</view>
+9
View File
@@ -19,4 +19,13 @@ export default {
method: "GET",
params,
}),
/**
* 用户领取核销券列表
*/
mchList: (data: any) =>
request({
url: "/wallet/act-coupon-record/app/mch-list",
method: "POST",
data,
}),
}
@@ -46,6 +46,7 @@ function getNavTop() {
}
function doToDashboard() {
tabbarStore.setRootActiveIndex(0)
router.jump("/pages/root/dashboard", {}, 3)
}
</script>
@@ -46,6 +46,7 @@ function doBackClick() {
}
function doToDashboard() {
tabbarStore.setRootActiveIndex(0)
router.jump("/pages/root/dashboard", {}, 3)
}
</script>
@@ -182,16 +182,17 @@ defineExpose({
<slot name="main-bottom"></slot>
</view>
</view>
<view class="layout_foot">
<tm-tabbar v-if="tabbarStore.isTabbar && tabbarStore.rootActiveIndex != 2" class="layout_foot"
:activeIndex="tabbarStore.activeIndex" :list="tabbarStore.pageTabbarList" :outIndex="-1" fontSize="12px"
iconSize="24px" bgColor="#F6F5FA" :zIndex="99999" darkBgColor="#F6F5FA" borderColor="#DBDDDE"
color="#8E8E8E" selected-color="#0094ff" position="fixed" @change="tabbarChange" />
<tm-tabbar v-else-if="tabbarStore.isDashboardPage" class="layout_foot"
:activeIndex="tabbarStore.rootActiveIndex" :list="tabbarStore.rootPageTabbarList" :outIndex="1"
fontSize="12px" iconSize="24px" bgColor="#F6F5FA" :zIndex="99999" darkBgColor="#F6F5FA"
borderColor="#DBDDDE" color="#8E8E8E" outBgColor="#0094ff" selected-color="#0094ff" position="fixed"
@change="rootTabbarChange" />
<view v-if="tabbarStore.isTabbar && tabbarStore.rootActiveIndex != 2" class="layout_foot">
<tm-tabbar class="layout_foot" :activeIndex="tabbarStore.activeIndex" :list="tabbarStore.pageTabbarList"
:outIndex="-1" fontSize="12px" iconSize="24px" bgColor="#F6F5FA" :zIndex="99999" darkBgColor="#F6F5FA"
borderColor="#DBDDDE" color="#8E8E8E" selected-color="#0094ff" position="fixed"
@change="tabbarChange" />
</view>
<view class="layout_foot" v-else-if="tabbarStore.isDashboardPage">
<tm-tabbar class="layout_foot" :activeIndex="tabbarStore.rootActiveIndex"
:list="tabbarStore.rootPageTabbarList" :outIndex="1" fontSize="12px" iconSize="24px" bgColor="#F6F5FA"
:zIndex="99999" darkBgColor="#F6F5FA" borderColor="#DBDDDE" color="#8E8E8E" outBgColor="#0094ff"
selected-color="#0094ff" position="fixed" @change="rootTabbarChange" />
</view>
<VerifyDeviceImpModel ref="verifyDeviceImpModelRef" :close="verifyDeviceImpModelClose" />
</view>
+89 -28
View File
@@ -1,33 +1,35 @@
<template>
<view class="bg-[#f5f5f5] px-8rpx py-16rpx rounded-4 fixed bottom-0 left-0 right-0 w-full">
<view class="flex justify-end">
<image v-if="show" class="w-[50rpx] h-[50rpx] mx-16rpx mb-8rpx" src="@/static/page/fold.svg"
@click="show = !show" />
<image v-if="!show" class="w-[50rpx] h-[50rpx] mx-16rpx mb-8rpx" src="@/static/page/unfold.svg"
@click="show = !show" />
</view>
<view v-if="show" class="flex mb-20rpx" v-for="(row, rowIndex) in keyboardLayout" :key="rowIndex">
<view
class="flex-1 h-[90rpx] flex items-center justify-center bg-white text-2xl text-[#333] mx-[5px] rounded-lg cursor-pointer select-none active:bg-[#f0f0f0]"
v-for="(key, keyIndex) in row" :key="keyIndex" @click="handleKeyPress(key.toString())">
{{ key }}
<view class="keyboard-wrap">
<view class="keyboard-panel" :class="{ 'keyboard-panel--open': show }">
<view class="keyboard-toggle" @tap.stop="toggleShow">
<image class="keyboard-toggle__icon"
:src="show ? '/static/page/fold.svg' : '/static/page/unfold.svg'" />
</view>
</view>
<view v-if="show" class="flex mb-0rpx">
<view
class="flex-1 h-[90rpx] flex items-center justify-center bg-white text-2xl text-[#333] mx-[5px] rounded-lg cursor-pointer select-none active:bg-[#f0f0f0]"
@click="handleKeyPress('.')">
.
</view>
<view
class="flex-auto h-[90rpx] flex items-center justify-center bg-white text-2xl text-[#333] mx-[5px] rounded-lg cursor-pointer select-none active:bg-[#f0f0f0]"
@click="handleKeyPress('0')">
0
</view>
<view
class="flex-1 h-[90rpx] flex items-center justify-center bg-white mx-[5px] rounded-lg cursor-pointer select-none active:bg-[#f0f0f0]"
@click="handleDelete">
<image class="size-45rpx" src="@/static/page/keyboard-delete.svg" />
<view v-show="show" class="keyboard-content">
<view class="flex mb-20rpx" v-for="(row, rowIndex) in keyboardLayout" :key="rowIndex">
<view
class="flex-1 h-[90rpx] flex items-center justify-center bg-white text-2xl text-[#333] mx-[5px] rounded-lg cursor-pointer select-none active:bg-[#f0f0f0]"
v-for="(key, keyIndex) in row" :key="keyIndex" @tap="handleKeyPress(key.toString())">
{{ key }}
</view>
</view>
<view class="flex mb-0rpx">
<view
class="flex-1 h-[90rpx] flex items-center justify-center bg-white text-2xl text-[#333] mx-[5px] rounded-lg cursor-pointer select-none active:bg-[#f0f0f0]"
@tap="handleKeyPress('.')">
.
</view>
<view
class="flex-auto h-[90rpx] flex items-center justify-center bg-white text-2xl text-[#333] mx-[5px] rounded-lg cursor-pointer select-none active:bg-[#f0f0f0]"
@tap="handleKeyPress('0')">
0
</view>
<view
class="flex-1 h-[90rpx] flex items-center justify-center bg-white mx-[5px] rounded-lg cursor-pointer select-none active:bg-[#f0f0f0]"
@tap="handleDelete">
<image class="size-45rpx" src="@/static/page/keyboard-delete.svg" />
</view>
</view>
</view>
</view>
</view>
@@ -45,6 +47,10 @@ const keyboardLayout = ref([
])
const show = ref(true)
const toggleShow = () => {
show.value = !show.value
}
// 按下键盘
const handleKeyPress = (key) => {
if (key === "." && amount.value.includes(".")) return
@@ -74,3 +80,58 @@ const handleDelete = () => {
}
}
</script>
<style scoped lang="scss">
.keyboard-wrap {
--keyboard-toggle-size: 82rpx;
--keyboard-expanded-height: 538rpx;
position: fixed;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 1000001;
}
.keyboard-panel {
position: absolute;
right: 0;
bottom: calc(var(--keyboard-expanded-height) - var(--keyboard-toggle-size));
width: 82rpx;
padding: 12rpx 0 12rpx 12rpx;
background: transparent;
overflow: hidden;
pointer-events: auto;
transition: width 0.2s ease, padding 0.2s ease, background-color 0.2s ease, bottom 0.2s ease;
&--open {
bottom: 0;
width: 100%;
padding: 12rpx;
background: #f5f5f5;
border-radius: 24rpx 24rpx 0 0;
box-shadow: 0 0 24rpx rgba(0, 0, 0, 0.08);
}
}
.keyboard-toggle {
width: 82rpx;
height: 82rpx;
margin-left: auto;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 24rpx 0 0 24rpx;
box-shadow: 0 0 24rpx rgba(0, 0, 0, 0.08);
}
.keyboard-toggle__icon {
width: 50rpx;
height: 50rpx;
}
.keyboard-content {
margin-top: 12rpx;
}
</style>
+1
View File
@@ -76,6 +76,7 @@ const actUserReceiveEnum = {
"4": "已领取,核销码在我的礼券中",
"5": "用户不能参加此活动",
"6": "其他错误",
"7": "已领完",
"99": "用户可以参加活动",
// 特殊
"-100": "仅限生日当月使用",
+6 -1
View File
@@ -1,8 +1,13 @@
import { router } from ".."
import { getUrlParamsObj } from "../utils/app"
import { getUrlParamsObj, isDevEnv } from "../utils/app"
export function useScanCode(actModalShow) {
async function scanCodeAction(): Promise<string | undefined> {
if (isDevEnv()) {
const testQrCode = "https://stgl.pj.gov.cn/h5/#/pages/pay/scanPay?id=1931916121241079810"
uni.showModal({ content: "开发环境不是浙政钉和诗画环境,扫码仅供测试" })
return Promise.resolve(testQrCode)
}
const scanResult = await new Promise<string>((resolve, reject) => {
uni.scanCode({
success: (res) => {