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

756 lines
17 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 power-page">
<asset-page-shell title="算力兑换" />
<view class="asset-scroll power-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/s-w.png"
mode="aspectFit"
></image>
</view>
<text class="hero-card__coin-text">算力</text>
</view>
<text class="hero-card__label">已有算力:</text>
<text class="hero-card__value asset-number-font">{{ displayPower }}</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 info-row--last">
<view class="info-row__left">
<image
class="info-row__icon"
:src="pageIcon('bmt')"
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">RMB/BMT</text>
</view>
</view>
<view class="info-card__estimate">
<text>全部兑换预估可得</text>
<text class="info-card__estimate-value asset-number-font">{{ allEstimatePower }}</text>
<text>算力</text>
</view>
</view>
<view class="panel-card form-card">
<view class="form-card__title">
<image
class="form-card__title-icon"
:src="pageIcon('power')"
mode="aspectFit"
></image>
<text class="form-card__title-text">算力兑换</text>
</view>
<view class="mode-tabs">
<view
v-for="item in modeList"
:key="item.value"
class="mode-tabs__item"
:class="{ 'is-active': form.mode === item.value }"
@click="form.mode = item.value"
>
{{ item.label }}
</view>
</view>
<view class="form-input">
<input
v-model="form.amount"
class="form-input__field asset-number-font"
type="number"
:placeholder="'请输入' + currentModeLabel + '数量'"
placeholder-class="form-input__placeholder"
/>
<text class="form-input__suffix">{{ currentModeLabel }}</text>
</view>
<view class="preview-card">
<view class="preview-card__row">
<view class="preview-card__left">
<text class="preview-card__label">预估兑换算力</text>
</view>
<text class="preview-card__value asset-number-font">{{ estimatePower }}</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" @click="openConfirmDialog">确认兑换</view>
<view class="action-link" @click="openLedger">兑换记录</view>
</view>
</view>
<asset-confirm-popup
:visible="dialog.visible"
:title="dialog.title"
:message="dialog.message"
:description="dialog.description"
:confirm-text="dialog.confirmText"
:cancel-text="dialog.cancelText"
:show-cancel="dialog.showCancel"
:status="dialog.status"
@cancel="closeDialog"
@confirm="handleDialogConfirm"
/>
</view>
</template>
<script>
import AssetConfirmPopup from "../../components/asset-confirm-popup.vue";
import AssetPageShell from "../../components/asset-page-shell.vue";
import {
fetchPowerExchangeDetail,
submitAssetPowerExchange,
} from "../../api/assets";
export default {
components: {
AssetConfirmPopup,
AssetPageShell,
},
data() {
return {
modeList: [
{ label: "抵用券兑换", value: "voucher" },
{ label: "消费券兑换", value: "coupon" },
],
detail: {
ticker: {},
balances: {},
exchangeMultiplier: 1,
tips: [],
},
hasShown: false,
form: {
mode: "voucher",
amount: "",
},
dialog: {
visible: false,
title: "确认兑换",
message: "",
description: "",
confirmText: "确认",
cancelText: "取消",
showCancel: true,
status: "default",
action: "",
},
submitting: false,
};
},
onLoad() {
this.loadPage(true);
},
onShow() {
if (this.hasShown) {
this.loadPage();
return;
}
this.hasShown = true;
},
computed: {
amountValue() {
return Number(this.form.amount || 0);
},
currentModeLabel() {
return this.form.mode === "coupon" ? "消费券" : "抵用券";
},
currentModeBalance() {
if (this.form.mode === "coupon") {
return Number(this.detail.balances.coupon || 0);
}
return Number(this.detail.balances.voucher || 0);
},
priceNumber() {
return Number(
this.detail.ticker.close || this.detail.ticker.cnyPrice || 0,
);
},
exchangeMultiplierNumber() {
const value = Number(this.detail.exchangeMultiplier || 1);
return value > 0 ? value : 1;
},
estimatePower() {
if (!this.amountValue || !this.priceNumber) {
return "0";
}
return this.formatAmount(
this.amountValue * this.priceNumber * this.exchangeMultiplierNumber,
2,
);
},
allEstimatePower() {
if (!this.priceNumber) {
return "0";
}
const totalTickets =
Number(this.detail.balances.voucher || 0) +
Number(this.detail.balances.coupon || 0);
return this.formatAmount(
totalTickets * this.priceNumber * this.exchangeMultiplierNumber,
2,
);
},
displayPower() {
return this.formatAmount(this.detail.balances.power, 2);
},
displayVoucher() {
return this.formatAmount(this.detail.balances.voucher, 2);
},
displayCoupon() {
return this.formatAmount(this.detail.balances.coupon, 2);
},
displayPrice() {
return this.priceNumber ? this.priceNumber : "0.00";
},
normalizedTips() {
if (this.detail.tips && this.detail.tips.length) {
return this.detail.tips;
}
return [
"算力 = 抵用券或消费券 * BMT实时价格 * 兑换倍率;",
"抵用券和消费券总数小于100券的不可兑换",
"算力用于兑换BMT使用。",
];
},
},
methods: {
pageIcon(type) {
const iconMap = {
power: "https://imgs.agrimedia.cn/bm-bmt/s.png",
bmt: "https://imgs.agrimedia.cn/bm-bmt/b.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",
};
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 fetchPowerExchangeDetail(
showLoading
? {
showLoading: true,
loadingText: "加载中",
}
: null,
);
} catch (error) {
this.openResultDialog(
"error",
"兑换失败!",
error.message || "页面加载失败",
);
}
},
openDialog(payload) {
this.dialog = {
...this.dialog,
...payload,
visible: true,
};
},
closeDialog() {
this.dialog.visible = false;
this.dialog.action = "";
},
openResultDialog(status, message, description) {
this.openDialog({
title: "确认兑换",
message: message,
description: description || "",
confirmText: "确认",
cancelText: "取消",
showCancel: false,
status: status,
action: "close",
});
},
openConfirmDialog() {
if (!this.amountValue) {
this.openResultDialog("error", "兑换失败!", "请输入兑换券数");
return;
}
if (this.amountValue < 100) {
this.openResultDialog(
"error",
"兑换失败!",
"抵用券和消费券总数小于100券的不可兑换",
);
return;
}
if (this.amountValue > this.currentModeBalance) {
this.openResultDialog(
"error",
"兑换失败!",
"当前可用" + this.currentModeLabel + "不足",
);
return;
}
this.openDialog({
title: "确认兑换",
message:
"确认使用" +
this.formatAmount(this.amountValue, 0) +
this.currentModeLabel +
"兑换算力?",
description: "",
confirmText: "确认",
cancelText: "取消",
showCancel: true,
status: "default",
action: "submit",
});
},
async submit() {
if (this.submitting) {
return;
}
this.submitting = true;
try {
await submitAssetPowerExchange({
mode: this.form.mode,
amount: this.amountValue,
}, {
showLoading: true,
loadingText: "兑换中",
});
this.form.amount = "";
await this.loadPage();
this.openResultDialog("success", "兑换成功!", "");
} catch (error) {
this.openResultDialog(
"error",
"兑换失败!",
error.message || "系统繁忙,请稍后再试",
);
} finally {
this.submitting = false;
}
},
handleDialogConfirm() {
if (this.dialog.action === "submit") {
this.closeDialog();
this.submit();
return;
}
this.closeDialog();
},
openLedger() {
uni.navigateTo({
url: "/pages/assets/ledger?type=power",
});
},
},
};
</script>
<style lang="scss" scoped>
@import "../../styles/tokens.scss";
@import "../../styles/common.scss";
.power-page {
min-height: 100vh;
background: #191e32;
}
.power-scroll {
padding: 8rpx 20rpx calc(env(safe-area-inset-bottom) + 36rpx);
}
.panel-card,
.hero-card {
border-radius: 12rpx;
background: #20263e;
box-shadow: 0 12rpx 24rpx rgba(8, 13, 30, 0.12);
}
.hero-card {
position: relative;
min-height: 240rpx;
padding: 24rpx 28rpx;
overflow: hidden;
background: #20263e url("https://imgs.agrimedia.cn/bm-bmt/suanli-bgs.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, #33ddb0 0%, #24c38f 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: 32rpx;
font-weight: 800;
line-height: 1.1;
font-family: var(--asset-number-font-family);
color: #ffffff;
}
.panel-card {
margin-top: 18rpx;
padding: 0 16rpx;
}
.info-card {
padding-top: 2rpx;
padding-bottom: 14rpx;
}
.info-row {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 104rpx;
border-bottom: 1px solid rgba(136, 148, 193, 0.34);
}
.info-row--last {
border-bottom: 1px solid rgba(136, 148, 193, 0.34);
}
.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;
align-items: baseline;
justify-content: flex-end;
padding-top: 18rpx;
font-size: 28rpx;
color: #1ad296;
}
.info-card__estimate-value {
margin: 0 10rpx;
font-size: 32rpx;
font-weight: 800;
color: #1ad296;
}
.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;
}
.mode-tabs {
display: flex;
width: 400rpx;
margin: 26rpx auto 0;
padding: 4rpx;
border-radius: 999rpx;
background: rgba(102, 115, 160, 0.28);
box-shadow: inset 0 0 0 1px rgba(165, 177, 216, 0.16);
}
.mode-tabs__item {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
height: 60rpx;
border-radius: 999rpx;
font-size: 28rpx;
font-weight: 600;
color: rgba(210, 219, 242, 0.72);
}
.mode-tabs__item.is-active {
background: #02abf1;
color: #ffffff;
}
.form-input {
display: flex;
align-items: center;
margin-top: 34rpx;
padding: 0 20rpx;
border-radius: 10rpx;
background: #191e32;
}
.form-input__field {
flex: 1;
height: 98rpx;
font-size: 30rpx;
font-weight: 700;
color: #ffffff;
}
.form-input__placeholder {
color: rgba(177, 187, 214, 0.42);
}
.form-input__suffix {
margin-left: 16rpx;
font-size: 28rpx;
font-weight: 600;
color: #ffffff;
}
.preview-card {
margin-top: 18rpx;
padding: 14rpx 18rpx;
border-radius: 8rpx;
background: linear-gradient(180deg, #21455a 0%, #1f4358 100%);
}
.preview-card__row {
display: flex;
align-items: center;
justify-content: space-between;
}
.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(229, 252, 255, 0.94);
}
.preview-card__value {
font-size: 28rpx;
font-weight: 800;
color: #1df0b8;
}
.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-weight: 400;
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 {
padding: 36rpx 22rpx 0;
}
.action-button {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 90rpx;
font-weight: 500;
font-size: 30rpx;
color: #ffffff;
border-radius: 46rpx;
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;
}
.action-link {
margin-top: 26rpx;
text-align: center;
font-weight: 500;
font-size: 28rpx;
color: #1fb4ff;
}
</style>