bm-bmt/pages/assets/bmt-exchange.vue

790 lines
18 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 bmt-page">
<asset-page-shell title="BMT兑换" />
<view class="asset-scroll bmt-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="pageIcon('bmt')"
mode="aspectFit"
></image>
</view>
<text class="hero-card__coin-text">BMT</text>
</view>
<text class="hero-card__label">可用BMT:</text>
<text class="hero-card__value asset-number-font">{{ displayBmt }}</text>
</view>
</view>
<view class="panel-card info-card">
<view class="info-row">
<view class="info-row__left">
<image
class="info-row__icon"
:src="pageIcon('voucher')"
mode="aspectFit"
></image>
<text class="info-row__label">我的算力</text>
</view>
<text class="info-row__value asset-number-font">{{ displayVoucher }}</text>
</view>
<view class="info-row">
<view class="info-row__left">
<image
class="info-row__icon"
:src="pageIcon('coupon')"
mode="aspectFit"
></image>
<text class="info-row__label">可用积分</text>
</view>
<text class="info-row__value asset-number-font">{{ displayCoupon }}</text>
</view>
<view class="info-row">
<view class="info-row__left">
<image
class="info-row__icon"
:src="pageIcon('bmtr')"
mode="aspectFit"
></image>
<text class="info-row__label">BMTR数量</text>
</view>
<text class="info-row__value asset-number-font">{{ displayBmtr }}</text>
</view>
<view class="info-row info-row--last">
<view class="info-row__left">
<image
class="info-row__icon"
src="https://imgs.agrimedia.cn/bm-bmt/b.png"
mode="aspectFit"
></image>
<text class="info-row__label">BMT实时价格</text>
</view>
<view class="info-row__price">
<text class="info-row__value asset-number-font">{{ displayPrice }}</text>
<text class="info-row__unit">BMT/CNY</text>
</view>
</view>
<view class="info-card__estimate">
<text>全部兑换预估可得</text>
<text class="info-card__estimate-value asset-number-font">{{ allEstimateBmt }}</text>
<text>BMT</text>
</view>
</view>
<view class="panel-card form-card">
<view class="form-card__title">
<image
class="form-card__title-icon"
src="https://imgs.agrimedia.cn/bm-bmt/b.png"
mode="aspectFit"
></image>
<text class="form-card__title-text">BMT兑换</text>
</view>
<view class="form-input">
<decimal-input
v-model="form.points"
class="form-input__field asset-number-font"
:digit="0"
placeholder="请输入积分数量"
placeholder-style="color: rgba(177, 187, 214, 0.42);"
/>
<text class="form-input__suffix">可用积分</text>
</view>
<view class="preview-card">
<view class="preview-card__row">
<view class="preview-card__left">
<text class="preview-card__label">预估兑换BMT</text>
</view>
<text class="preview-card__value asset-number-font">{{ estimateBmt }}</text>
</view>
<view class="preview-card__row">
<view class="preview-card__left">
<text class="preview-card__label" style="color: #fff"
>消耗算力</text
>
</view>
<text class="preview-card__value preview-card__value--light asset-number-font">{{
powerCost
}}</text>
</view>
<view class="preview-card__row">
<view class="preview-card__left">
<text class="preview-card__label preview-card__label--light"
>消耗BMTR</text
>
</view>
<text class="preview-card__value preview-card__value--light asset-number-font">{{
bmtrCost
}}</text>
</view>
</view>
<view class="tips-block">
<text class="tips-block__title">兑换说明:</text>
<view
v-for="(tip, index) in normalizedTips"
:key="tip"
class="tips-block__item"
>
<text class="tips-block__index asset-number-font">{{ index + 1 }}.</text>
<text class="tips-block__text asset-number-font">{{ tip }}</text>
</view>
</view>
</view>
<view class="action-wrap">
<view
class="action-button"
@touchend.stop.prevent="openConfirm"
@tap.stop="openConfirm"
@click.stop="openConfirm"
>确认兑换</view
>
<view class="action-link" @click="openLedger">兑换记录</view>
</view>
</view>
<asset-confirm-popup
:visible="confirmVisible"
title="兑换提示"
:message="
'确认使用 ' + formatAmount(pointsValue, 0) + ' 积分兑换 BMT 吗?'
"
@cancel="confirmVisible = false"
@confirm="submit"
>
<view class="popup-line">
<text class="meta-pair__label">预估兑换BMT:</text>
<text class="meta-pair__value asset-number-font">{{ estimateBmt }}</text>
</view>
<view class="popup-line">
<text class="meta-pair__label">消耗可用积分:</text>
<text class="meta-pair__value asset-number-font">{{ formatAmount(pointsValue, 0) }}</text>
</view>
<view class="popup-line">
<text class="meta-pair__label">消耗算力:</text>
<text class="meta-pair__value asset-number-font">{{ powerCost }}</text>
</view>
<view class="popup-line">
<text class="meta-pair__label">消耗BMTR:</text>
<text class="meta-pair__value asset-number-font">{{ bmtrCost }}</text>
</view>
</asset-confirm-popup>
</view>
</template>
<script>
import AssetConfirmPopup from "../../components/asset-confirm-popup.vue";
import AssetPageShell from "../../components/asset-page-shell.vue";
import DecimalInput from "../../components/DecimalInput/DecimalInput.vue";
import {
fetchBmtExchangeDetail,
submitAssetBmtExchange,
} from "../../api/assets";
export default {
components: {
AssetConfirmPopup,
AssetPageShell,
DecimalInput,
},
data() {
return {
detail: {
ticker: {},
balances: {},
exchangeConfig: {},
tips: [],
},
hasShown: false,
form: {
points: "",
},
confirmVisible: false,
submitting: false,
};
},
onLoad() {
this.loadPage(true);
},
onShow() {
if (this.hasShown) {
this.loadPage();
return;
}
this.hasShown = true;
},
computed: {
pointsValue() {
return Number(this.form.points || 0);
},
configPointNumber() {
return Number(this.detail.exchangeConfig.point || 0);
},
configPowerNumber() {
return Number(this.detail.exchangeConfig.cPower || 0);
},
configBmtrNumber() {
return Number(this.detail.exchangeConfig.bmtr || 0);
},
redeemNumNumber() {
return Number(this.detail.exchangeConfig.redeemNum || 0);
},
estimateBmt() {
if (!this.pointsValue) {
return "0";
}
const price = this.priceNumber;
if (!price) {
return "0";
}
return this.formatAmount(this.pointsValue / price, 2);
},
powerCost() {
if (!this.pointsValue || !this.configPointNumber) {
return "0.00";
}
return (
(this.pointsValue / this.configPointNumber) * this.configPowerNumber
).toFixed(2);
},
bmtrCostNumber() {
if (!this.pointsValue || !this.configPointNumber) {
return 0;
}
return (
(this.pointsValue / this.configPointNumber) * this.configBmtrNumber
);
},
bmtrCost() {
return this.formatAmount(this.bmtrCostNumber, 4);
},
allEstimateBmt() {
const points = Number(this.detail.balances.points || 0);
const price = this.priceNumber;
if (!price) {
return "0.00";
}
const truncatedBmt = Math.floor((points / price) * 100) / 100;
return this.formatAmount(truncatedBmt, 2);
},
priceNumber() {
return Number(
this.detail.ticker.close || 0,
);
},
displayBmt() {
return this.detail.balances.bmt;
},
displayVoucher() {
return this.detail.balances.power;
},
displayCoupon() {
return this.detail.balances.points;
},
displayBmtr() {
return this.formatAmount(this.detail.balances.bmtr, 4);
},
displayPrice() {
return this.priceNumber ? this.priceNumber : "0.00";
},
normalizedTips() {
if (this.detail.tips && this.detail.tips.length) {
return this.detail.tips;
}
return [
"BMT=积分-BMT实时价格;",
`消耗算力=输入可用积分数÷${this.configPointNumber}×${this.configPowerNumber};`,
`消耗BMTR=输入可用积分数÷${this.configPointNumber}×${this.configBmtrNumber};`,
`只能兑换${this.redeemNumNumber}的整数倍,小于${this.redeemNumNumber}积分不可兑换;`,
"凌晨0点-凌晨1点积分系统维护不可兑换;",
];
},
},
methods: {
pageIcon(type) {
const iconMap = {
power: "https://imgs.agrimedia.cn/bm-bmt/s.png",
bmt: "https://imgs.agrimedia.cn/bm-bmt/b-w.png",
transfer: "https://imgs.agrimedia.cn/bm-bmt/z.png",
withdraw: "https://imgs.agrimedia.cn/bm-bmt/t.png",
voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-icon-g%20%281%29.png",
points: "https://imgs.agrimedia.cn/bm-bmt/j.png",
coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-icon-o.png",
bmtr: "https://imgs.agrimedia.cn/webimg/202607171808386371853.svg",
};
return iconMap[type] || "";
},
formatAmount(value, digits) {
const number = Number(value || 0);
if (!Number.isFinite(number)) {
return digits > 0 ? Number(0).toFixed(digits) : "0";
}
if (digits > 0) {
const fixed = number.toFixed(digits);
const parts = fixed.split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
}
return Math.floor(number)
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
},
async loadPage(showLoading) {
try {
this.detail = await fetchBmtExchangeDetail(
showLoading
? {
showLoading: true,
loadingText: "加载中",
}
: null,
);
} catch (error) {
uni.showToast({
title: error.message || "页面加载失败",
icon: "none",
});
}
},
openConfirm() {
if (this.submitting || this.confirmVisible) {
return;
}
this.confirmVisible = true;
},
async submit() {
if (this.submitting) {
return;
}
if (!this.pointsValue) {
uni.showToast({
title: "请输入积分数量",
icon: "none",
});
return;
}
if (!this.configPointNumber) {
uni.showToast({
title: "兑换配置加载失败,请稍后重试",
icon: "none",
});
return;
}
if (this.redeemNumNumber && this.pointsValue < this.redeemNumNumber) {
uni.showToast({
title: `小于${this.redeemNumNumber}积分不可兑换`,
icon: "none",
});
return;
}
if (this.redeemNumNumber && this.pointsValue % this.redeemNumNumber !== 0) {
uni.showToast({
title: `只能兑换${this.redeemNumNumber}的整数倍`,
icon: "none",
});
return;
}
if (this.pointsValue > Number(this.detail.balances.points || 0)) {
uni.showToast({
title: "可用积分不足",
icon: "none",
});
return;
}
if (Number(this.powerCost) > Number(this.detail.balances.power || 0)) {
uni.showToast({
title: "可用算力不足",
icon: "none",
});
return;
}
if (this.bmtrCostNumber > Number(this.detail.balances.bmtr || 0)) {
uni.showToast({
title: "BMTR数量不足",
icon: "none",
});
return;
}
this.submitting = true;
try {
await submitAssetBmtExchange({
amount: this.pointsValue,
}, {
showLoading: true,
loadingText: "兑换中",
});
this.confirmVisible = false;
this.form.points = "";
uni.showToast({
title: "兑换成功",
icon: "none",
});
this.loadPage();
} catch (error) {
uni.showToast({
title: error.message || "兑换失败",
icon: "none",
});
} finally {
this.submitting = false;
}
},
openLedger() {
uni.navigateTo({
url: "/pages/assets/ledger?type=bmt",
});
},
},
};
</script>
<style lang="scss" scoped>
@import "../../styles/tokens.scss";
@import "../../styles/common.scss";
.bmt-page {
min-height: 100vh;
background: #191e32;
}
.bmt-scroll {
padding: 8rpx 20rpx calc(env(safe-area-inset-bottom) + 36rpx);
}
.panel-card,
.hero-card {
border-radius: 12rpx;
background: #20263e;
}
.hero-card {
position: relative;
min-height: 240rpx;
padding: 24rpx 28rpx;
overflow: hidden;
background: #20263e url("https://imgs.agrimedia.cn/bm-bmt/bmt2-header.png")
no-repeat center top / 100% 240rpx;
}
.hero-card__content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
}
.hero-card__coin-row {
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, #18c5ff 0%, #1496ff 100%);
}
.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 {
margin-top: 34rpx;
font-size: 28rpx;
color: #9ba7ce;
}
.hero-card__value {
margin-top: 8rpx;
font-size: 48rpx;
font-weight: 800;
color: #ffffff;
line-height: 1.1;
}
.panel-card {
margin-top: 18rpx;
padding: 0 26rpx;
}
.info-card {
padding-top: 2rpx;
padding-bottom: 14rpx;
}
.info-row {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 96rpx;
border-bottom: 1rpx solid #4e5a82;
}
.info-row--last {
border-bottom: 1rpx solid #4e5a82;
}
.info-row__left {
display: flex;
align-items: center;
min-width: 0;
}
.info-row__icon {
width: 34rpx;
height: 34rpx;
flex-shrink: 0;
}
.info-row__label {
margin-left: 20rpx;
font-size: 28rpx;
font-weight: 400;
color: #eef2ff;
}
.info-row__value {
font-size: 32rpx;
font-weight: 700;
color: #ffffff;
}
.info-row__price {
display: flex;
align-items: baseline;
}
.info-row__unit {
margin-left: 10rpx;
font-size: 22rpx;
color: rgba(184, 193, 218, 0.88);
}
.info-card__estimate {
display: flex;
justify-content: flex-end;
align-items: baseline;
padding-top: 18rpx;
font-size: 28rpx;
color: #1ad296;
}
.info-card__estimate-value {
margin: 0 10rpx;
font-size: 32rpx;
font-weight: 800;
color: #2fe2a4;
}
.form-card {
padding-top: 18rpx;
padding-bottom: 20rpx;
}
.form-card__title {
display: flex;
align-items: center;
}
.form-card__title-icon {
width: 34rpx;
height: 34rpx;
flex-shrink: 0;
}
.form-card__title-text {
margin-left: 14rpx;
font-size: 28rpx;
font-weight: 700;
color: #ffffff;
}
.form-input {
display: flex;
align-items: center;
margin-top: 20rpx;
padding: 0 20rpx;
border-radius: 10rpx;
background: #191e32;
}
.form-input__icon {
width: 34rpx;
height: 34rpx;
margin-right: 18rpx;
flex-shrink: 0;
}
.form-input__field {
flex: 1;
height: 98rpx;
font-size: 30rpx;
font-weight: 700;
color: #ffffff;
}
.form-input__suffix {
margin-left: 16rpx;
font-size: 22rpx;
font-weight: 600;
color: #ffffff;
}
.preview-card {
margin-top: 18rpx;
padding: 12rpx 26rpx;
border-radius: 8rpx;
background: rgba(26, 210, 150, 0.1);
}
.preview-card__row {
display: flex;
align-items: center;
justify-content: space-between;
}
.preview-card__row + .preview-card__row {
margin-top: 8rpx;
}
.preview-card__left {
display: flex;
align-items: center;
}
.preview-card__icon {
width: 30rpx;
height: 30rpx;
margin-right: 10rpx;
flex-shrink: 0;
}
.preview-card__label {
font-size: 26rpx;
color: rgba(26, 210, 150, 1);
}
.preview-card__label--light {
color: #ffffff;
}
.preview-card__value {
font-size: 26rpx;
font-weight: 800;
color: #1ad296;
}
.preview-card__value--light {
color: #ffffff;
}
.tips-block {
margin-top: 24rpx;
}
.tips-block__title {
display: block;
font-size: 24rpx;
color: rgba(190, 197, 219, 0.92);
}
.tips-block__item {
display: flex;
margin-top: 10rpx;
}
.tips-block__index,
.tips-block__text {
font-size: 24rpx;
color: #929dbf;
line-height: 44rpx;
color: rgba(166, 175, 204, 0.92);
}
.tips-block__index {
margin-right: 6rpx;
}
.tips-block__text {
flex: 1;
}
.action-wrap {
position: relative;
z-index: 30;
padding: 36rpx 22rpx 0;
}
.action-button {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 88rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, #20b6f5 0%, #1ca5e3 100%);
box-shadow: 0 16rpx 30rpx rgba(20, 119, 214, 0.16);
font-size: 30rpx;
font-weight: 700;
color: #ffffff;
cursor: pointer;
touch-action: manipulation;
}
.action-link {
margin-top: 26rpx;
text-align: center;
font-weight: 500;
font-size: 28rpx;
color: #1fb4ff;
}
.popup-line {
margin-top: 14rpx;
}
</style>