bm-bmt/pages/assets/bmtr.vue

536 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 bmtr-page">
<asset-page-shell title="BMTR" />
<view class="bmtr-scroll">
<view class="bmtr-hero">
<view class="bmtr-hero__main">
<view class="bmtr-hero__head">
<view class="bmtr-hero__badge">
<text class="bmtr-hero__badge-text">R</text>
</view>
<text class="bmtr-hero__title">BMTR</text>
</view>
<text class="bmtr-hero__label">可用 BMTR</text>
<text class="bmtr-hero__value asset-number-font">{{
summary.available
}}</text>
<!-- <text class="bmtr-hero__total asset-number-font">累计{{ summary.total }}</text> -->
</view>
<!-- 设计图右侧的钱包插图替换 heroImage 为实际图片地址即可显示 -->
<image v-if="heroImage" class="bmtr-hero__image" :src="heroImage" mode="aspectFit"></image>
</view>
<view class="bmtr-address glass-panel" v-if="displayAddress">
<view class="bmtr-address__head">
<image class="bmtr-address__icon" src="https://imgs.agrimedia.cn/bm-bmt/qianbao.png" mode="aspectFit"></image>
<text class="bmtr-address__title">白马提回地址</text>
</view>
<view class="bmtr-address__field">
<view class="bmtr-address__content">
<text class="bmtr-address__label">从交易所提回BMTR的地址</text>
<text class="bmtr-address__value asset-number-font">{{ displayAddress }}</text>
</view>
<view class="bmtr-address__actions">
<text class="bmtr-address__action" @click="toggleAddress">{{
isAddressVisible ? "隐藏" : "显示"
}}</text>
<text class="bmtr-address__action" @click="copyAddress">复制</text>
</view>
</view>
</view>
<text class="bmtr-section-title">BMTR记录</text>
<view v-if="records.length" class="bmtr-records glass-panel">
<view v-for="item in records" :key="item.id" class="bmtr-record">
<view class="bmtr-record__main">
<text class="bmtr-record__title">{{ item.title }}</text>
<text class="bmtr-record__time">{{ item.time }}</text>
</view>
<text class="bmtr-record__amount asset-number-font" :class="item.amount >= 0
? 'bmtr-record__amount--in'
: 'bmtr-record__amount--out'
">{{ formatAmount(item.amount) }}</text>
</view>
</view>
<view v-else class="bmtr-records-empty glass-panel">
<text class="bmtr-records-empty__text">暂无记录</text>
</view>
<view v-if="showLoadMoreState" class="list-load-more">
<text class="list-load-more__text asset-number-font">{{ loadMoreText }}</text>
</view>
</view>
</view>
</template>
<script>
import AssetPageShell from "../../components/asset-page-shell.vue";
import { fetchBmtrDetail, fetchBmtrRecords } from "../../api/assets";
export default {
components: {
AssetPageShell,
},
data() {
return {
heroImage: "",
summary: {
available: "0.0000",
total: "0.00",
},
address: "",
isAddressVisible: false,
records: [],
page: 1,
pageSize: 10,
hasMore: false,
loadingMore: false,
hasShown: false,
};
},
computed: {
displayAddress() {
if (this.isAddressVisible) {
return this.address;
}
return this.maskAddress(this.address);
},
showLoadMoreState() {
return this.records.length > 0;
},
loadMoreText() {
if (this.loadingMore) {
return "加载中...";
}
if (this.hasMore) {
return "上拉加载更多";
}
return "没有更多了";
},
},
onLoad() {
this.resetPagingState();
this.loadDetail(true);
this.loadRecords(false, 1);
},
onShow() {
if (this.hasShown) {
this.resetPagingState();
this.loadDetail(false);
this.loadRecords(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();
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;
},
async loadDetail(showLoading) {
try {
const detail = await fetchBmtrDetail(
showLoading
? {
showLoading: true,
loadingText: "加载中",
}
: null,
);
this.summary = Object.assign({}, this.summary, {
available: detail.available,
});
this.address = detail.address;
} catch (error) {
uni.showToast({
title: error.message || "页面加载失败",
icon: "none",
});
}
},
async loadRecords(showLoading, targetPage) {
const page = Number(targetPage || 1);
const isLoadMore = page > 1;
if (isLoadMore && this.loadingMore) {
return;
}
if (isLoadMore) {
this.loadingMore = true;
}
try {
const result = await fetchBmtrRecords(
{
page: page,
pageSize: this.pageSize,
},
!isLoadMore && showLoading
? {
showLoading: true,
loadingText: "加载中",
}
: null,
);
const incomingRecords = Array.isArray(result.records)
? result.records
: [];
const previousCount = this.records.length;
const mergedRecords = isLoadMore
? this.mergeRecords(this.records, incomingRecords)
: incomingRecords;
this.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.loadRecords(false, this.page + 1);
},
maskAddress(address) {
const safeAddress = String(address || "");
if (!safeAddress) {
return "";
}
if (safeAddress.length <= 4) {
return "****";
}
const headLength = Math.min(4, Math.ceil(safeAddress.length / 3));
const tailLength = Math.min(4, Math.floor(safeAddress.length / 3));
return (
safeAddress.slice(0, headLength) +
"****" +
safeAddress.slice(-tailLength)
);
},
toggleAddress() {
this.isAddressVisible = !this.isAddressVisible;
},
copyAddress() {
uni.setClipboardData({
data: this.address,
success() {
uni.showToast({
title: "已复制",
icon: "none",
});
},
});
},
formatAmount(amount) {
const number = Number(amount || 0);
return (number >= 0 ? "+" : "-") + Math.abs(number);
},
},
};
</script>
<style lang="scss" scoped>
@import "../../styles/tokens.scss";
@import "../../styles/common.scss";
.bmtr-page {
min-height: 100vh;
}
.bmtr-scroll {
padding: 12rpx 24rpx calc(env(safe-area-inset-bottom) + 36rpx);
}
.bmtr-hero {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-radius: 24rpx;
border: 1px solid rgba(143, 167, 207, 0.16);
background:
url("https://imgs.agrimedia.cn/webimg/202607221601148466081.png") center /
cover no-repeat,
radial-gradient(circle at 88% 30%,
rgba(76, 201, 255, 0.16),
transparent 55%),
linear-gradient(135deg, #1d2b52 0%, #14203f 100%);
overflow: hidden;
}
.bmtr-hero__main {
display: flex;
flex-direction: column;
min-width: 0;
}
.bmtr-hero__head {
display: flex;
align-items: center;
}
.bmtr-hero__badge {
display: flex;
align-items: center;
justify-content: center;
width: 64rpx;
height: 64rpx;
margin-right: 16rpx;
border-radius: 50%;
background: #ff5a5a;
}
.bmtr-hero__badge-text {
font-size: 36rpx;
font-weight: 800;
color: #ffffff;
}
.bmtr-hero__title {
font-size: 36rpx;
font-weight: 700;
color: $asset-text-main;
}
.bmtr-hero__label {
margin-top: 24rpx;
font-size: 26rpx;
color: $asset-text-subtle;
}
.bmtr-hero__value {
margin-top: 8rpx;
font-size: 56rpx;
font-weight: 800;
line-height: 1.1;
color: $asset-text-main;
}
.bmtr-hero__total {
margin-top: 12rpx;
font-size: 28rpx;
font-weight: 600;
color: #35d9a0;
}
.bmtr-hero__image {
width: 260rpx;
height: 200rpx;
flex: 0 0 auto;
margin-left: 24rpx;
}
.bmtr-address {
margin-top: 24rpx;
padding: 28rpx;
}
.bmtr-address__head {
display: flex;
align-items: center;
}
.bmtr-address__icon {
width: 40rpx;
height: 40rpx;
margin-right: 12rpx;
}
.bmtr-address__title {
font-size: 32rpx;
font-weight: 700;
color: $asset-text-main;
}
.bmtr-address__field {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 24rpx;
padding: 24rpx;
border-radius: 14rpx;
background: rgba(18, 25, 48, 0.72);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.bmtr-address__content {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
}
.bmtr-address__label {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.62);
}
.bmtr-address__value {
margin-top: 12rpx;
font-size: 28rpx;
font-weight: 600;
word-break: break-all;
color: $asset-text-main;
}
.bmtr-address__actions {
display: flex;
align-items: center;
flex: 0 0 auto;
margin-left: 24rpx;
}
.bmtr-address__action {
font-size: 26rpx;
font-weight: 600;
color: #38d0bd;
}
.bmtr-address__action+.bmtr-address__action {
margin-left: 28rpx;
}
.bmtr-section-title {
display: block;
margin: 36rpx 0 20rpx;
font-size: 32rpx;
font-weight: 700;
color: $asset-text-main;
}
.bmtr-records {
padding: 0 28rpx;
}
.bmtr-record {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.bmtr-record:last-child {
border-bottom: 0;
}
.bmtr-record__main {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
}
.bmtr-record__title {
font-size: 30rpx;
font-weight: 600;
color: $asset-text-main;
}
.bmtr-record__time {
margin-top: 10rpx;
font-size: 24rpx;
color: rgba(255, 255, 255, 0.45);
}
.bmtr-record__amount {
flex: 0 0 auto;
margin-left: 24rpx;
font-size: 36rpx;
font-weight: 800;
}
.bmtr-record__amount--in {
color: #ff9440;
}
.bmtr-record__amount--out {
color: #35d9a0;
}
.bmtr-records-empty {
display: flex;
justify-content: center;
padding: 70rpx 24rpx;
}
.bmtr-records-empty__text {
font-size: 26rpx;
color: rgba(158, 170, 204, 0.86);
}
.list-load-more {
display: flex;
justify-content: center;
padding: 12rpx 0 22rpx;
}
.list-load-more__text {
font-size: 22rpx;
color: rgba(173, 182, 211, 0.9);
}
</style>