Merge branch 'master' of https://gitea.zianxin.com/zax/biz-mobile-react-cloud
This commit is contained in:
commit
ed0389cde4
12 files changed
+519
-74
No files matched your search
@@ -26,7 +26,7 @@ VITE_OAUTH2_APP_CLIENT='mini:mini'
|
||||
# --------------------------- 开发环境 api地址 ----------------------------------
|
||||
# VITE_APP_DEV_SERVICE = https://stgl.pj.gov.cn/api # 域名线上
|
||||
#VITE_APP_DEV_SERVICE = http://59.202.170.152:81/api # 信创线上
|
||||
; VITE_APP_DEV_SERVICE = https://dzc-test-1.fy.takin.cc # 邓-本地
|
||||
# VITE_APP_DEV_SERVICE = https://dzc-test-1.fy.takin.cc # 邓-本地
|
||||
VITE_APP_DEV_SERVICE = http://127.0.0.1:9999 # 邓-本地
|
||||
# VITE_APP_DEV_SERVICE = http://123.157.250.162:8089/api # 测试线上
|
||||
|
||||
|
||||
Generated
+43
@@ -8,6 +8,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@babel/runtime':
|
||||
specifier: ^7.26.0
|
||||
version: 7.26.7
|
||||
'@dcloudio/uni-app':
|
||||
specifier: 3.0.0-4020920240930001
|
||||
version: 3.0.0-4020920240930001(@dcloudio/types@3.4.14)(postcss@8.5.1)(rollup@4.34.2)(vue@3.5.13(typescript@4.9.5))
|
||||
@@ -65,6 +68,9 @@ importers:
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
gdt-jsapi:
|
||||
specifier: ^1.9.55-beta.14
|
||||
version: 1.9.55-beta.14
|
||||
luch-request:
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.1
|
||||
@@ -80,6 +86,9 @@ importers:
|
||||
radash:
|
||||
specifier: ^12.1.0
|
||||
version: 12.1.0
|
||||
vconsole:
|
||||
specifier: ^3.15.1
|
||||
version: 3.15.1
|
||||
vue:
|
||||
specifier: ^3.3.11
|
||||
version: 3.5.13(typescript@4.9.5)
|
||||
@@ -1304,6 +1313,9 @@ packages:
|
||||
'@polka/url@1.0.0-next.28':
|
||||
resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==}
|
||||
|
||||
'@remax/mini-types@0.1.0':
|
||||
resolution: {integrity: sha512-bVDcbUrp6p0PZwfS0xGVQS6k9f8B35BtZaYt/W/IGKh7VALYQ+tS2KcytLRmIJ/Vmohaw5ikIRN89I+frPdh4g==}
|
||||
|
||||
'@rollup/pluginutils@5.1.4':
|
||||
resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -2071,6 +2083,10 @@ packages:
|
||||
copy-anything@2.0.6:
|
||||
resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
|
||||
|
||||
copy-text-to-clipboard@3.2.2:
|
||||
resolution: {integrity: sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
core-js-compat@3.40.0:
|
||||
resolution: {integrity: sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==}
|
||||
|
||||
@@ -2430,6 +2446,9 @@ packages:
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
gdt-jsapi@1.9.55-beta.14:
|
||||
resolution: {integrity: sha512-OJ9EVvDGEVuBbV8LimUeMjjBgA2Fx9rChrzS1OyxZ1gKsc9pHp9T0hl1QeUfk2vZyOzK3EvAX4G8iUspy2XJRA==}
|
||||
|
||||
generic-names@4.0.0:
|
||||
resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==}
|
||||
|
||||
@@ -3051,6 +3070,9 @@ packages:
|
||||
muggle-string@0.3.1:
|
||||
resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==}
|
||||
|
||||
mutation-observer@1.0.3:
|
||||
resolution: {integrity: sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA==}
|
||||
|
||||
nanoid@3.3.8:
|
||||
resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
@@ -3872,6 +3894,9 @@ packages:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
vconsole@3.15.1:
|
||||
resolution: {integrity: sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==}
|
||||
|
||||
vite@5.2.8:
|
||||
resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
@@ -6066,6 +6091,8 @@ snapshots:
|
||||
|
||||
'@polka/url@1.0.0-next.28': {}
|
||||
|
||||
'@remax/mini-types@0.1.0': {}
|
||||
|
||||
'@rollup/pluginutils@5.1.4(rollup@4.34.2)':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
@@ -6980,6 +7007,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-what: 3.14.1
|
||||
|
||||
copy-text-to-clipboard@3.2.2: {}
|
||||
|
||||
core-js-compat@3.40.0:
|
||||
dependencies:
|
||||
browserslist: 4.24.4
|
||||
@@ -7327,6 +7356,11 @@ snapshots:
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
gdt-jsapi@1.9.55-beta.14:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.7
|
||||
'@remax/mini-types': 0.1.0
|
||||
|
||||
generic-names@4.0.0:
|
||||
dependencies:
|
||||
loader-utils: 3.3.1
|
||||
@@ -8151,6 +8185,8 @@ snapshots:
|
||||
|
||||
muggle-string@0.3.1: {}
|
||||
|
||||
mutation-observer@1.0.3: {}
|
||||
|
||||
nanoid@3.3.8: {}
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
@@ -8981,6 +9017,13 @@ snapshots:
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
vconsole@3.15.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.7
|
||||
copy-text-to-clipboard: 3.2.2
|
||||
core-js: 3.40.0
|
||||
mutation-observer: 1.0.3
|
||||
|
||||
vite@5.2.8(@types/node@18.19.75)(less@4.3.0)(sass@1.71.1)(terser@5.37.0):
|
||||
dependencies:
|
||||
esbuild: 0.20.2
|
||||
|
||||
@@ -13,14 +13,8 @@
|
||||
</view>
|
||||
|
||||
<view v-if="imageList.length" class="image-list">
|
||||
<image
|
||||
v-for="(src, index) in imageList"
|
||||
:key="index"
|
||||
class="detail-image"
|
||||
:src="src"
|
||||
mode="aspectFill"
|
||||
@tap="previewImage(index)"
|
||||
/>
|
||||
<image v-for="(src, index) in imageList" :key="index" class="detail-image" :src="src" mode="aspectFill"
|
||||
@tap="previewImage(index)" />
|
||||
</view>
|
||||
|
||||
<view class="detail-section">
|
||||
@@ -95,8 +89,8 @@ const imageList = computed(() => {
|
||||
const rawList = Array.isArray(imgs)
|
||||
? imgs
|
||||
: imgs
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
|
||||
return rawList
|
||||
.map((item) => toAbsoluteUrl(item))
|
||||
@@ -106,11 +100,7 @@ const imageList = computed(() => {
|
||||
const descriptionNodes = computed(() => {
|
||||
const desc = detail.value?.description
|
||||
if (!desc) return ''
|
||||
return desc.replace(/<img\s+[^>]*src=["']([^"']+)["']/gi, (match, src) => {
|
||||
const fullSrc = toAbsoluteUrl(src)
|
||||
if (!fullSrc) return match
|
||||
return match.replace(src, fullSrc)
|
||||
})
|
||||
return desc?.replaceAll(`<img src="/api`, `<img src="https://stgl.pj.gov.cn/api`)
|
||||
})
|
||||
|
||||
const contactInfo = computed(() => {
|
||||
@@ -274,4 +264,3 @@ onLoad((options: Record<string, any>) => {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,11 +31,7 @@ const toAbsoluteUrl = (url?: string | null) => {
|
||||
const richTextNodes = computed(() => {
|
||||
const html = detail.value.richTextContent
|
||||
if (!html) return ""
|
||||
return html.replace(/<img\s+[^>]*src=["']([^"']+)["']/gi, (match, src) => {
|
||||
const fullSrc = toAbsoluteUrl(src)
|
||||
if (!fullSrc) return match
|
||||
return match.replace(src, fullSrc)
|
||||
})
|
||||
return html?.replaceAll(`<img src="/api`, `<img src="https://stgl.pj.gov.cn/api`)
|
||||
})
|
||||
|
||||
onLoad(async (options: any) => {
|
||||
|
||||
@@ -2,19 +2,25 @@
|
||||
import { reactive, ref, computed } from "vue"
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
||||
|
||||
import { hooks, router } from "@/sheep"
|
||||
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>
|
||||
@@ -36,17 +42,23 @@ const doVisibleQrcode = () => {
|
||||
</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>
|
||||
@@ -58,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>
|
||||
@@ -119,7 +139,7 @@ const doVisibleQrcode = () => {
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 48rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
margin: 0 8rpx 0 2rpx;
|
||||
line-height: 1;
|
||||
@@ -136,6 +156,8 @@ const doVisibleQrcode = () => {
|
||||
}
|
||||
|
||||
.condition {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: #0094ff;
|
||||
opacity: 0.8;
|
||||
@@ -190,7 +212,7 @@ const doVisibleQrcode = () => {
|
||||
|
||||
/* 右侧按钮区 */
|
||||
.coupon-right {
|
||||
width: 160rpx;
|
||||
width: 140rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -30,24 +30,39 @@
|
||||
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-8 pt-5 flex justify-between items-center font-bold">
|
||||
<text class="text-32 c-#FF4F3F">{{ state.actDetail.name }}</text>
|
||||
<text class="text-30 c-#FF4F3F px-1 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-4 pt-32rpx text-26 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-8 pt-4 text-28 c-#fff">
|
||||
<view v-if="state.actDetail.validType == '1'">
|
||||
<view class="px-4 pb-32rpx pt-8rpx text-26 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>
|
||||
@@ -244,18 +259,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;
|
||||
@@ -268,4 +275,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>
|
||||
@@ -18,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)
|
||||
@@ -70,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);
|
||||
@@ -155,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 { hooks } from "@/sheep"
|
||||
|
||||
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>
|
||||
+135
-18
@@ -1,24 +1,33 @@
|
||||
<template>
|
||||
<crud-layout bg="/static/page/blue-bg.png">
|
||||
<view class="h-screen flex flex-col">
|
||||
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<view class="flex flex-col">
|
||||
<view class="mx-auto text-34 py-2 h-80rpx">{{ scanPayState.mchName }}</view>
|
||||
|
||||
<tm-sheet :margin="[0, 10]" class="flex flex-row">
|
||||
<span class="text-2xl text-[#333] mr-1 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-end">
|
||||
<span class="text-2xl text-[#333] mr-1 line-height-normal">¥</span>
|
||||
<span class="text-3xl 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-28 mb-1">选择支付钱包:</view>
|
||||
<view
|
||||
v-for="(v, index) in walletList"
|
||||
:key="index"
|
||||
class="border-6 border-solid border-#efefef my-2 duration-200 flex justify-between items-center p-3 rounded-2"
|
||||
:class="{ 'border-6 border-solid border-blue!': selectWallet.id == v.id }"
|
||||
@click="selectWallet = v"
|
||||
>
|
||||
<view v-for="(v, index) in walletList" :key="index"
|
||||
class="border-4rpx border-solid border-#efefef my-8rpx duration-200 flex justify-between items-center p-18rpx rounded-2"
|
||||
:class="{ 'border-4rpx border-solid border-blue!': selectWallet.id == v.id }"
|
||||
@click="selectWallet = v">
|
||||
<view class="flex items-center">
|
||||
<image class="w-70 h-70 mr-2" :src="filePath(v.walletTypeIcon)" />
|
||||
<image class="w-50 h-50 mr-8rpx" :src="filePath(v.walletTypeIcon)" />
|
||||
<view class="text-32">{{ v.walletTypeName }}</view>
|
||||
</view>
|
||||
<view class="text-28 flex items-center">
|
||||
@@ -28,7 +37,14 @@
|
||||
</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-28 mb-1">选择优惠券:</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>
|
||||
|
||||
<tm-button class="mt-1" :block="true" @click="handlePay()" :disabled="loading">支 付</tm-button>
|
||||
<view class="h-120rpx" />
|
||||
|
||||
<number-keyboard v-if="numberKeyboardShow" v-model="amount" />
|
||||
</view>
|
||||
@@ -36,17 +52,19 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, reactive } from "vue"
|
||||
import { ref, computed, reactive, 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 { hooks } from "@@/index"
|
||||
import { router } from "@/sheep"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import { displayPrice, storePrice } from "@/sheep/utils/formatMoney"
|
||||
import { usePayStore } from "@/sheep/stores/pay"
|
||||
import { isEmpty } from "radash"
|
||||
|
||||
import NumberKeyboard from "@/sheep/components/number-keyboard.vue"
|
||||
import SelectCouponCard from "./components/select-coupon-card.vue"
|
||||
|
||||
const loading = ref(false)
|
||||
const scanPayState = reactive({
|
||||
@@ -56,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)
|
||||
@@ -90,6 +110,7 @@ onLoad(async (options: any) => {
|
||||
onShow(async () => {
|
||||
try {
|
||||
await getWalletList()
|
||||
await getActCouponApi()
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
@@ -102,6 +123,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()
|
||||
@@ -125,6 +176,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" })
|
||||
@@ -156,6 +272,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" })
|
||||
}
|
||||
@@ -164,7 +281,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) {
|
||||
|
||||
@@ -116,7 +116,14 @@ const doRefund = (v: any) => {
|
||||
class="second-text-30 mr-2">
|
||||
共 {{ 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>
|
||||
|
||||
@@ -19,4 +19,13 @@ export default {
|
||||
method: "GET",
|
||||
params,
|
||||
}),
|
||||
/**
|
||||
* 用户领取核销券列表
|
||||
*/
|
||||
mchList: (data: any) =>
|
||||
request({
|
||||
url: "/wallet/act-coupon-record/app/mch-list",
|
||||
method: "POST",
|
||||
data,
|
||||
}),
|
||||
}
|
||||
@@ -78,6 +78,7 @@ const actUserReceiveEnum = {
|
||||
"4": "已领取,核销码在我的礼券中",
|
||||
"5": "用户不能参加此活动",
|
||||
"6": "其他错误",
|
||||
"7": "已领完",
|
||||
"99": "用户可以参加活动",
|
||||
// 特殊
|
||||
"-100": "仅限生日当月使用",
|
||||
|
||||
Reference in new issue
Block a user