cashier/pages/records/index.vue

273 lines
5.7 KiB
Vue

<template>
<view class="records-page">
<view v-if="records.length" class="records-list">
<view v-for="item in records" :key="item.trade_no" class="record-card">
<view class="record-header">
<text class="record-merchant li-single-line">{{ item.supplier_name || '收款商家' }}</text>
<text class="record-status" :class="statusClass(item.pay_status)">{{ formatPayStatus(item.pay_status) }}</text>
</view>
<view class="record-amount-row">
<text class="record-amount-label">实付金额</text>
<text class="record-amount">¥ {{ formatMoney(item.real_money) }}</text>
</view>
<view v-if="showWuyeMoney(item)" class="record-meta-row">
<text class="record-meta-label">物业返还</text>
<text class="record-meta-value">¥ {{ formatMoney(item.wuye_money) }}</text>
</view>
<view class="record-meta-row">
<text class="record-meta-label">支付方式</text>
<text class="record-meta-value">{{ formatPayType(item.pay_type) }}</text>
</view>
<view class="record-meta-row">
<text class="record-meta-label">支付时间</text>
<text class="record-meta-value">{{ item.pay_time || item.create_time || '-' }}</text>
</view>
<view class="record-footer">
<text class="record-trade-no">单号 {{ item.trade_no }}</text>
</view>
</view>
</view>
<view v-else-if="!loading" class="records-empty">
<wd-status-tip :image="emptyImage" tip="暂无付款记录" />
</view>
<view v-if="records.length && (loading || finished)" class="records-footer">
<wd-loadmore v-if="loading" state="loading" />
<text v-else class="records-finished">没有更多了</text>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
import { tradeList } from '@/api/cashier';
import type { TradeListItem } from '@/api/cashier';
import { APP_CONFIG } from '@/config';
const PAGE_SIZE = 10;
const records = ref<TradeListItem[]>([]);
const page = ref(1);
const loading = ref(false);
const finished = ref(false);
const emptyImage = `${APP_CONFIG.RESOURCE_URL}tip/message.png`;
const formatMoney = (value?: string | number) => {
const amount = Number(value || 0);
if (!Number.isFinite(amount)) return '0.00';
return amount.toFixed(2);
};
const formatPayStatus = (status?: number) => {
const map: Record<number, string> = {
0: '未支付',
1: '已支付',
2: '部分退款',
3: '全额退款',
4: '已取消'
};
return map[Number(status)] || '未知状态';
};
const statusClass = (status?: number) => {
const value = Number(status);
if (value === 1) return 'status-success';
if (value === 0) return 'status-pending';
if (value === 4) return 'status-cancel';
return 'status-refund';
};
const formatPayType = (payType?: number) => {
const map: Record<number, string> = {
1: '微信',
2: '支付宝',
3: '云闪付',
4: '现金',
5: '余额',
6: '线下'
};
return map[Number(payType)] || '-';
};
const showWuyeMoney = (item: TradeListItem) => Number(item.wuye_money || 0) > 0;
const fetchRecords = async (reset = false) => {
if (loading.value) return;
if (!reset && finished.value) return;
loading.value = true;
const nextPage = reset ? 1 : page.value;
try {
const response = await tradeList({
trade_no: '',
page: nextPage,
limit: PAGE_SIZE
});
if (Number(response?.code) !== 200) {
throw new Error(response?.msg || '获取付款记录失败');
}
const list = Array.isArray(response?.data) ? response.data : [];
records.value = reset ? list : records.value.concat(list);
page.value = nextPage + 1;
finished.value = list.length < PAGE_SIZE;
} catch (error: any) {
uni.showToast({
title: error?.message || '获取付款记录失败',
icon: 'none'
});
} finally {
loading.value = false;
if (reset) {
uni.stopPullDownRefresh();
}
}
};
onLoad(() => {
fetchRecords(true);
});
onPullDownRefresh(() => {
finished.value = false;
fetchRecords(true);
});
onReachBottom(() => {
fetchRecords(false);
});
</script>
<style lang="scss" scoped>
.records-page {
min-height: 100vh;
padding: 24rpx;
background: #f4f6f8;
}
.records-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.record-card {
padding: 28rpx 28rpx 24rpx;
border-radius: 16rpx;
background: #ffffff;
box-shadow: 0 8rpx 22rpx rgba(17, 24, 39, 0.04);
}
.record-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
.record-merchant {
flex: 1;
min-width: 0;
color: #111827;
font-size: 30rpx;
font-weight: 700;
}
.record-status {
flex-shrink: 0;
padding: 6rpx 16rpx;
border-radius: 999rpx;
font-size: 22rpx;
font-weight: 600;
}
.status-success {
background: #e8f2ff;
color: #1f5bd8;
}
.status-pending {
background: #fff7e8;
color: #d48806;
}
.status-cancel {
background: #f3f4f6;
color: #6b7280;
}
.status-refund {
background: #fff1f0;
color: #cf1322;
}
.record-amount-row {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-top: 24rpx;
}
.record-amount-label {
color: #8b929e;
font-size: 26rpx;
}
.record-amount {
color: #111827;
font-size: 40rpx;
font-weight: 800;
}
.record-meta-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 18rpx;
}
.record-meta-label {
color: #8b929e;
font-size: 26rpx;
}
.record-meta-value {
max-width: 420rpx;
color: #374151;
font-size: 26rpx;
text-align: right;
}
.record-footer {
margin-top: 22rpx;
padding-top: 20rpx;
border-top: 1rpx solid #eef1f5;
}
.record-trade-no {
color: #9ca3af;
font-size: 22rpx;
}
.records-empty {
padding-top: 300rpx;
}
.records-footer {
padding: 12rpx 0 24rpx;
}
.records-finished {
display: block;
padding: 8rpx 0;
color: #9ca3af;
font-size: 24rpx;
text-align: center;
}
</style>