appointment_system/miniprogram/src/pages/me/invite-reward-page.vue
2025-12-23 23:47:41 +08:00

1796 lines
44 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>
<page-meta :page-style="pageStyle"></page-meta>
<view class="page">
<!-- 固定导航栏 -->
<view class="navbar-fixed" :style="{ background: navbarBgColor }">
<image src="/static/ic_back.png" class="back-btn" @click="goBack" mode=""></image>
<text class="navbar-title">{{ $t('invite.title') }}</text>
</view>
<!-- 顶部红色奖励区域 -->
<view class="reward-header">
<image src="/static/new_bg1.png" class="bg-image" mode="scaleToFill"></image>
<!-- 奖励内容 -->
<view class="reward-content">
<text class="reward-title">{{ $t('invite.rewardTitle') }}</text>
<view class="reward-desc-box">
<text class="reward-desc">{{ $t('invite.rewardDesc') }}</text>
</view>
</view>
</view>
<!-- 参与步骤 -->
<view class="steps-section">
<view class="section-title-row">
<view class="title-line"></view>
<text class="section-title">{{ $t('invite.stepsTitle') }}</text>
<view class="title-line"></view>
</view>
<view class="step-item">
<view class="step-icon step-icon-1">
<image src="/static/new_user.png" class="step-img" mode="widthFix"></image>
</view>
<text class="step-text">{{ $t('invite.step1') }}</text>
</view>
<view class="step-item">
<view class="step-icon step-icon-2">
<image src="/static/red_box.png" class="step-img" mode="widthFix"></image>
</view>
<text class="step-text">{{ $t('invite.step2') }}</text>
</view>
<view class="step-item">
<view class="step-icon step-icon-3">
<image src="/static/ic_wallet.png" class="step-img" mode="widthFix"></image>
</view>
<text class="step-text">{{ $t('invite.step3') }}</text>
</view>
<view class="detail-link" @click="showDetail">
<text class="detail-text">{{ $t('invite.viewDetail') }}</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="action-buttons">
<view class="action-btn qrcode-btn" @click="generateQRCode">
<text class="btn-text">{{ $t('invite.generateQRCode') }}</text>
</view>
<!-- #ifdef MP-WEIXIN -->
<button class="action-btn share-btn share-btn-native" open-type="share">
<text class="btn-text">{{ $t('invite.shareToFriend') }}</text>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="action-btn share-btn" @click="shareToFriend">
<text class="btn-text">{{ $t('invite.shareToFriend') }}</text>
</view>
<!-- #endif -->
</view>
<!-- 提现记录 -->
<view class="record-section">
<view class="record-header" @click="goToWithdrawDetail">
<text class="record-title">{{ $t('invite.withdrawRecord') }}</text>
<view class="record-more">
<text class="more-text">{{ $t('invite.withdrawPeriod') }}</text>
<image src="/static/arrow_right3.png" class="arrow-icon" mode=""></image>
</view>
</view>
<view class="record-content">
<image src="/static/new_bg2.png" class="record-bg-image" mode="scaleToFill"></image>
<view class="reward-cards">
<view class="reward-card" v-for="(item, index) in withdrawRecords" :key="index">
<text class="reward-amount">{{ item.amount }}{{ $t('common.currency') }}</text>
<text class="reward-status">{{ item.status }}</text>
</view>
</view>
<view class="apply-withdraw-btn" @click="applyWithdraw">
<text class="apply-text">{{ $t('invite.applyWithdraw') }}</text>
</view>
</view>
</view>
<!-- 邀请记录 -->
<view class="invite-record-section">
<view class="invite-record-header">
<text class="record-title">{{ $t('invite.inviteRecord') }}</text>
</view>
<view class="invite-record-content">
<image src="/static/approved.png" class="content-bg-stamp" mode="aspectFit"></image>
<view class="table-header">
<text class="table-col">{{ $t('invite.username') }}</text>
<text class="table-col">{{ $t('invite.uid') }}</text>
<text class="table-col">{{ $t('invite.inviteTime') }}</text>
<text class="table-col">{{ $t('invite.paid') }}</text>
</view>
<view class="table-body">
<view class="table-row" v-for="(item, index) in inviteRecords" :key="index">
<text class="table-cell">{{ item.username }}</text>
<text class="table-cell">{{ item.uid }}</text>
<text class="table-cell">{{ item.time }}</text>
<view class="table-cell status-cell">
<text v-if="item.paid && item.amount" class="paid-amount">¥{{ item.amount }}</text>
<text v-else class="unpaid-text">¥0</text>
</view>
</view>
</view>
<view v-if="inviteRecords.length === 0" class="empty-state">
<text class="empty-text">{{ $t('common.noData') }}</text>
</view>
</view>
</view>
<!-- 提现明细弹窗 -->
<up-popup :show="showWithdrawModal" mode="center" :round="20" :overlay="true" :closeOnClickOverlay="true" bgColor="#ffffff" @close="closeWithdrawModal">
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">{{ $t('invite.withdrawDetail') }}</text>
<image src="/static/ic_colse.png" class="close-icon" @click="closeWithdrawModal" mode=""></image>
</view>
<view class="modal-table">
<view class="modal-table-header">
<text class="modal-table-col">{{ $t('invite.time') }}</text>
<text class="modal-table-col">{{ $t('invite.amount') }}</text>
<text class="modal-table-col">{{ $t('invite.status') }}</text>
</view>
<view class="modal-table-body">
<view class="modal-table-row" v-for="(item, index) in withdrawDetailList" :key="index">
<text class="modal-table-cell">{{ item.time }}</text>
<text class="modal-table-cell">¥{{ item.amount }}</text>
<text class="modal-table-cell">{{ item.status }}</text>
</view>
<view v-if="withdrawDetailList.length === 0" class="modal-empty-state">
<text class="modal-empty-text">{{ $t('common.noData') }}</text>
</view>
</view>
</view>
</view>
</up-popup>
<!-- 二维码弹窗 -->
<up-popup :show="showQRCodeModal" mode="center" :round="20" :overlay="true" :closeOnClickOverlay="true" bgColor="#ffffff" @close="closeQRCodeModal">
<view class="qrcode-modal-content">
<text class="qrcode-modal-title">{{ $t('invite.qrcodeTitle') || '邀请好友,赢现金' }}</text>
<view class="qrcode-box">
<!-- 微信小程序码 -->
<image v-if="wxQrcodeImage" :src="wxQrcodeImage" class="wx-qrcode-image" mode="aspectFit"></image>
<!-- 小程序未发布时显示提示 -->
<view v-else-if="useLocalQrcode" class="unpublished-tip">
<image src="/static/ic_warning.png" class="tip-icon" mode="aspectFit"></image>
<text class="tip-text">小程序未发布</text>
<text class="tip-subtext">暂无法生成二维码</text>
</view>
<!-- 加载中 -->
<view v-else-if="qrcodeLoading" class="qrcode-loading">
<text>加载中...</text>
</view>
<!-- 错误提示 -->
<text v-else-if="qrcodeError" class="qrcode-error">{{ qrcodeError }}</text>
</view>
<text v-if="useLocalQrcode" class="qrcode-tip">请使用下方分享功能邀请好友</text>
<view class="qrcode-actions">
<!-- #ifdef MP-WEIXIN -->
<button class="qrcode-action-btn share-action-btn share-btn-native" open-type="share">
<text class="action-btn-text">{{ $t('invite.shareToFriend') || '分享给好友' }}</text>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="qrcode-action-btn share-action-btn" @click="shareQRCodeToFriend">
<text class="action-btn-text">{{ $t('invite.shareToFriend') || '分享给好友' }}</text>
</view>
<!-- #endif -->
<view class="qrcode-action-btn save-action-btn" @click="saveQRCodeImage" v-if="!useLocalQrcode">
<text class="action-btn-text">{{ $t('invite.saveImage') || '保存图片' }}</text>
</view>
</view>
</view>
</up-popup>
<!-- 申请提现弹窗 - 原生写法 -->
<view class="apply-modal-overlay" v-if="showApplyModal" @click="closeApplyModal">
<view class="apply-modal-wrapper" @click.stop>
<view class="apply-modal-content">
<!-- 红色标题栏 -->
<view class="apply-modal-header">
<text class="apply-modal-title">{{ $t('invite.withdrawApplication') }} {{ applyStep }}/2</text>
</view>
<!-- 第一步:输入金额 -->
<view v-if="applyStep === 1" class="apply-step-content">
<image src="/static/new_bg2.png" class="step-bg-image" mode="aspectFill"></image>
<text class="step-label">{{ $t('invite.selectCurrency') || '选择货币类型' }}</text>
<view class="currency-methods">
<view class="currency-method" :class="{ active: withdrawCurrency === 'CNY' }"
@click="selectCurrency('CNY')">
<text class="method-text">¥ {{ $t('invite.currencyCNY') || '人民币' }}</text>
</view>
<view class="currency-method" :class="{ active: withdrawCurrency === 'PHP' }"
@click="selectCurrency('PHP')">
<text class="method-text">₱ {{ $t('invite.currencyPHP') || '比索' }}</text>
</view>
</view>
<text class="step-label">{{ $t('invite.enterAmount') }}</text>
<view class="amount-input-row">
<input class="amount-input" type="number" v-model="withdrawAmount"
:placeholder="$t('invite.enterPlaceholder')" />
<text class="currency-text">{{ withdrawCurrency === 'CNY' ? '¥' : '₱' }}</text>
</view>
<text class="amount-hint">{{ $t('invite.amountHint') }}</text>
<view class="apply-btn" @click="nextStep">
<text class="apply-btn-text">{{ $t('invite.nextStep') }}</text>
</view>
</view>
<!-- 第二步:选择收款方式 -->
<view v-if="applyStep === 2" class="apply-step-content">
<image src="/static/new_bg2.png" class="step-bg-image" mode="aspectFill"></image>
<text class="step-label">1.{{ $t('invite.selectPaymentMethod') }}</text>
<view class="payment-methods">
<view class="payment-method" :class="{ active: paymentMethod === 'wechat' }"
@click="selectPaymentMethod('wechat')">
<text class="method-text">{{ $t('invite.wechat') }}</text>
</view>
<view class="payment-method" :class="{ active: paymentMethod === 'alipay' }"
@click="selectPaymentMethod('alipay')">
<text class="method-text">{{ $t('invite.alipay') }}</text>
</view>
<view class="payment-method" :class="{ active: paymentMethod === 'bank' }"
@click="selectPaymentMethod('bank')">
<text class="method-text">{{ $t('invite.bankCard') }}</text>
</view>
</view>
<!-- 微信/支付宝:上传收款码 -->
<view v-if="paymentMethod !== 'bank'">
<text class="step-label">2.{{ $t('invite.uploadQRCode') }}</text>
<view class="upload-area" @click="uploadQRCode">
<image v-if="qrcodeImage" :src="qrcodeImage" class="uploaded-image" mode="aspectFit">
</image>
<text v-else class="upload-icon">+</text>
</view>
</view>
<!-- 银行卡:填写银行卡信息 -->
<view v-if="paymentMethod === 'bank'">
<text class="step-label">2.{{ $t('invite.enterBankInfo') }}</text>
<view class="form-item">
<text class="form-label">{{ $t('invite.bankCardNumber') }}</text>
<input class="form-input" v-model="bankCardNumber"
:placeholder="$t('invite.enterBankCardNumber')" />
</view>
<view class="form-item">
<text class="form-label">{{ $t('invite.cardholderName') }}</text>
<input class="form-input" v-model="cardholderName"
:placeholder="$t('invite.enterCardholderName')" />
</view>
<view class="form-item">
<text class="form-label">{{ $t('invite.bankName') }}</text>
<input class="form-input" v-model="bankName" :placeholder="$t('invite.enterBankName')" />
</view>
<view class="form-item">
<text class="form-label">{{ $t('invite.swiftCode') }} ({{ $t('invite.optional') }})</text>
<input class="form-input" v-model="swiftCode" :placeholder="$t('invite.enterSwiftCode')" />
</view>
</view>
<view class="apply-btn" @click="submitWithdraw">
<text class="apply-btn-text">{{ $t('invite.applyWithdraw') }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
AppServer
} from '@/modules/api/AppServer.js'
const appServer = new AppServer()
export default {
data() {
return {
navbarBgColor: 'transparent',
scrollTop: 0,
showWithdrawModal: false,
showApplyModal: false,
showQRCodeModal: false,
showQRCodeContent: false, // 控制二维码内容显示
qrcodeDataUrl: '',
qrcodeValue: '', // 二维码内容(本地生成用)
wxQrcodeImage: '', // 微信小程序码图片base64
useLocalQrcode: false, // 是否使用本地二维码组件
qrcodeLoading: false, // 小程序码加载状态
qrcodeError: '', // 小程序码错误信息
appLogo: '', // 应用logo用于分享图片
applyStep: 1,
withdrawAmount: '',
withdrawCurrency: 'CNY',
paymentMethod: 'wechat',
qrcodeImage: '',
bankCardNumber: '',
cardholderName: '',
bankName: '',
swiftCode: '',
withdrawRecords: [],
withdrawDetailList: [],
inviteRecords: [],
// 佣金统计
commissionStats: {
totalInvites: 0,
paidInvites: 0,
totalCommission: '0.00',
availableBalance: '0.00',
invitationCode: ''
},
loading: false,
inviteRules: '' // 邀请规则说明(从后台配置获取)
}
},
computed: {
// 弹窗显示时禁止页面滚动
pageStyle() {
if (this.showQRCodeModal || this.showWithdrawModal || this.showApplyModal) {
return 'overflow: hidden;'
}
return ''
}
},
onLoad() {
this.loadData()
},
// 微信小程序分享配置
onShareAppMessage() {
const inviteCode = this.commissionStats.invitationCode || ''
// 使用应用logo作为分享图片
// 注意:微信小程序分享图片支持本地路径和网络路径(https)
// 开发环境下http图片可能无法显示使用本地图片作为备选
let shareImage = '/static/new_bg1.png'
if (this.appLogo && this.appLogo.startsWith('http')) {
shareImage = this.appLogo
}
console.log('分享图片路径:', shareImage, 'appLogo:', this.appLogo)
return {
title: '邀请你加入,一起赚佣金!',
path: `/pages/index/index?inviteCode=${inviteCode}`,
imageUrl: shareImage
}
},
// 分享到朋友圈(微信小程序)
onShareTimeline() {
const inviteCode = this.commissionStats.invitationCode || ''
let shareImage = '/static/new_bg1.png'
if (this.appLogo && this.appLogo.startsWith('http')) {
shareImage = this.appLogo
}
return {
title: '邀请你加入,一起赚佣金!',
query: `inviteCode=${inviteCode}`,
imageUrl: shareImage
}
},
methods: {
// 加载数据
async loadData() {
this.loading = true
try {
await Promise.all([
this.loadCommissionStats(),
this.loadCommissionRecords(),
this.loadWithdrawRecords(),
this.loadAppConfig()
])
} catch (error) {
console.error('加载数据失败:', error)
} finally {
this.loading = false
}
},
// 加载应用配置从globalData获取logo和邀请规则
loadAppConfig() {
try {
const app = getApp()
if (app && app.globalData && app.globalData.config) {
const config = app.globalData.config
if (config.app_logo) {
this.appLogo = app.getConfigImageUrl('app_logo')
console.log('从globalData获取到应用logo:', this.appLogo)
}
// 获取邀请规则说明
if (config.invite_rules) {
this.inviteRules = config.invite_rules
}
}
} catch (error) {
console.error('获取应用配置失败:', error)
}
},
// 加载佣金统计
async loadCommissionStats() {
try {
const res = await appServer.GetCommissionStats()
console.log('GetCommissionStats response:', res)
// 后端返回格式: { code: 0, message: "", data: {...} }
if (res && res.code === 0 && res.data) {
this.commissionStats = res.data
console.log('commissionStats updated:', this.commissionStats)
// 更新提现记录显示
this.updateWithdrawDisplay()
// 预先设置二维码内容,用于分享(使用小程序路径)
const inviteCode = res.data.invitationCode || ''
this.qrcodeValue = `/pages/index/index?inviteCode=${inviteCode}`
}
} catch (error) {
console.error('获取佣金统计失败:', error)
}
},
// 加载佣金记录(改为加载邀请记录)
async loadCommissionRecords() {
try {
// 使用邀请记录API显示所有被邀请的用户包括未支付的
const res = await appServer.GetInvitationRecords()
// 后端返回格式: { success: true, data: { records: [...] } }
if (res && res.success && res.data && res.data.records) {
this.inviteRecords = res.data.records.map(item => ({
username: item.invitee?.nickname || '-',
uid: item.invitee?.uid || '-',
time: this.formatDate(item.createdAt),
paid: item.firstPaymentAt != null,
amount: item.rewardAmount ? parseFloat(item.rewardAmount) : 0
}))
}
} catch (error) {
console.error('获取邀请记录失败:', error)
}
},
// 加载提现记录
async loadWithdrawRecords() {
try {
const res = await appServer.GetWithdrawals({
page: 1,
limit: 10
})
// 后端返回格式: { code: 0, message: "", data: {...} }
if (res && res.code === 0 && res.data && res.data.records) {
this.withdrawDetailList = res.data.records.map(item => ({
time: this.formatDate(item.createdAt),
amount: item.amount,
status: this.getWithdrawStatusText(item.status)
}))
}
} catch (error) {
console.error('获取提现记录失败:', error)
}
},
// 更新提现显示
updateWithdrawDisplay() {
// 显示待提现、处理中、已完成的金额
this.withdrawRecords = [{
amount: this.commissionStats.availableBalance || '0',
status: this.$t('invite.statusWaiting')
}]
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return '-'
const date = new Date(dateStr)
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`
},
// 获取提现状态文本
getWithdrawStatusText(status) {
const statusMap = {
'waiting': this.$t('invite.statusWaiting'),
'processing': this.$t('invite.statusProcessing'),
'completed': this.$t('invite.statusCompleted'),
'rejected': this.$t('invite.statusRejected') || '已拒绝'
}
return statusMap[status] || status
},
goBack() {
uni.navigateBack()
},
showDetail() {
uni.showModal({
title: '规则说明',
content: this.inviteRules || this.$t('invite.ruleContent'),
showCancel: false
})
},
async generateQRCode() {
console.log('generateQRCode called, current state:', {
wxQrcodeImage: this.wxQrcodeImage,
qrcodeValue: this.qrcodeValue,
useLocalQrcode: this.useLocalQrcode,
invitationCode: this.commissionStats.invitationCode
})
// 如果已有小程序码,直接显示
if (this.wxQrcodeImage || (this.useLocalQrcode && this.commissionStats.invitationCode)) {
this.showQRCodeModal = true
return
}
// 显示弹窗并开始加载
this.showQRCodeModal = true
this.qrcodeLoading = true
this.qrcodeError = ''
try {
// 调用后端API获取微信小程序码
const res = await appServer.GetMiniProgramQRCode({ width: 430 })
console.log('获取小程序码响应:', res)
if (res && res.code === 0 && res.data) {
// 更新邀请码(后端返回的最新邀请码)
if (res.data.invitationCode) {
this.commissionStats.invitationCode = res.data.invitationCode
}
if (res.data.qrcodeUrl) {
// 使用微信小程序码
this.wxQrcodeImage = res.data.qrcodeUrl
this.useLocalQrcode = false
} else if (res.data.useLocalQrcode) {
// 小程序未发布,显示邀请码
this.qrcodeValue = res.data.qrcodeContent
this.useLocalQrcode = true
this.showQRCodeContent = true
}
} else {
this.qrcodeError = res?.message || '生成小程序码失败'
}
} catch (error) {
console.error('生成小程序码失败:', error)
this.qrcodeError = '网络错误,请重试'
} finally {
this.qrcodeLoading = false
}
},
closeQRCodeModal() {
this.showQRCodeModal = false
},
shareQRCodeToFriend() {
// 分享给好友
// #ifdef MP-WEIXIN
// 微信小程序使用 button 的 open-type="share" 来触发分享
// 这里使用 uni.share 作为备选方案
// #endif
uni.showToast({
title: this.$t('invite.shareSuccess') || '请使用右上角分享',
icon: 'none'
})
},
saveQRCodeImage() {
// 使用本地二维码组件
if (this.useLocalQrcode && this.$refs.uQrcode) {
this.$refs.uQrcode.toTempFilePath({
success: (res) => {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
uni.showToast({
title: this.$t('invite.saveSuccess') || '保存成功',
icon: 'success'
})
},
fail: (err) => {
console.error('保存图片失败:', err)
if (err.errMsg && err.errMsg.includes('auth deny')) {
uni.showModal({
title: '提示',
content: '需要您授权保存图片到相册',
success: (modalRes) => {
if (modalRes.confirm) {
uni.openSetting()
}
}
})
} else {
uni.showToast({
title: '保存失败',
icon: 'none'
})
}
}
})
},
fail: (err) => {
console.error('获取二维码图片失败:', err)
uni.showToast({
title: '获取二维码失败',
icon: 'none'
})
}
})
return
}
// 使用微信小程序码
if (!this.wxQrcodeImage) {
uni.showToast({
title: '小程序码加载中,请稍候',
icon: 'none'
})
return
}
// 将 base64 图片保存到相册
const base64Data = this.wxQrcodeImage.replace(/^data:image\/\w+;base64,/, '')
const filePath = `${wx.env.USER_DATA_PATH}/qrcode_${Date.now()}.png`
const fs = uni.getFileSystemManager()
fs.writeFile({
filePath: filePath,
data: base64Data,
encoding: 'base64',
success: () => {
uni.saveImageToPhotosAlbum({
filePath: filePath,
success: () => {
uni.showToast({
title: this.$t('invite.saveSuccess') || '保存成功',
icon: 'success'
})
},
fail: (err) => {
console.error('保存图片失败:', err)
if (err.errMsg && err.errMsg.includes('auth deny')) {
uni.showModal({
title: '提示',
content: '需要您授权保存图片到相册',
success: (modalRes) => {
if (modalRes.confirm) {
uni.openSetting()
}
}
})
} else {
uni.showToast({
title: '保存失败',
icon: 'none'
})
}
}
})
},
fail: (err) => {
console.error('写入文件失败:', err)
uni.showToast({
title: '保存失败',
icon: 'none'
})
}
})
},
shareToFriend() {
uni.share({
provider: 'weixin',
scene: 'WXSceneSession',
type: 0,
title: this.$t('invite.shareTitle'),
success: () => {
uni.showToast({
title: this.$t('invite.shareSuccess'),
icon: 'success'
})
}
})
},
goToWithdrawDetail() {
// 打开提现明细弹窗数据已在loadWithdrawRecords中加载
this.showWithdrawModal = true
},
closeWithdrawModal() {
this.showWithdrawModal = false
},
applyWithdraw() {
// 打开申请提现弹窗
this.applyStep = 1
this.withdrawAmount = ''
this.withdrawCurrency = 'CNY'
this.paymentMethod = 'wechat'
this.qrcodeImage = ''
this.bankCardNumber = ''
this.cardholderName = ''
this.bankName = ''
this.swiftCode = ''
this.showApplyModal = true
},
closeApplyModal() {
this.showApplyModal = false
},
nextStep() {
// 解析提现金额,支持小数点后两位
const amount = parseFloat(this.withdrawAmount)
// 验证金额是否有效
if (!this.withdrawAmount || isNaN(amount)) {
uni.showToast({
title: this.$t('invite.enterAmountError'),
icon: 'none'
})
return
}
// 最低1元
if (amount < 1) {
uni.showToast({
title: this.$t('invite.amountTooLow') || '低于1元无法申请提现',
icon: 'none'
})
return
}
// 最高不超过待提现金额
const availableBalance = parseFloat(this.commissionStats.availableBalance || '0')
if (amount > availableBalance) {
uni.showToast({
title: this.$t('invite.amountExceedsBalance') || '超出可提现金额',
icon: 'none'
})
return
}
// 限制小数点后两位
const formattedAmount = Math.floor(amount * 100) / 100
this.withdrawAmount = formattedAmount.toString()
this.applyStep = 2
},
selectPaymentMethod(method) {
this.paymentMethod = method
},
selectCurrency(currency) {
this.withdrawCurrency = currency
},
uploadQRCode() {
uni.chooseImage({
count: 1,
success: (res) => {
this.qrcodeImage = res.tempFilePaths[0]
}
})
},
async submitWithdraw() {
// 验证微信/支付宝收款码
if (this.paymentMethod !== 'bank' && !this.qrcodeImage) {
uni.showToast({
title: this.$t('invite.uploadQRCodeError'),
icon: 'none'
})
return
}
// 验证银行卡信息
if (this.paymentMethod === 'bank') {
if (!this.bankCardNumber || !this.cardholderName || !this.bankName) {
uni.showToast({
title: this.$t('invite.bankInfoError'),
icon: 'none'
})
return
}
}
// 构建支付详情
let paymentDetails = {}
if (this.paymentMethod === 'bank') {
paymentDetails = {
bankCardNumber: this.bankCardNumber,
cardholderName: this.cardholderName,
bankName: this.bankName,
swiftCode: this.swiftCode || undefined
}
} else {
// 微信/支付宝需要上传收款码图片
try {
const uploadRes = await appServer.UploadImage(this.qrcodeImage)
// 后端返回格式: { code: 0, message: "", data: {...} }
if (uploadRes && uploadRes.code === 0 && uploadRes.data) {
paymentDetails = {
qrcodeUrl: uploadRes.data.url
}
} else {
uni.showToast({
title: '上传收款码失败',
icon: 'none'
})
return
}
} catch (error) {
console.error('上传收款码失败:', error)
uni.showToast({
title: '上传收款码失败',
icon: 'none'
})
return
}
}
// 提交提现申请
try {
const res = await appServer.CreateWithdrawal({
amount: parseFloat(this.withdrawAmount),
currency: this.withdrawCurrency,
paymentMethod: this.paymentMethod,
paymentDetails: paymentDetails
})
// 后端返回格式: { code: 0, message: "", data: {...} }
if (res && res.code === 0) {
uni.showToast({
title: this.$t('invite.applySuccess'),
icon: 'success'
})
this.closeApplyModal()
// 刷新数据
this.loadData()
} else {
uni.showToast({
title: res.error?.message || '提现申请失败',
icon: 'none'
})
}
} catch (error) {
console.error('提现申请失败:', error)
uni.showToast({
title: '提现申请失败',
icon: 'none'
})
}
},
handleScroll(e) {
// 页面滚动事件在 uni-app 中需要使用 onPageScroll
}
},
onPageScroll(e) {
const scrollTop = e.scrollTop
// 滚动距离超过 100px 时,导航栏背景从透明渐变到红色
if (scrollTop > 100) {
this.navbarBgColor = 'linear-gradient(135deg, #FF3B4E 0%, #FF6B7A 100%)'
} else {
// 根据滚动距离计算透明度
const opacity = scrollTop / 100
this.navbarBgColor = `rgba(255, 59, 78, ${opacity})`
}
}
}
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background-color: #F3F4F8;
}
.navbar-fixed {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
padding-top: 44px;
height: 88rpx;
transition: background 0.3s ease;
.back-btn {
position: absolute;
left: 30rpx;
width: 48rpx;
height: 48rpx;
filter: brightness(0) invert(1);
}
.navbar-title {
font-size: 36rpx;
font-weight: 500;
color: #FFFFFF;
}
}
.reward-header {
position: relative;
height: 550rpx;
background-color: #FF3B4E;
overflow: hidden;
.bg-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.reward-content {
position: relative;
z-index: 5;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 550rpx;
padding: 80rpx 30rpx 0;
.reward-title {
font-size: 60rpx;
font-weight: bold;
color: #FFFFFF;
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.3);
margin-bottom: 60rpx;
letter-spacing: 2rpx;
text-align: center;
word-break: break-word;
max-width: 100%;
line-height: 1.3;
}
.reward-desc-box {
background-color: rgba(255, 255, 255, 0.98);
border-radius: 60rpx;
padding: 24rpx 50rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
max-width: 90%;
.reward-desc {
font-size: 26rpx;
color: #333333;
line-height: 1.8;
text-align: center;
word-break: break-word;
}
}
}
}
.steps-section {
background-color: #FFFFFF;
margin: 30rpx;
border-radius: 20rpx;
padding: 40rpx 30rpx;
.section-title-row {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40rpx;
.title-line {
width: 60rpx;
height: 2rpx;
background-color: #333333;
flex-shrink: 0;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333333;
margin: 0 20rpx;
white-space: nowrap;
}
}
.step-item {
display: flex;
align-items: flex-start;
margin-left: 60rpx;
margin-bottom: 30rpx;
.step-icon {
width: 74rpx;
height: 74rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 24rpx;
flex-shrink: 0;
.step-img {
width: 74rpx;
height: 74rpx;
}
}
.step-text {
font-size: 26rpx;
color: #333333;
flex: 1;
line-height: 1.6;
word-break: break-word;
padding-top: 10rpx;
}
}
.detail-link {
text-align: center;
margin-top: 20rpx;
.detail-text {
font-size: 26rpx;
color: #007AFF;
text-decoration: underline;
}
}
}
.action-buttons {
display: flex;
justify-content: space-between;
padding: 0 30rpx;
margin-bottom: 30rpx;
gap: 20rpx;
.action-btn {
flex: 1;
min-height: 50rpx;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 10rpx;
&.qrcode-btn {
background: linear-gradient(135deg, #FF3B4E 0%, #FF6B7A 100%);
}
&.share-btn {
background: linear-gradient(135deg, #FF6B7A 0%, #FF3B4E 100%);
}
// 微信原生分享按钮样式重置
&.share-btn-native {
border: none;
margin: 0;
line-height: normal;
&::after {
border: none;
}
}
.btn-text {
font-size: 28rpx;
font-weight: bold;
color: #FFFFFF;
text-align: center;
word-break: break-word;
line-height: 1.4;
}
}
}
.record-section {
margin: 0 30rpx 30rpx;
border-radius: 20rpx;
overflow: hidden;
.record-header {
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(135deg, #FF3B4E 0%, #FF6B7A 100%);
padding: 24rpx 30rpx;
.record-title {
font-size: 32rpx;
font-weight: bold;
color: #FFFFFF;
}
.record-more {
display: flex;
align-items: center;
.more-text {
font-size: 24rpx;
color: #FFFFFF;
margin-right: 8rpx;
}
.arrow-icon {
width: 24rpx;
height: 24rpx;
filter: brightness(0) invert(1);
}
}
}
.record-content {
background-color: #FFFFFF;
padding: 50rpx 30rpx 40rpx;
border: 4rpx solid #FF3B4E;
border-top: none;
border-radius: 0 0 20rpx 20rpx;
position: relative;
overflow: hidden;
.record-bg-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 1;
pointer-events: none;
z-index: 0;
}
.reward-cards {
position: relative;
z-index: 1;
display: flex;
justify-content: space-around;
margin-bottom: 40rpx;
position: relative;
.reward-card {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
&:not(:last-child)::after {
content: '';
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 1rpx;
height: 80rpx;
background-color: #E5E5E5;
}
.reward-amount {
font-size: 48rpx;
font-weight: bold;
color: #FF3B4E;
margin-bottom: 16rpx;
}
.reward-status {
font-size: 26rpx;
color: #333333;
}
}
}
.apply-withdraw-btn {
background: linear-gradient(135deg, #FF3B4E 0%, #FF6B7A 100%);
height: 88rpx;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 1;
width: 70%;
margin: 0 auto;
.apply-text {
font-size: 32rpx;
font-weight: bold;
color: #FFFFFF;
}
}
}
}
.invite-record-section {
margin: 0 30rpx 50rpx;
border-radius: 20rpx;
overflow: hidden;
padding-bottom: 50rpx;
.invite-record-header {
background: linear-gradient(135deg, #FF3B4E 0%, #FF6B7A 100%);
padding: 24rpx 30rpx;
.record-title {
font-size: 32rpx;
font-weight: bold;
color: #FFFFFF;
}
}
.invite-record-content {
background-color: #FFFFFF;
border: 4rpx solid #FF3B4E;
border-top: none;
border-radius: 0 0 20rpx 20rpx;
position: relative;
overflow: hidden;
.content-bg-stamp {
position: absolute;
right: 30rpx;
bottom: 30rpx;
width: 200rpx;
height: 200rpx;
opacity: 0.85;
pointer-events: none;
z-index: 0;
}
}
.table-header {
display: flex;
background-color: #FFFFFF;
padding: 24rpx 16rpx;
border-bottom: 2rpx solid #F0F0F0;
position: relative;
z-index: 1;
.table-col {
flex: 1;
font-size: 28rpx;
font-weight: bold;
color: #333333;
text-align: center;
}
}
.table-body {
position: relative;
z-index: 1;
.table-row {
display: flex;
padding: 24rpx 16rpx;
border-bottom: 1rpx solid #F0F0F0;
align-items: center;
&:last-child {
border-bottom: none;
}
.table-cell {
flex: 1;
font-size: 26rpx;
color: #999999;
text-align: center;
&.status-cell {
display: flex;
align-items: center;
justify-content: center;
.paid-amount {
font-size: 28rpx;
font-weight: bold;
color: #333333;
}
.unpaid-text {
font-size: 28rpx;
font-weight: bold;
color: #999999;
}
}
}
}
}
.empty-state {
padding: 80rpx 0;
text-align: center;
.empty-text {
font-size: 28rpx;
color: #999999;
}
}
}
// 提现明细弹窗
.modal-content {
width: 600rpx;
max-height: 70vh;
display: flex;
flex-direction: column;
box-sizing: border-box;
.modal-header {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx;
border-bottom: 1rpx solid #F0F0F0;
position: relative;
.modal-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
.close-icon {
position: absolute;
right: 30rpx;
width: 40rpx;
height: 40rpx;
}
}
.modal-table {
flex: 1;
overflow-y: auto;
.modal-table-header {
display: flex;
padding: 24rpx 20rpx;
background-color: #F8F8F8;
border-bottom: 1rpx solid #E5E5E5;
.modal-table-col {
flex: 1;
font-size: 28rpx;
font-weight: bold;
color: #333333;
text-align: center;
}
}
.modal-table-body {
.modal-table-row {
display: flex;
padding: 24rpx 20rpx;
border-bottom: 1rpx solid #F0F0F0;
&:last-child {
border-bottom: none;
}
.modal-table-cell {
flex: 1;
font-size: 26rpx;
color: #666666;
text-align: center;
}
}
.modal-empty-state {
padding: 80rpx 0;
text-align: center;
.modal-empty-text {
font-size: 28rpx;
color: #999999;
}
}
}
}
}
// 二维码弹窗
.qrcode-modal-content {
width: 600rpx;
padding: 40rpx 30rpx;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
position: relative;
overflow: hidden;
.qrcode-modal-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
margin-bottom: 30rpx;
text-align: center;
}
.qrcode-tip {
font-size: 24rpx;
color: #ff9800;
margin-bottom: 20rpx;
text-align: center;
}
.qrcode-box {
width: 440rpx;
min-height: 440rpx;
background-color: #FFFFFF;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
.wx-qrcode-image {
width: 400rpx;
height: 400rpx;
}
.unpublished-tip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40rpx;
.tip-icon {
width: 80rpx;
height: 80rpx;
margin-bottom: 20rpx;
opacity: 0.6;
}
.tip-text {
font-size: 32rpx;
font-weight: bold;
color: #999;
margin-bottom: 10rpx;
}
.tip-subtext {
font-size: 26rpx;
color: #bbb;
}
}
.qrcode-loading {
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 28rpx;
}
.qrcode-error {
color: #ff4d4f;
font-size: 26rpx;
text-align: center;
}
:deep(.u-qrcode__canvas) {
position: static !important;
left: auto !important;
top: auto !important;
z-index: 1 !important;
}
:deep(canvas) {
position: static !important;
left: auto !important;
top: auto !important;
}
}
.qrcode-actions {
display: flex;
gap: 30rpx;
width: 100%;
.qrcode-action-btn {
flex: 1;
height: 80rpx;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
&.share-action-btn {
background: linear-gradient(135deg, #4ECDC4 0%, #44A08D 100%);
}
&.save-action-btn {
background: linear-gradient(135deg, #4ECDC4 0%, #44A08D 100%);
}
// 微信原生分享按钮样式重置
&.share-btn-native {
border: none;
padding: 0;
margin: 0;
line-height: 80rpx;
&::after {
border: none;
}
}
.action-btn-text {
font-size: 28rpx;
font-weight: bold;
color: #FFFFFF;
}
}
}
}
// 申请提现弹窗 - 原生写法
.apply-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
}
.apply-modal-wrapper {
width: 650rpx;
max-width: 90vw;
max-height: 80vh;
background-color: #FFFFFF;
border-radius: 20rpx;
overflow: hidden;
}
.apply-modal-content {
width: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
.apply-modal-header {
background: linear-gradient(135deg, #FF3B4E 0%, #FF6B7A 100%);
padding: 30rpx 20rpx;
text-align: center;
flex-shrink: 0;
.apply-modal-title {
font-size: 30rpx;
font-weight: bold;
color: #FFFFFF;
word-break: keep-all;
}
}
.apply-step-content {
padding: 40rpx 30rpx;
position: relative;
background-color: #FFFFFF;
overflow: hidden;
flex: 1;
.step-bg-image {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
opacity: 0.85;
pointer-events: none;
z-index: 0;
}
.step-label {
font-size: 26rpx;
color: #333333;
font-weight: bold;
display: block;
margin-bottom: 20rpx;
position: relative;
z-index: 1;
line-height: 1.5;
word-break: break-word;
}
.amount-input-row {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
position: relative;
z-index: 1;
.amount-input {
flex: 1;
height: 80rpx;
background-color: #FFFFFF;
border: 1rpx solid #E5E5E5;
border-radius: 12rpx;
padding: 0 20rpx;
font-size: 32rpx;
text-align: center;
}
.currency-text {
font-size: 32rpx;
color: #333333;
margin-left: 16rpx;
}
}
.amount-hint {
font-size: 24rpx;
color: #999999;
text-align: center;
display: block;
margin-bottom: 40rpx;
position: relative;
z-index: 1;
}
.currency-methods {
display: flex;
gap: 20rpx;
margin-bottom: 30rpx;
position: relative;
z-index: 1;
.currency-method {
flex: 1;
height: 80rpx;
background-color: #E8E8E8;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 12rpx;
&.active {
background: linear-gradient(135deg, #FF3B4E 0%, #FF6B7A 100%);
.method-text {
color: #FFFFFF;
}
}
.method-text {
font-size: 28rpx;
color: #666666;
font-weight: 500;
text-align: center;
line-height: 1.2;
}
}
}
.payment-methods {
display: flex;
gap: 16rpx;
margin-bottom: 30rpx;
position: relative;
z-index: 1;
.payment-method {
flex: 1;
height: 88rpx;
background-color: #E8E8E8;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 12rpx;
&.active {
background: linear-gradient(135deg, #4ECDC4 0%, #44A08D 100%);
.method-text {
color: #FFFFFF;
}
}
.method-text {
font-size: 28rpx;
color: #666666;
font-weight: 500;
text-align: center;
line-height: 1.2;
}
}
}
.upload-area {
width: 200rpx;
height: 200rpx;
border: 2rpx dashed #CCCCCC;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 40rpx;
background-color: #FFFFFF;
position: relative;
z-index: 1;
.upload-icon {
font-size: 80rpx;
color: #CCCCCC;
}
.uploaded-image {
width: 100%;
height: 100%;
border-radius: 12rpx;
}
}
.form-item {
margin-bottom: 20rpx;
position: relative;
z-index: 1;
.form-label {
font-size: 24rpx;
color: #666666;
display: block;
margin-bottom: 10rpx;
line-height: 1.5;
word-break: break-word;
}
.form-input {
width: 100%;
height: 80rpx;
background-color: #FFFFFF;
border: 1rpx solid #E5E5E5;
border-radius: 12rpx;
padding: 0 20rpx;
font-size: 26rpx;
box-sizing: border-box;
}
}
.apply-btn {
background: linear-gradient(135deg, #FF3B4E 0%, #FF6B7A 100%);
height: 88rpx;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 1;
margin-top: 20rpx;
.apply-btn-text {
font-size: 32rpx;
font-weight: bold;
color: #FFFFFF;
text-align: center;
}
}
}
}
</style>