202 lines
4.7 KiB
Vue
202 lines
4.7 KiB
Vue
<template>
|
|
<view class="score-records-page">
|
|
<view v-if="records.length" class="records-list">
|
|
<view v-for="(item, index) in records" :key="recordKey(item, index)" class="record-card">
|
|
<view class="record-header">
|
|
<text class="record-title">{{ item.remark || formatRecordType(item) }}</text>
|
|
<text class="record-score" :class="scoreClass(item)">{{ formatScoreChange(item) }}</text>
|
|
</view>
|
|
<view class="record-meta-row">
|
|
<text class="record-meta-label">时间</text>
|
|
<text class="record-meta-value">{{ item.create_time || '-' }}</text>
|
|
</view>
|
|
<view v-if="item.score !== undefined && item.score !== null && item.score !== ''" class="record-meta-row">
|
|
<text class="record-meta-label">余额</text>
|
|
<text class="record-meta-value">¥ {{ formatMoney(item.score) }}</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 :state="loadMoreState" />
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref } from 'vue';
|
|
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
|
|
import { platformScoreRecord } from '@/api/cashier';
|
|
import type { ScoreRecordItem } from '@/api/cashier';
|
|
import { APP_CONFIG } from '@/config';
|
|
|
|
const PAGE_SIZE = 10;
|
|
|
|
const records = ref<ScoreRecordItem[]>([]);
|
|
const page = ref(1);
|
|
const loading = ref(false);
|
|
const finished = ref(false);
|
|
|
|
const emptyImage = `${APP_CONFIG.RESOURCE_URL}tip/message.png`;
|
|
const loadMoreState = computed(() => (loading.value ? 'loading' : 'finished'));
|
|
|
|
const formatMoney = (value?: string | number) => {
|
|
const amount = Number(value || 0);
|
|
if (!Number.isFinite(amount)) return '0.00';
|
|
return amount.toFixed(2);
|
|
};
|
|
|
|
const formatRecordType = (item: ScoreRecordItem) => {
|
|
const type = String(item.type != null ? item.type : '');
|
|
const map: Record<string, string> = {
|
|
'1': '积分收入',
|
|
'2': '积分支出',
|
|
'3': '积分返还'
|
|
};
|
|
return map[type] || '积分变动';
|
|
};
|
|
|
|
const formatScoreChange = (item: ScoreRecordItem) => {
|
|
const change = item.change_score != null ? item.change_score : item.score;
|
|
const amount = Number(change || 0);
|
|
if (!Number.isFinite(amount)) return '0.00';
|
|
const prefix = amount > 0 ? '+' : '';
|
|
return `${prefix}${amount.toFixed(2)}`;
|
|
};
|
|
|
|
const scoreClass = (item: ScoreRecordItem) => {
|
|
const raw = item.change_score != null ? item.change_score : item.score;
|
|
const change = Number(raw != null ? raw : 0);
|
|
return change >= 0 ? 'score-plus' : 'score-minus';
|
|
};
|
|
|
|
const recordKey = (item: ScoreRecordItem, index: number) =>
|
|
String(item.id != null ? item.id : `${item.create_time || 'record'}-${index}`);
|
|
|
|
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 platformScoreRecord({
|
|
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>
|
|
.score-records-page {
|
|
min-height: 100vh;
|
|
padding: 24rpx;
|
|
background: #f4f6f8;
|
|
}
|
|
|
|
.records-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20rpx;
|
|
}
|
|
|
|
.record-card {
|
|
padding: 28rpx;
|
|
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-title {
|
|
flex: 1;
|
|
min-width: 0;
|
|
color: #111827;
|
|
font-size: 30rpx;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.record-score {
|
|
flex-shrink: 0;
|
|
font-size: 34rpx;
|
|
font-weight: 800;
|
|
}
|
|
|
|
.score-plus {
|
|
color: #1f5bd8;
|
|
}
|
|
|
|
.score-minus {
|
|
color: #ef4444;
|
|
}
|
|
|
|
.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 {
|
|
color: #374151;
|
|
font-size: 26rpx;
|
|
}
|
|
|
|
.records-empty {
|
|
padding-top: 300rpx;
|
|
}
|
|
|
|
.records-footer {
|
|
padding: 12rpx 0 24rpx;
|
|
}
|
|
</style>
|