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

899 lines
21 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="BMT闪兑" />
<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/b-w.png"
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 class="hero-card__price">
<text class="hero-card__price-label">BMT实时价格</text>
<view class="hero-card__price-row">
<text class="hero-card__price-value asset-number-font">{{ displayPrice }}</text>
<text class="hero-card__price-unit">BMT/CNY</text>
</view>
</view>
</view>
<view class="panel-card info-card">
<view class="info-row">
<view class="info-row__left">
<image
class="info-row__icon"
:src="pageIcon('yue')"
mode="aspectFit"
></image>
<text class="info-row__label">我的余额</text>
</view>
<text class="info-row__value asset-number-font">{{ displayBalance }}</text>
</view>
<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('bmtr')"
mode="aspectFit"
></image>
<text class="info-row__label">BMTR数量</text>
</view>
<text class="info-row__value asset-number-font">{{ displayBmtr }}</text>
</view>
<view v-if="!isBmtrMode" 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/shandui.png"
mode="aspectFit"
></image>
<text class="form-card__title-text">BMT闪兑</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"
@wheel.prevent="preventAmountWheelChange"
/>
<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">预估闪兑BMT</text>
</view>
<text class="preview-card__value asset-number-font">{{ estimateBmt }}</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 {
fetchBmtFlashExchangeDetail,
submitAssetBmtFlashExchange,
} from "../../api/assets";
export default {
components: {
AssetConfirmPopup,
AssetPageShell,
},
data() {
return {
modeList: [
{ label: "抵用券闪兑", value: "voucher" },
{ label: "消费券闪兑", value: "coupon" },
{ label: "余额闪兑", value: "balance" },
{ label: "BMTR闪兑", value: "bmtr" },
],
detail: {
ticker: {},
balances: {},
bmtrPercent: "",
bmtrRatio: "",
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() {
if (this.form.mode === "bmtr") {
return "BMTR";
}
if (this.form.mode === "coupon") {
return "消费券";
}
if (this.form.mode === "balance") {
return "余额";
}
return "抵用券";
},
currentModeBalance() {
if (this.form.mode === "bmtr") {
return Number(this.detail.balances.bmtr || 0);
}
if (this.form.mode === "coupon") {
return Number(this.detail.balances.coupon || 0);
}
if (this.form.mode === "balance") {
return Number(this.detail.balances.brokerage_price || 0);
}
return Number(this.detail.balances.voucher || 0);
},
priceNumber() {
return Number(this.detail.ticker.close || this.detail.ticker.cnyPrice || 0);
},
isBmtrMode() {
return this.form.mode === "bmtr";
},
bmtrPercentNumber() {
const percent = Number(this.detail.bmtrPercent);
return Number.isFinite(percent) && percent > 0 ? percent : 0;
},
hasBmtrPercent() {
return this.bmtrPercentNumber > 0;
},
bmtrRatioNumber() {
const ratio = Number(this.detail.bmtrRatio);
return Number.isFinite(ratio) && ratio > 0 ? ratio : 0;
},
hasBmtrRatio() {
return this.bmtrRatioNumber > 0;
},
bmtrExchangeRate() {
return Math.max(0, (100 - this.bmtrPercentNumber) / 100);
},
displayBmtrRatio() {
if (!this.hasBmtrRatio) {
return "--";
}
return String(this.detail.bmtrRatio);
},
displayBmtrPercent() {
if (!this.hasBmtrPercent) {
return "--";
}
const value = this.bmtrPercentNumber;
return Number.isInteger(value) ? String(value) : String(value);
},
estimateBmt() {
if (!this.amountValue) {
return "0";
}
if (this.isBmtrMode) {
if (!this.hasBmtrPercent || !this.hasBmtrRatio) {
return "--";
}
return this.formatAmount(
this.amountValue * this.bmtrRatioNumber * this.bmtrExchangeRate,
2,
);
}
if (!this.priceNumber) {
return "0";
}
return this.formatAmount(this.amountValue / this.priceNumber, 2);
},
allEstimateBmt() {
if (this.isBmtrMode) {
return "0";
}
if (!this.priceNumber) {
return "0";
}
const totalAmount =
Number(this.detail.balances.voucher || 0) +
Number(this.detail.balances.coupon || 0) +
Number(this.detail.balances.brokerage_price || 0);
return this.formatAmount(totalAmount / this.priceNumber, 2);
},
displayBmt() {
return this.formatAmount(this.detail.balances.bmt, 2);
},
displayVoucher() {
return this.formatAmount(this.detail.balances.voucher, 2);
},
displayBalance() {
return this.formatAmount(this.detail.balances.brokerage_price, 2);
},
displayCoupon() {
return this.formatAmount(this.detail.balances.coupon, 2);
},
displayBmtr() {
return this.formatAmount(this.detail.balances.bmtr, 4);
},
displayPrice() {
return this.priceNumber ? this.priceNumber : "0.00";
},
displayInputAmount() {
return this.formatAmount(this.amountValue, this.isBmtrMode ? 2 : 0);
},
normalizedTips() {
const tips =
this.detail.tips && this.detail.tips.length
? this.detail.tips
: [
"BMT闪兑 = 输入数量 ÷ BMT实时价格",
"BMT闪兑时所需的抵用券、消费券、余额。必须大于10的倍数。",
"闪兑成功后数量将直接结算到可用BMT。",
];
if (!this.isBmtrMode) {
return tips;
}
return tips.concat([
"BMTR兑换BMT1:" +
this.displayBmtrRatio +
"兑换,按兑换数量扣除" +
this.displayBmtrPercent +
"%手续费兑换最小数量必须是10的倍数",
]);
},
},
methods: {
pageIcon(type) {
const iconMap = {
yue: "https://imgs.agrimedia.cn/bm-bmt/qianbao-icon.png",
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",
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, ",");
},
preventAmountWheelChange(event) {
if (event && typeof event.preventDefault === "function") {
event.preventDefault();
}
const target = event && event.target;
if (target && typeof target.blur === "function") {
target.blur();
}
},
async loadPage(showLoading) {
try {
this.detail = await fetchBmtFlashExchangeDetail(
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.isBmtrMode && this.amountValue < 0.01) {
this.openResultDialog(
"error",
"闪兑失败!",
"BMTR闪兑最小数量为0.01。",
);
return;
}
if (!this.isBmtrMode && this.amountValue < 10) {
this.openResultDialog(
"error",
"闪兑失败!",
"BMT闪兑时所需的抵用券、消费券、余额。必须大于10的倍数。",
);
return;
}
if (this.amountValue > this.currentModeBalance) {
this.openResultDialog(
"error",
"闪兑失败!",
"当前可用" + this.currentModeLabel + "不足",
);
return;
}
this.openDialog({
title: "确认闪兑",
message:
"确认使用" +
this.displayInputAmount +
this.currentModeLabel +
"闪兑BMT?",
description: "预估可得 " + this.estimateBmt + " BMT",
confirmText: "确认",
cancelText: "取消",
showCancel: true,
status: "default",
action: "submit",
});
},
async submit() {
if (this.submitting) {
return;
}
this.submitting = true;
try {
await submitAssetBmtFlashExchange(
{
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=bmt-flash",
});
},
},
};
</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/bmt2-header.png")
no-repeat center top / 100% 240rpx;
}
.hero-card__content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
max-width: 360rpx;
}
.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;
line-height: 1.1;
color: #ffffff;
word-break: break-all;
}
.hero-card__price {
position: absolute;
right: 36rpx;
top: 104rpx;
z-index: 2;
min-width: 220rpx;
padding: 18rpx 22rpx;
border-radius: 10rpx;
background: rgba(28, 35, 61, 0.82);
box-shadow: 0 14rpx 30rpx rgba(6, 10, 24, 0.24);
}
.hero-card__price-label {
display: block;
font-size: 26rpx;
color: rgba(225, 232, 255, 0.86);
}
.hero-card__price-row {
display: flex;
align-items: baseline;
margin-top: 12rpx;
}
.hero-card__price-value {
font-size: 34rpx;
font-weight: 800;
color: #ffffff;
}
.hero-card__price-unit {
margin-left: 10rpx;
font-size: 20rpx;
color: rgba(225, 232, 255, 0.72);
}
.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-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: 100%;
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: 24rpx;
font-weight: 600;
white-space: nowrap;
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__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;
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-size: 30rpx;
font-weight: 700;
color: #ffffff;
border-radius: 46rpx;
background: linear-gradient(135deg, #20b6f5 0%, #1ca5e3 100%);
box-shadow: 0 16rpx 30rpx rgba(20, 119, 214, 0.16);
}
.action-link {
margin-top: 26rpx;
text-align: center;
font-weight: 500;
font-size: 28rpx;
color: #1fb4ff;
}
</style>