bm-bmt/pages/assets/points-convert.vue

560 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="asset-page asset-theme points-home-page">
<asset-page-shell title="积分转换" />
<view class="asset-scroll points-home-scroll">
<view class="hero-card">
<view class="hero-card__content">
<view class="hero-card__coin-row">
<view class="hero-card__coin">
<image
class="hero-card__coin-image"
src="https://imgs.agrimedia.cn/bm-bmt/j-w.png"
mode="aspectFit"
></image>
</view>
<text class="hero-card__coin-text">积分</text>
</view>
<text class="hero-card__label">当前可用积分</text>
<view class="hero-card__bottom">
<text class="hero-card__value asset-number-font">{{
detail.availablePoints
}}</text>
<view class="hero-card__button" @click="openConvertList">
积分转换
</view>
</view>
</view>
</view>
<picker
class="month-picker"
mode="selector"
:range="monthOptions"
:value="monthIndex"
@change="handleMonthChange"
>
<view class="month-picker__inner">
<text class="month-picker__text">{{
activeMonth
}}</text>
<text class="month-picker__arrow"></text>
</view>
</picker>
<view v-if="filteredRecords.length" class="record-list">
<view
v-for="item in filteredRecords"
:key="item.id + '-' + item.time"
class="record-card"
:class="{ 'record-card--clickable': item.detailAvailable }"
@click="openRecord(item)"
>
<view class="record-card__main">
<text class="record-card__title">{{ item.title }}</text>
<text class="record-card__time asset-number-font">{{
item.time
}}</text>
</view>
<view class="record-card__side">
<view class="record-card__amount-row">
<text
class="record-card__amount asset-number-font"
:class="'record-card__amount--' + item.amountTone"
>
{{ item.amount }}
</text>
<text v-if="item.detailAvailable" class="record-card__arrow"
>&gt;</text
>
</view>
</view>
</view>
<view v-if="showLoadMoreState" class="record-load-more">
<text class="record-load-more__text asset-number-font">{{
loadMoreText
}}</text>
</view>
</view>
<view v-else class="empty-card">
<text class="empty-card__title">暂无转换记录</text>
<text class="empty-card__desc">当前月份还没有积分转换明细</text>
</view>
</view>
</view>
</template>
<script>
import AssetPageShell from "../../components/asset-page-shell.vue";
import { fetchPointsConvertHome } from "../../api/assets";
export default {
components: {
AssetPageShell,
},
data() {
return {
hasShown: false,
selectedMonth: "",
detail: {
availablePoints: "0",
records: [],
},
page: 1,
pageSize: 10,
hasMore: false,
loadingMore: false,
};
},
computed: {
monthOptions() {
return this.buildMonthOptions();
},
monthIndex() {
const currentIndex = this.monthOptions.indexOf(this.activeMonth);
return currentIndex > -1 ? currentIndex : 0;
},
activeMonth() {
return this.selectedMonth || this.currentMonth();
},
filteredRecords() {
return Array.isArray(this.detail.records) ? this.detail.records : [];
},
showLoadMoreState() {
return this.filteredRecords.length > 0;
},
loadMoreText() {
if (this.loadingMore) {
return "加载中...";
}
if (this.hasMore) {
return "上拉加载更多";
}
return "没有更多了";
},
},
onLoad() {
this.selectedMonth = this.currentMonth();
this.resetPagingState();
this.loadPage(true, 1);
},
onShow() {
if (this.hasShown) {
this.resetPagingState();
this.loadPage(false, 1);
return;
}
this.hasShown = true;
},
onReachBottom() {
this.loadMore();
},
methods: {
resetPagingState() {
this.page = 1;
this.hasMore = false;
this.loadingMore = false;
},
updatePaging(result, requestedPage, receivedLength) {
const pagination =
result && result.pagination && typeof result.pagination === "object"
? result.pagination
: null;
this.page =
Number(pagination && pagination.page) || Number(requestedPage) || this.page || 1;
if (pagination && typeof pagination.hasMore === "boolean") {
this.hasMore = pagination.hasMore;
return;
}
this.hasMore = receivedLength >= this.pageSize;
},
mergeRecords(currentRecords, nextRecords) {
const mergedMap = {};
const mergedList = [];
const appendItem = (item) => {
const key =
String(item && item.id ? item.id : "").trim() ||
[
item && item.orderSn,
item && item.time,
item && item.title,
item && item.amount,
]
.map((value) => String(value || ""))
.join("-");
if (!key || mergedMap[key]) {
return;
}
mergedMap[key] = true;
mergedList.push(item);
};
(Array.isArray(currentRecords) ? currentRecords : []).forEach(appendItem);
(Array.isArray(nextRecords) ? nextRecords : []).forEach(appendItem);
return mergedList;
},
currentMonth() {
const currentDate = new Date();
return (
currentDate.getFullYear() +
"-" +
String(currentDate.getMonth() + 1).padStart(2, "0")
);
},
buildMonthOptions() {
const currentDate = new Date();
const monthList = [];
for (let index = 0; index < 12; index += 1) {
const cursor = new Date(
currentDate.getFullYear(),
currentDate.getMonth() - index,
1,
);
monthList.push(
cursor.getFullYear() +
"-" +
String(cursor.getMonth() + 1).padStart(2, "0"),
);
}
return monthList;
},
handleMonthChange(event) {
const nextIndex = Number(
event && event.detail ? event.detail.value : 0,
);
this.selectedMonth = this.monthOptions[nextIndex] || this.activeMonth;
this.resetPagingState();
this.loadPage(true, 1);
},
async loadPage(showLoading, targetPage) {
const page = Number(targetPage || 1);
const isLoadMore = page > 1;
if (isLoadMore && this.loadingMore) {
return;
}
if (isLoadMore) {
this.loadingMore = true;
}
try {
const requestOptions =
!isLoadMore && showLoading
? {
showLoading: true,
loadingText: "加载中",
}
: null;
const result = await fetchPointsConvertHome(
this.activeMonth,
{
page: page,
pageSize: this.pageSize,
},
requestOptions,
);
const incomingRecords = Array.isArray(result.records) ? result.records : [];
const previousCount = this.filteredRecords.length;
const mergedRecords = isLoadMore
? this.mergeRecords(this.filteredRecords, incomingRecords)
: incomingRecords;
this.detail = Object.assign({}, this.detail, result, {
records: mergedRecords,
});
this.updatePaging(result, page, incomingRecords.length);
if (isLoadMore && mergedRecords.length <= previousCount) {
this.hasMore = false;
}
} catch (error) {
uni.showToast({
title: error.message || (isLoadMore ? "加载更多失败" : "页面加载失败"),
icon: "none",
});
} finally {
if (isLoadMore) {
this.loadingMore = false;
}
}
},
loadMore() {
if (this.loadingMore || !this.hasMore) {
return;
}
this.loadPage(false, this.page + 1);
},
openConvertList() {
uni.navigateTo({
url: "/pages/assets/points-convert-list",
});
},
openRecord(item) {
if (!item || !item.detailAvailable) {
return;
}
const payload = encodeURIComponent(
JSON.stringify({
id: item.id,
sourceId: item.sourceId,
orderSn: item.orderSn,
title: item.title,
amount: item.amount,
time: item.time,
}),
);
uni.navigateTo({
url: "/pages/assets/points-convert-detail?payload=" + payload,
});
},
},
};
</script>
<style lang="scss" scoped>
@import "../../styles/tokens.scss";
@import "../../styles/common.scss";
.points-home-page {
min-height: 100vh;
background: #191e32;
}
.points-home-scroll {
min-height: calc(100vh - env(safe-area-inset-top) - 104rpx);
padding: 10rpx 14rpx calc(env(safe-area-inset-bottom) + 36rpx);
}
.hero-card {
position: relative;
overflow: hidden;
min-height: 248rpx;
padding: 24rpx 28rpx;
border-radius: 10rpx;
background: #242944 url("https://imgs.agrimedia.cn/bm-bmt/jifen-header.png")
no-repeat center top / 100% 248rpx;
box-shadow: 0 12rpx 24rpx rgba(8, 13, 30, 0.16);
}
.hero-card__content {
position: relative;
z-index: 1;
}
.hero-card__coin-row,
.hero-card__bottom,
.record-card,
.record-card__amount-row,
.month-picker__inner {
display: flex;
align-items: center;
}
.hero-card__coin {
display: flex;
align-items: center;
justify-content: center;
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background: linear-gradient(135deg, #f85ca9 0%, #de4d95 100%);
box-shadow: 0 12rpx 20rpx rgba(230, 90, 156, 0.2);
}
.hero-card__coin-image {
width: 30rpx;
height: 30rpx;
}
.hero-card__coin-text {
margin-left: 16rpx;
font-size: 32rpx;
font-weight: 500;
color: #ffffff;
}
.hero-card__label {
display: block;
margin-top: 38rpx;
font-size: 28rpx;
color: rgba(233, 239, 255, 0.9);
}
.hero-card__bottom {
margin-top: 10rpx;
}
.hero-card__value {
max-width: 360rpx;
font-size: 58rpx;
font-weight: 800;
line-height: 1.05;
color: #ffffff;
margin-right: 20rpx;
}
.hero-card__button {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 166rpx;
height: 48rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, #1ec5ff 0%, #179fe8 100%);
box-shadow: 0 14rpx 24rpx rgba(27, 179, 242, 0.22);
font-size: 26rpx;
font-weight: 500;
color: #ffffff;
}
.month-picker {
display: inline-block;
margin-top: 22rpx;
}
.month-picker__inner {
justify-content: space-between;
width: 210rpx;
height: 64rpx;
padding: 0 18rpx;
border-radius: 6rpx;
background: #4a5174;
}
.month-picker__text {
font-size: 26rpx;
color: #ffffff;
}
.month-picker__arrow {
font-size: 18rpx;
color: rgba(255, 255, 255, 0.92);
}
.record-list {
margin-top: 22rpx;
}
.record-card,
.empty-card {
border-radius: 10rpx;
background: #242944;
box-shadow: 0 12rpx 24rpx rgba(8, 13, 30, 0.14);
}
.record-card {
justify-content: space-between;
padding: 26rpx 28rpx;
margin-top: 18rpx;
}
.record-card:first-child {
margin-top: 0;
}
.record-card--clickable {
cursor: pointer;
}
.record-card__main {
flex: 1;
min-width: 0;
}
.record-card__title {
display: block;
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
}
.record-card__time {
display: block;
margin-top: 14rpx;
font-size: 22rpx;
color: rgba(158, 170, 204, 0.9);
}
.record-card__side {
display: flex;
justify-content: flex-end;
margin-left: 20rpx;
}
.record-card__amount-row {
justify-content: flex-end;
}
.record-card__amount {
font-size: 28rpx;
font-weight: 700;
}
.record-card__amount--increase {
color: #ff8b63;
}
.record-card__amount--decrease {
color: #20ddb0;
}
.record-card__arrow {
margin-left: 16rpx;
font-size: 38rpx;
line-height: 1;
color: rgba(255, 255, 255, 0.96);
}
.record-load-more {
display: flex;
align-items: center;
justify-content: center;
padding: 28rpx 0 8rpx;
}
.record-load-more__text {
font-size: 24rpx;
color: rgba(179, 189, 214, 0.78);
}
.empty-card {
margin-top: 22rpx;
padding: 60rpx 28rpx;
text-align: center;
}
.empty-card__title {
display: block;
font-size: 28rpx;
color: #ffffff;
}
.empty-card__desc {
display: block;
margin-top: 14rpx;
font-size: 22rpx;
color: rgba(158, 170, 204, 0.86);
}
</style>