文件
fenleixinxi/uniapp/pages/distribution/withdraw-records.vue
MiAn b0eb1134a5 feat: 新增微信小程序虚拟支付提审防审模式及相关功能
本次提交主要实现微信小程序虚拟支付提审防审机制,包括:
1. 后台配置虚拟支付防审开关,支持开启/关闭模式
2. 前端根据开关状态及设备类型,iOS设备自动触发合规提示
3. 分销模块调整提现规则,无门槛即时提现
4. 登录页面优化,增加返回和暂不登录入口,符合微信审核规范
5. 新增提现记录页面,优化分销中心界面布局

该功能确保微信提审期间能顺利通过审核,审核通过后可一键切换为正常运营模式,无需重新发布小程序代码。
2026-11-01 22:41:27 +08:00

633 行
14 KiB
Vue

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<template>
<view class="records-page fc-page">
<!-- 顶部沉浸式导航栏 -->
<fc-navbar title="提现记录" theme="gradient" :show-back="true"></fc-navbar>
<!-- 顶部状态分类轻筛选栏 -->
<view class="records__filter-bar">
<view
v-for="tab in filterTabs"
:key="tab.value"
class="records__filter-item"
:class="{ 'records__filter-item--active': activeStatus === tab.value }"
@click="switchStatus(tab.value)"
>
<text class="records__filter-text">{{ tab.label }}</text>
<text v-if="tab.count > 0" class="records__filter-badge">{{ tab.count }}</text>
</view>
</view>
<!-- 提现列表主体 -->
<view class="records__body">
<!-- 骨架屏加载状态 -->
<view v-if="loading" class="records__skeleton">
<view v-for="i in 4" :key="'skel-' + i" class="records__skeleton-card">
<view class="records__skeleton-left">
<view class="records__skeleton-line fc-skeleton"></view>
<view class="records__skeleton-line records__skeleton-line--short fc-skeleton"></view>
</view>
<view class="records__skeleton-right fc-skeleton"></view>
</view>
</view>
<!-- 纯净通透空状态(去白色卡片束缚,自然居中透气) -->
<view v-else-if="!filteredRecords.length" class="records__empty">
<view class="records__empty-icon-wrap">
<image class="records__empty-icon" :src="iconUrl('receipt')" mode="aspectFit" />
</view>
<text class="records__empty-title">{{ activeStatus === 'all' ? '暂无提现记录' : '暂无此分类记录' }}</text>
<text class="records__empty-sub">推广赚取佣金后,可随时发起微信零钱提现</text>
</view>
<!-- 记录列表卡片 -->
<view v-else class="records__list">
<view
v-for="item in filteredRecords"
:key="item.id"
class="records__card"
@click="goDetail(item)"
>
<view class="records__card-top">
<view class="records__card-type">
<image class="records__type-icon" :src="iconUrl('wallet')" mode="aspectFit" />
<text class="records__type-title">提现到微信零钱</text>
</view>
<!-- 状态标签 -->
<view class="records__badge" :class="'records__badge--' + item.status">
<text class="records__badge-text">{{ statusText(item.status) }}</text>
</view>
</view>
<view class="records__card-middle">
<view class="records__card-meta">
<text class="records__meta-time">{{ formatTimeText(item.createdAt) }}</text>
<text class="records__meta-no fc-ellipsis">单号 {{ item.withdrawNo }}</text>
</view>
<view class="records__card-amount">
<text class="records__amount-symbol">-¥</text>
<text class="records__amount-num">{{ formatAmount(item.amount) }}</text>
</view>
</view>
<!-- 失败原因提示栏 -->
<view v-if="item.status === 'failed' && item.failReason" class="records__fail-row">
<text class="records__fail-label">驳回/失败说明:</text>
<text class="records__fail-text">{{ item.failReason }}</text>
</view>
<!-- 底部操作/去确认收款条 -->
<view class="records__card-footer">
<view v-if="item.status === 'paying'" class="records__action-wrap">
<button
v-if="item.transferStatus === 'WAIT_USER_CONFIRM' && item.packageInfo"
class="records__confirm-btn"
@click.stop="confirmTransfer(item)"
>
<text>去微信确认收款 ›</text>
</button>
<text v-else class="records__wait-text">微信商户打款中,预计稍后到账</text>
</view>
<view v-else class="records__detail-link">
<text>查看流转详情</text>
<text class="records__link-arrow">›</text>
</view>
</view>
</view>
<!-- 底部安全间距与完成提示 -->
<view class="records__list-footer">
<text class="records__list-footer-text">仅展示近期提现流水记录</text>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 提现记录列表页面
* 纯粹轻量财务明细页:状态筛选、流转追溯与微信商家转账收款唤起
*/
import distributionApi from '@/common/api/distribution.js'
import { iconUrl } from '@/common/store/ui.js'
import { formatTime } from '@/common/utils/format.js'
import FcNavbar from '@/components/fc-navbar/fc-navbar.vue'
export default {
name: 'WithdrawRecordsPage',
components: { FcNavbar },
data() {
return {
loading: true,
records: [],
overview: null,
activeStatus: 'all',
}
},
computed: {
/**
* 状态分类选项列表及对应条数
* @returns {Array}
*/
filterTabs() {
const counts = {
all: this.records.length,
paid: 0,
paying: 0,
failed: 0,
}
for (const r of this.records) {
if (r.status === 'paid') counts.paid++
else if (r.status === 'paying' || r.status === 'auditing') counts.paying++
else if (r.status === 'failed' || r.status === 'rejected') counts.failed++
}
return [
{ label: '全部', value: 'all', count: 0 },
{ label: '已到账', value: 'paid', count: counts.paid },
{ label: '处理中', value: 'paying', count: counts.paying },
{ label: '未成功', value: 'failed', count: counts.failed },
]
},
/**
* 筛选后的提现流水列表
* @returns {Array}
*/
filteredRecords() {
if (this.activeStatus === 'all') return this.records
if (this.activeStatus === 'paid') {
return this.records.filter((r) => r.status === 'paid')
}
if (this.activeStatus === 'paying') {
return this.records.filter((r) => r.status === 'paying' || r.status === 'auditing')
}
if (this.activeStatus === 'failed') {
return this.records.filter((r) => r.status === 'failed' || r.status === 'rejected')
}
return this.records
},
},
/**
* 页面加载初始化
*/
onLoad() {
this.fetchData()
},
/**
* 下拉刷新列表
*/
async onPullDownRefresh() {
await this.fetchData()
uni.stopPullDownRefresh()
},
methods: {
iconUrl,
/**
* 切换状态筛选
* @param {string} val 状态值
*/
switchStatus(val) {
this.activeStatus = val
},
/**
* 格式化金额
* @param {number|string} val 数值
* @returns {string} 格式化两位小数
*/
formatAmount(val) {
const n = Number(val)
return isNaN(n) ? '0.00' : n.toFixed(2)
},
/**
* 格式化时间字符串
* @param {string} dateStr 时间串
* @returns {string}
*/
formatTimeText(dateStr) {
if (!dateStr) return '--'
return formatTime(dateStr)
},
/**
* 提现状态文本映射
* @param {string} status 状态码
* @returns {string} 语义化文本
*/
statusText(status) {
const map = {
auditing: '审核中',
paying: '打款处理中',
paid: '提现到账',
rejected: '审核驳回',
failed: '转账失败',
}
return map[status] || '处理中'
},
/**
* 加载提现记录
*/
async fetchData() {
this.loading = true
try {
const [recordsRes, overviewRes] = await Promise.all([
distributionApi.withdrawRecords(),
distributionApi.overview().catch(() => null),
])
this.records = Array.isArray(recordsRes)
? recordsRes
: (recordsRes && recordsRes.list) || []
if (overviewRes) {
this.overview = overviewRes
}
} catch (e) {
uni.showToast({ title: '加载失败,请重试', icon: 'none' })
} finally {
this.loading = false
}
},
/**
* 跳转提现单详情页
* @param {Object} item 提现单据对象
*/
goDetail(item) {
if (!item || !item.id) return
uni.navigateTo({
url: `/pages/distribution/withdraw-detail?id=${item.id}&withdrawNo=${encodeURIComponent(item.withdrawNo || '')}`,
})
},
/**
* 唤起微信商家转账确认收款页(小程序原生组件)
* @param {Object} item 提现单据对象
*/
confirmTransfer(item) {
if (!item || !item.packageInfo) {
this.goDetail(item)
return
}
// #ifdef MP-WEIXIN
if (typeof wx !== 'undefined' && typeof wx.openBusinessView === 'function') {
wx.openBusinessView({
businessType: 'confirmTransfer',
extraData: {
package_info: item.packageInfo,
mch_id: item.transferMchId || (this.overview && this.overview.transferMchId) || '',
appid: item.transferAppId || (this.overview && this.overview.transferAppId) || '',
},
success: (res) => {
if (res && res.extraData && res.extraData.status === 'SUCCESS') {
uni.showToast({ title: '已确认收款', icon: 'success' })
this.fetchData()
}
},
fail: () => {
this.goDetail(item)
},
})
return
}
// #endif
this.goDetail(item)
},
},
}
</script>
<style lang="scss" scoped>
.records-page {
min-height: 100vh;
background: #f2fef7;
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
/* 顶部状态筛选分类栏 */
.records__filter-bar {
display: flex;
align-items: center;
padding: 20rpx 32rpx 12rpx;
gap: 16rpx;
}
.records__filter-item {
padding: 12rpx 28rpx;
border-radius: 32rpx;
background: #ffffff;
border: 1rpx solid rgba(226, 238, 230, 0.85);
display: flex;
align-items: center;
transition: all 0.2s ease;
&--active {
background: #00d26a;
border-color: #00d26a;
.records__filter-text {
color: #04301e;
font-weight: 700;
}
.records__filter-badge {
background: #04301e;
color: #ffffff;
}
}
}
.records__filter-text {
font-size: 26rpx;
color: #64748b;
line-height: 1;
}
.records__filter-badge {
font-size: 20rpx;
background: #f1f5f9;
color: #64748b;
border-radius: 16rpx;
padding: 2rpx 10rpx;
margin-left: 8rpx;
line-height: 1.2;
}
/* 列表主体 */
.records__body {
padding: 12rpx 32rpx;
}
/* 骨架屏 */
.records__skeleton {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.records__skeleton-card {
background: #ffffff;
border-radius: 20rpx;
padding: 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.records__skeleton-left {
width: 60%;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.records__skeleton-line {
height: 28rpx;
border-radius: 8rpx;
&--short {
width: 65%;
height: 22rpx;
}
}
.records__skeleton-right {
width: 140rpx;
height: 48rpx;
border-radius: 8rpx;
}
/* 纯净通透空状态 */
.records__empty {
padding: 140rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.records__empty-icon-wrap {
width: 140rpx;
height: 140rpx;
border-radius: 50%;
background: rgba(0, 210, 106, 0.1);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30rpx;
}
.records__empty-icon {
width: 72rpx;
height: 72rpx;
opacity: 0.7;
}
.records__empty-title {
font-size: 32rpx;
font-weight: 700;
color: #1e293b;
margin-bottom: 12rpx;
}
.records__empty-sub {
font-size: 24rpx;
color: #64748b;
text-align: center;
}
/* 单条提现卡片 */
.records__list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.records__card {
background: #ffffff;
border-radius: 24rpx;
padding: 28rpx 32rpx 24rpx;
border: 1rpx solid rgba(226, 238, 230, 0.9);
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.02);
display: flex;
flex-direction: column;
transition: all 0.2s ease;
&:active {
background: #f9fbf9;
}
}
.records__card-top {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f1f6f3;
}
.records__card-type {
display: flex;
align-items: center;
}
.records__type-icon {
width: 36rpx;
height: 36rpx;
margin-right: 12rpx;
}
.records__type-title {
font-size: 28rpx;
font-weight: 700;
color: #1e293b;
}
/* 状态药丸标签 */
.records__badge {
padding: 6rpx 18rpx;
border-radius: 20rpx;
font-size: 22rpx;
font-weight: 600;
&--paid {
background: #e8f9f0;
color: #00a854;
}
&--paying {
background: #eff6ff;
color: #2563eb;
}
&--auditing {
background: #fffbeb;
color: #b45309;
}
&--rejected,
&--failed {
background: #fef2f2;
color: #dc2626;
}
}
.records__badge-text {
line-height: 1;
}
/* 中部数值与单号 */
.records__card-middle {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 0 20rpx;
}
.records__card-meta {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.records__meta-time {
font-size: 24rpx;
color: #64748b;
}
.records__meta-no {
font-size: 22rpx;
color: #94a3b8;
max-width: 360rpx;
}
.records__card-amount {
display: flex;
align-items: baseline;
color: #1e293b;
}
.records__amount-symbol {
font-size: 26rpx;
font-weight: 700;
margin-right: 2rpx;
}
.records__amount-num {
font-size: 42rpx;
font-weight: 800;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* 驳回与失败行 */
.records__fail-row {
background: #fef2f2;
border-radius: 12rpx;
padding: 12rpx 18rpx;
margin-bottom: 16rpx;
display: flex;
align-items: flex-start;
}
.records__fail-label {
font-size: 22rpx;
color: #dc2626;
font-weight: 600;
flex-shrink: 0;
}
.records__fail-text {
font-size: 22rpx;
color: #dc2626;
line-height: 1.4;
}
/* 底部操作行 */
.records__card-footer {
padding-top: 16rpx;
border-top: 1rpx dashed #edf3f0;
display: flex;
align-items: center;
justify-content: flex-end;
}
.records__detail-link {
display: flex;
align-items: center;
font-size: 24rpx;
color: #64748b;
}
.records__link-arrow {
font-size: 28rpx;
margin-left: 6rpx;
line-height: 1;
}
.records__confirm-btn {
background: #00d26a;
color: #04301e;
font-size: 24rpx;
font-weight: 700;
border-radius: 24rpx;
padding: 8rpx 24rpx;
margin: 0;
line-height: 1.6;
border: none;
&::after {
border: none;
}
}
.records__wait-text {
font-size: 22rpx;
color: #64748b;
}
/* 列表底部 */
.records__list-footer {
text-align: center;
padding: 30rpx 0 10rpx;
}
.records__list-footer-text {
font-size: 22rpx;
color: #94a3b8;
}
</style>