Files
cashier_wx/pages/user/member/czzx.vue
2026-01-22 13:40:43 +08:00

499 lines
12 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">
<up-navbar bgColor="transparent" title="充值中心" @leftClick="back"></up-navbar>
<view class="header-wrap">
<image class="bg" src="/static/czzx_header_bg.png" mode="aspectFill"></image>
<view class="select-shop">
<view class="select-btn">
<up-icon name="map" color="#333"></up-icon>
<text class="t">{{ shopInfo.shopName }}</text>
<up-icon name="arrow-right" color="#333"></up-icon>
</view>
<ymf-share></ymf-share>
</view>
<view class="balance-wrap">
<view class="left">
<text class="i t">余额</text>
<text class="n t">{{ shopUserInfo.amount || 0 }}</text>
</view>
<view class="right">
<text class="t" @click="toduihuan">兑换码</text>
<text class="t" @click="toDetail">明细</text>
<text class="t" @click="toPwd">密码设置</text>
</view>
</view>
<view class="btm-wrap">
<view class=""></view>
</view>
</view>
<view class="bottom">
<view class="u-flex u-flex-between">
<view class="u-flex">
<image src="/static/vip/money.png" style="width: 44rpx; height: 44rpx" mode=""></image>
<text class="u-m-l-24 color-333 font-16 font-700">立即充值</text>
</view>
<view class="font-12 color-999">
<text>充值代表接受</text>
<text style="color: #ecb592">用户隐私协议</text>
</view>
</view>
<view class="list u-m-t-40">
<view class="item color1" @click="sel = index" v-for="(item, index) in list" :key="index" :class="{ active: sel == index }">
<view class="">
<text></text>
<text class="font-700" style="font-size: 48 u-flex-1rpx" :class="{ color2: sel == index }">{{ item.amount }}</text>
</view>
<view class="font-12" v-if="item.rewardAmount" :class="{ color2: sel == index }">
<text></text>
<text></text>
<text class="font-14">{{ item.rewardAmount }}</text>
</view>
<view class="font-12" v-if="item.rewardPoints" style="color: #5f2e0f">
<text></text>
<text class="font-14">{{ item.rewardPoints }}</text>
<text class="">积分</text>
</view>
<view class="font-12 color-666" v-if="item.couponInfoList.length">
<text></text>
<text>{{ couponNum(item.couponInfoList) }}</text>
<text>张券</text>
<text class="color2 u-m-l-8" v-if="sel == index" @click="lookCoupon(item)">查看</text>
</view>
<view class="sel u-flex" v-if="sel == index">
<image class="image" src="/static/vip/sel.png" mode=""></image>
</view>
</view>
</view>
<template v-if="state.isCustom">
<view class="u-flex other flex-center">
<text class="font-14 color-333 font-700 u-m-r-28">其他金额</text>
<up-input v-model="money" type="number" placeholder="请输入充值金额" border="none" placeholder-style="font-size:14px;"></up-input>
</view>
<view class="color-999 font-12 u-m-t-4">自定义金额充值时不享受任何优惠赠送</view>
</template>
<button class="buy-btn" @click="buy" :class="{ disabled: !state.isEnable }">
<text class="font-16">{{ charge_money }}</text>
<text class="font-14 u-m-l-24">立即充值</text>
</button>
<view class="u-m-t-36 color-999 font-12">
<view>充值说明</view>
<view class="u-m-t-16">
<text>适用门店</text>
<text class="color2 u-m-l-28" @click="toShopList">全国门店通用 {{ '>' }}</text>
</view>
<view class="u-m-t-16">
<text>有效期限</text>
<text class="u-m-l-28">永久有效</text>
</view>
<view class="u-m-t-16 u-flex u-flex-y-center">
<text class="no-wrap">注意事项</text>
<view class="u-m-l-28">
<view>1.储值完成后不支持自助退款,可联系商家处理</view>
<view>2.余额不支持转赠,不可提现,长期有效</view>
</view>
</view>
<view class="u-m-t-16 u-flex u-flex-y-center">
<text class="no-wrap">充值说明</text>
<text class="u-m-l-28" style="word-break: break-all">
{{ state.remark || '' }}
</text>
</view>
</view>
</view>
<CouponList v-model="couponModel.show" :list="couponModel.couponInfoList"></CouponList>
</view>
</template>
<script setup>
import ymfShare from '@/components/ymf-components/ymf-share.vue';
import { shareMixin, handleMixinOnLoad, returnQuery } from '@/utils/share.js';
import { APIusershopInfodetail, APIshopUserInfo } from '@/common/api/member.js';
import CouponList from '@/components/coupon/list.vue';
import * as rechargeApi from '@/common/api/market/recharge.js';
import { recharge } from '@/common/api/order/index.js';
import { joinMember } from '@/common/api/order/index.js';
import { ref, onMounted, computed, reactive, watch } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import { pay } from '@/utils/pay.js';
function toShopList() {
uni.navigateTo({
url: '/pages/user/member/czzx-shop-list?shopId=' + option.shopId
});
}
function toduihuan() {
uni.navigateTo({
url: '/user/exchange/index'
});
}
function couponNum(list) {
return list.reduce((prve, cur) => {
return prve + cur.num;
}, 0);
}
const couponModel = reactive({
show: false,
couponInfoList: []
});
function lookCoupon(item) {
couponModel.show = true;
couponModel.couponInfoList = item.couponInfoList;
}
async function buy() {
if (!state.isEnable) {
return uni.showToast({
title: '充值未开启暂不能充值',
icon: 'none'
});
}
if (!charge_money.value) {
return uni.showToast({
title: '请选择或者输入充值金额',
icon: 'none'
});
}
const json = {
shopId: option.shopId,
shopUserId: shopUserInfo.id
};
if (sel.value < 0) {
json.amount = `${money.value}`.trim() * 1;
} else {
json.rechargeDetailId = list.value[sel.value].id;
json.amount = list.value[sel.value].amount;
}
const res = await recharge(json);
if (!res) {
return uni.showToast({
title: '充值失败',
icon: 'error'
});
}
const payRes = await pay(res);
console.log(payRes);
if (payRes) {
uni.showToast({
title: '充值成功',
icon: 'none'
});
init();
}
}
function toDetail() {
uni.navigateTo({
url: '/pages/user/member/billDetails?type=1&shopId=' + option.shopId
});
}
function toPwd() {
uni.navigateTo({
url: '/pages/user/member/setPassword?type=1&shopId=' + option.shopId
});
}
function back() {
safeNavigateBack();
}
/**
* 修复版:安全的页面返回方法
* 彻底避免 "cannot navigate back at first page" 报错
* @param {Number} delta 返回的页面数默认1
* @param {Function} fallback 失败时的降级处理函数
*/
function safeNavigateBack(delta = 1, fallback) {
// 1. 立即获取页面栈,确保拿到最新状态(关键修复点)
const pages = getCurrentPages();
// 2. 严谨判断:页面栈长度必须大于 delta 才能返回
const canNavigateBack = pages.length > delta;
console.log('页面栈信息:', {
pagesLength: pages.length,
delta: delta,
canNavigateBack: canNavigateBack
});
// 3. 如果不能返回,直接执行降级逻辑
if (!canNavigateBack) {
console.warn('当前是首页/页面栈不足无法返回');
handleFallback(fallback);
return; // 终止后续执行,彻底避免调用 navigateBack
}
// 4. 能返回时才执行 navigateBack
try {
uni.navigateBack({
delta: delta,
success: () => {
console.log('页面返回成功');
},
fail: (err) => {
console.error('navigateBack 执行失败:', err);
handleFallback(fallback);
}
});
} catch (error) {
console.error('页面返回异常:', error);
handleFallback(fallback);
}
}
/**
* 统一处理降级逻辑
* @param {Function} fallback 自定义降级函数
*/
function handleFallback(fallback) {
if (typeof fallback === 'function') {
fallback(); // 执行自定义降级
} else {
// 默认降级:返回首页(请替换为你的首页路径)
uni.showToast({
title: '已到首页无法返回',
icon: 'none',
duration: 1500
});
// 如果需要强制跳首页,解开下面注释(根据你的业务选择)
uni.switchTab({
url: '/pages/index/index',
fail: () => {
uni.redirectTo({
url: '/pages/index/index'
});
}
});
}
}
const list = ref([]);
const sel = ref(0);
const money = ref(null);
const option = reactive({
shopId: ''
});
const shopInfo = reactive({});
const shopUserInfo = reactive({});
const state = reactive({});
async function init() {
const shopInfoRes = await APIusershopInfodetail({
shopId: option.shopId
});
if (shopInfoRes) {
Object.assign(shopInfo, shopInfoRes.shopInfo);
}
const shopUserInfoRes = await APIshopUserInfo({
shopId: option.shopId
});
if (shopUserInfoRes) {
Object.assign(shopUserInfo, shopUserInfoRes);
}
const res = await rechargeApi.config({
shopId: option.shopId
});
if (res) {
Object.assign(state, res);
list.value = res.rechargeDetailList;
}
}
const charge_money = computed(() => {
if (sel.value < 0) {
if (money.value > 0) {
return money.value;
}
return '';
}
const item = list.value[sel.value];
if (item) {
return item.amount;
}
return '';
});
onShareAppMessage(async (res) => {
let query = await returnQuery();
return {
title: `充值-${shopInfo.shopName}`,
path: `/pages/user/member/czzx?${query}`,
imageUrl: shopInfo.logo,
query
};
});
onLoad(async (opt) => {
Object.assign(option, opt);
await handleMixinOnLoad(opt);
// init();
});
watch(
() => money.value,
(newval) => {
if (newval && newval > 0) {
sel.value = -1;
}
}
);
onShow(() => {
init();
});
</script>
<style scoped lang="scss">
.color1 {
color: #5f2e0f;
}
.color2 {
color: #ff6300;
}
.buy-btn {
margin-top: 28rpx;
padding: 32rpx 32rpx;
color: #fff;
font-size: 16px;
font-weight: 700;
border-radius: 80rpx;
line-height: 1;
background: linear-gradient(98deg, #fe6d1100 40.64%, #ffd1b4 105.2%), linear-gradient(259deg, #fe6d11 50.14%, #ffd1b4 114.93%);
box-shadow: 0 14rpx 30.4rpx 0 #fe8b435e;
&.disabled {
background: #eee;
box-shadow: none;
border: none;
color: #999;
}
}
.other {
background: #f6f6f6;
padding: 24rpx 16rpx;
margin-top: 40rpx;
}
.header-wrap {
width: 100%;
height: 530rpx;
box-sizing: border-box;
padding: calc(var(--status-bar-height) + 140rpx) 28rpx 28rpx;
position: relative;
.bg {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.select-shop {
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
.select-btn {
display: flex;
align-items: center;
gap: 12upx;
.t {
color: #333;
}
}
}
.balance-wrap {
display: flex;
justify-content: space-between;
position: relative;
padding-top: 20rpx;
.left {
display: flex;
align-items: center;
.t {
color: #5e3110;
&.i {
position: relative;
top: 10upx;
font-size: 28upx;
}
&.n {
font-size: 64upx;
font-weight: bold;
}
}
}
.right {
display: flex;
flex-direction: column;
gap: 12upx;
.t {
color: #86491d;
font-size: 28upx;
}
}
}
}
.bottom {
background-color: rgba(255, 255, 255, 0.3);
padding: 40rpx 28rpx 0 28rpx;
transform: translateY(-140rpx);
border-radius: 74rpx 74rpx 0 0;
}
.list {
display: grid;
grid-template-columns: repeat(3, 1fr);
column-gap: 20rpx;
row-gap: 22rpx;
.item {
padding: 36rpx 22rpx;
border-radius: 42rpx;
background: linear-gradient(180deg, #f5f5f5 58.54%, #fff 140.47%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
box-sizing: border-box;
border: 6rpx solid transparent;
transition: all 0.3s ease-in-out;
&.active {
background: linear-gradient(180deg, #ffc29a -26.17%, #fff 64.06%);
border: 6rpx solid #fe6c0e;
box-shadow: 0 0 31rpx 2rpx #fe8b435e;
}
.sel {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%) translateY(21rpx);
.image {
width: 42rpx;
height: 42rpx;
}
}
}
}
</style>