bm-bmt/pages/assets/transfer.vue

805 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 transfer-page">
<asset-page-shell title="转赠中心" />
<view class="asset-scroll transfer-scroll">
<view class="mode-tabs">
<view
v-for="tab in tabs"
:key="tab.value"
class="mode-tabs__item"
:class="{ 'is-active': activeTab === tab.value }"
@click="switchTab(tab.value)"
>
{{ tab.label }}
</view>
</view>
<view class="panel-card transfer-card">
<view class="section-head">
<image
class="section-head__icon"
:src="pageIcon(activeTab === 'power' ? 'power' : 'points')"
mode="aspectFit"
></image>
<text class="section-head__title">{{ currentTabLabel }}转赠</text>
<text class="section-head__meta asset-number-font"
>(可用{{ currentBalanceDisplay }}</text
>
</view>
<view class="input-bar">
<input
v-model="form.amount"
class="input-bar__field asset-number-font"
type="number"
:placeholder="amountPlaceholder"
placeholder-class="input-bar__placeholder"
/>
<text class="input-bar__action" @click="fillAll">全部赠送</text>
</view>
<view class="tips-list">
<view
v-for="(tip, index) in currentTips"
:key="tip"
class="tips-list__item"
>
<text class="tips-list__index asset-number-font">{{ index + 1 }}.</text>
<text class="tips-list__text asset-number-font">{{ tip }}</text>
</view>
</view>
</view>
<view class="transfer-split">
<image
class="transfer-split__icon"
src="https://imgs.agrimedia.cn/bm-bmt/z.png"
mode="aspectFit"
></image>
</view>
<view class="panel-card recipient-card">
<view class="section-head">
<view class="recipient-card__title-icon">
<view class="recipient-card__title-head"></view>
<view class="recipient-card__title-body"></view>
</view>
<text class="section-head__title">被赠送人</text>
</view>
<view class="input-bar recipient-search">
<input
v-model="searchId"
class="input-bar__field"
type="number"
placeholder="请输入被赠人ID"
placeholder-class="input-bar__placeholder"
/>
<text class="input-bar__action" @click="queryTarget">查询</text>
</view>
<view
v-if="currentTarget.id"
class="target-card"
@click="selectCurrent"
>
<view class="target-card__top">
<text class="target-card__id asset-number-font">ID: {{ currentTarget.id }}</text>
<view class="target-card__select">
<text class="target-card__select-text">选择</text>
<view class="target-card__select-dot">✓</view>
</view>
</view>
<view class="target-card__line"></view>
<view class="target-card__bottom">
<view class="target-card__profile">
<view
class="target-card__avatar"
:class="{
'target-card__avatar--image': !!currentTarget.avatar,
}"
>
<image
v-if="currentTarget.avatar"
class="target-card__avatar-image"
:src="currentTarget.avatar"
mode="aspectFill"
></image>
<text v-else class="target-card__avatar-text">{{
targetInitial(currentTarget.nickname)
}}</text>
</view>
<text class="target-card__name">{{
currentTarget.nickname
}}</text>
</view>
<text class="target-card__phone">{{ currentTarget.phone }}</text>
</view>
</view>
</view>
<view class="action-wrap">
<view class="action-button" @click="openConfirm">确认转赠</view>
<view class="action-link" @click="openLedger">转赠记录</view>
</view>
</view>
<asset-confirm-popup
:visible="confirmVisible"
title="确认转赠"
message="确认转赠以下资产给好友?"
confirm-text="确认"
cancel-text="取消"
@cancel="confirmVisible = false"
@confirm="submit"
>
<view class="confirm-detail">
<text class="confirm-detail__summary asset-number-font">
转赠{{ currentTabLabel }}{{ amountDisplay }}
<text class="confirm-detail__summary-sub asset-number-font"
>(实际到账{{ receivedDisplay }}</text
>
</text>
<view class="confirm-detail__target">
<text class="confirm-detail__id">ID: {{ currentTarget.id }}</text>
<view class="confirm-detail__line"></view>
<view class="confirm-detail__profile">
<view class="target-card__profile">
<view
class="target-card__avatar"
:class="{
'target-card__avatar--image': !!currentTarget.avatar,
}"
>
<image
v-if="currentTarget.avatar"
class="target-card__avatar-image"
:src="currentTarget.avatar"
mode="aspectFill"
></image>
<text v-else class="target-card__avatar-text">{{
targetInitial(currentTarget.nickname)
}}</text>
</view>
<text class="target-card__name">{{
currentTarget.nickname
}}</text>
</view>
<text class="target-card__phone">{{ currentTarget.phone }}</text>
</view>
</view>
<text class="confirm-detail__desc">
请仔细校对被赠送账号信息转赠成功后无法追回
</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 {
fetchTransferDetail,
searchTransferUser,
submitAssetTransfer,
} from "../../api/assets";
export default {
components: {
AssetConfirmPopup,
AssetPageShell,
},
data() {
return {
tabs: [
{
label: "积分转赠",
value: "points",
},
{
label: "算力转赠",
value: "power",
},
],
activeTab: "points",
targets: [],
selectedIndex: 0,
balances: {
points: "0",
power: "0",
},
feePercent: 10,
tips: {
points: [],
power: [],
},
form: {
amount: "",
},
searchId: "",
confirmVisible: false,
submitting: false,
};
},
onLoad(options) {
if (options && options.mode === "power") {
this.activeTab = "power";
}
this.loadPage(true);
},
computed: {
currentTabLabel() {
return this.activeTab === "power" ? "算力" : "可用积分";
},
currentBalance() {
return Number(this.balances[this.activeTab] || 0);
},
currentBalanceDisplay() {
return this.formatAmount(this.currentBalance, 0);
},
currentTips() {
const currentTips = this.tips[this.activeTab] || [];
if (currentTips.length) {
return currentTips;
}
if (this.activeTab === "power") {
return [
"只能转赠1的整数倍",
"转赠系统会扣除" + this.feePercent + "%的手续费",
];
}
return [
"只能转赠100的整数倍",
"凌晨0点-凌晨01点系统维护不可赠送",
"转赠系统会扣除" + this.feePercent + "%的手续费",
];
},
currentTarget() {
return this.targets[this.selectedIndex] || {};
},
amountValue() {
return Number(this.form.amount || 0);
},
amountDisplay() {
return this.formatAmount(this.amountValue, 0);
},
receivedDisplay() {
const received = this.amountValue
? this.amountValue * (1 - this.feePercent / 100)
: 0;
return this.formatAmount(received, 0);
},
amountPlaceholder() {
return this.activeTab === "power"
? "请输入1的整数倍"
: "请输入100的整数倍";
},
},
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.png",
points: "https://imgs.agrimedia.cn/bm-bmt/j.png",
coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-icon.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, ",");
},
targetInitial(name) {
return String(name || "友").slice(0, 1);
},
async loadPage(showLoading) {
try {
const result = await fetchTransferDetail(
showLoading
? {
showLoading: true,
loadingText: "加载中",
}
: null,
);
this.balances = result.balances || this.balances;
this.feePercent = Number(result.feePercent || 10);
this.tips = result.tips || this.tips;
} catch (error) {
uni.showToast({
title: error.message || "转赠页加载失败",
icon: "none",
});
}
},
switchTab(value) {
this.activeTab = value;
this.form.amount = "";
},
fillAll() {
this.form.amount = String(this.currentBalance || 0);
},
async queryTarget() {
const keyword = String(this.searchId || "").trim();
if (!keyword) {
uni.showToast({
title: "请输入被赠人ID",
icon: "none",
});
return;
}
try {
const target = await searchTransferUser(keyword);
this.targets = [target];
this.selectedIndex = 0;
this.searchId = target.id;
} catch (error) {
uni.showToast({
title: error.message || "未查询到好友",
icon: "none",
});
}
},
selectCurrent() {
if (this.currentTarget && this.currentTarget.id) {
this.searchId = this.currentTarget.id;
}
},
openConfirm() {
if (!this.currentTarget.id) {
uni.showToast({
title: "请先查询被赠送人",
icon: "none",
});
return;
}
if (!this.amountValue) {
uni.showToast({
title: "请输入转赠数量",
icon: "none",
});
return;
}
if (this.amountValue > this.currentBalance) {
uni.showToast({
title: "可用数量不足",
icon: "none",
});
return;
}
if (this.activeTab === "points" && this.amountValue % 100 !== 0) {
uni.showToast({
title: "积分只能转赠100的整数倍",
icon: "none",
});
return;
}
if (this.activeTab === "power" && !Number.isInteger(this.amountValue)) {
uni.showToast({
title: "算力只能转赠1的整数倍",
icon: "none",
});
return;
}
this.confirmVisible = true;
},
async submit() {
if (this.submitting) {
return;
}
this.submitting = true;
try {
const result = await submitAssetTransfer({
type: this.activeTab,
targetId: this.currentTarget.id,
amount: this.amountValue,
}, {
showLoading: true,
loadingText: "转赠中",
});
this.confirmVisible = false;
uni.showToast({
title: "转赠成功,到账 " + result.received,
icon: "none",
});
this.form.amount = "";
this.loadPage();
} catch (error) {
uni.showToast({
title: error.message || "转赠失败",
icon: "none",
});
} finally {
this.submitting = false;
}
},
openLedger() {
uni.navigateTo({
url: "/pages/assets/ledger?type=transfer",
});
},
},
};
</script>
<style lang="scss" scoped>
@import "../../styles/tokens.scss";
@import "../../styles/common.scss";
.transfer-page {
min-height: 100vh;
background: #191e32;
}
.transfer-scroll {
padding: 10rpx 22rpx calc(env(safe-area-inset-bottom) + 36rpx);
}
.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;
}
.panel-card {
margin-top: 34rpx;
padding: 22rpx 20rpx 24rpx;
border-radius: 12rpx;
background: #242944;
box-shadow: 0 12rpx 24rpx rgba(8, 13, 30, 0.12);
}
.section-head {
display: flex;
align-items: center;
}
.section-head__icon {
width: 28rpx;
height: 28rpx;
flex-shrink: 0;
}
.section-head__title {
margin-left: 20rpx;
font-size: 28rpx;
font-weight: 700;
color: #ffffff;
}
.section-head__meta {
margin-left: 12rpx;
font-size: 26rpx;
color: #9ba7ce;
}
.input-bar {
display: flex;
align-items: center;
margin-top: 22rpx;
padding: 0 18rpx;
border-radius: 10rpx;
background: #191e32;
}
.input-bar__field {
flex: 1;
height: 84rpx;
font-size: 28rpx;
color: #ffffff;
}
.input-bar__placeholder {
color: rgba(170, 179, 204, 0.66);
}
.input-bar__action {
margin-left: 20rpx;
font-size: 28rpx;
font-weight: 600;
color: #20b6f5;
}
.tips-list {
margin-top: 20rpx;
}
.tips-list__item {
display: flex;
margin-top: 10rpx;
}
.tips-list__index,
.tips-list__text {
font-size: 24rpx;
color: #929dbf;
line-height: 44rpx;
color: rgba(175, 184, 211, 0.92);
}
.tips-list__index {
margin-right: 6rpx;
}
.tips-list__text {
flex: 1;
}
.transfer-split {
display: flex;
align-items: center;
justify-content: center;
margin-top: 18rpx;
}
.transfer-split__icon {
width: 36rpx;
height: 36rpx;
transform: rotate(90deg);
}
.recipient-card {
margin-top: 18rpx;
}
.recipient-card__title-icon {
position: relative;
width: 28rpx;
height: 28rpx;
flex-shrink: 0;
}
.recipient-card__title-head {
position: absolute;
left: 8rpx;
top: 0;
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: #18aef2;
}
.recipient-card__title-body {
position: absolute;
left: 3rpx;
bottom: 2rpx;
width: 22rpx;
height: 14rpx;
border-radius: 12rpx 12rpx 8rpx 8rpx;
background: #18aef2;
}
.recipient-search {
margin-top: 24rpx;
}
.target-card {
margin-top: 18rpx;
padding: 18rpx 18rpx 16rpx;
border-radius: 10rpx;
background: #191e32;
font-size: 28rpx;
}
.target-card__top {
display: flex;
align-items: center;
justify-content: space-between;
}
.target-card__id {
font-weight: 600;
color: #ffffff;
}
.target-card__select {
display: flex;
align-items: center;
}
.target-card__select-text {
margin-right: 12rpx;
font-size: 28rpx;
color: rgba(244, 248, 255, 0.88);
}
.target-card__select-dot {
display: flex;
align-items: center;
justify-content: center;
width: 28rpx;
height: 28rpx;
border-radius: 50%;
background: #20b6f5;
font-size: 16rpx;
font-weight: 700;
color: #ffffff;
}
.target-card__line {
height: 1px;
margin: 18rpx 0 16rpx;
background: rgba(197, 206, 233, 0.3);
}
.target-card__bottom,
.confirm-detail__profile {
display: flex;
align-items: center;
justify-content: space-between;
}
.target-card__profile {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
.target-card__avatar {
display: flex;
align-items: center;
justify-content: center;
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background: linear-gradient(135deg, #ffbf86 0%, #f38b66 100%);
overflow: hidden;
flex-shrink: 0;
}
.target-card__avatar--image {
background: transparent;
}
.target-card__avatar-image {
width: 100%;
height: 100%;
}
.target-card__avatar-text {
font-weight: 700;
color: #ffffff;
}
.target-card__name {
flex: 1;
min-width: 0;
font-weight: 400;
font-size: 28rpx;
margin-left: 16rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #ffffff;
}
.target-card__phone {
margin-left: 18rpx;
font-size: 28rpx;
font-weight: 400;
color: rgba(244, 248, 255, 0.88);
}
.action-wrap {
padding: 86rpx 20rpx 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: 28rpx;
text-align: center;
font-weight: 500;
font-size: 28rpx;
color: #1fb4ff;
}
.confirm-detail__summary {
display: block;
margin-top: 6rpx;
font-size: 30rpx;
font-weight: 500;
text-align: center;
color: #ff8b5d;
}
.confirm-detail__summary-sub {
color: #ff8b5d;
}
.confirm-detail__target {
margin-top: 18rpx;
padding: 18rpx;
border-radius: 10rpx;
background: #191e32;
}
.confirm-detail__id {
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
}
.confirm-detail__line {
height: 1px;
margin: 18rpx 0 16rpx;
background: rgba(197, 206, 233, 0.3);
}
.confirm-detail__desc {
display: block;
margin-top: 18rpx;
font-size: 24rpx;
line-height: 1.65;
color: rgba(186, 194, 218, 0.88);
}
</style>