794 lines
18 KiB
Vue
794 lines
18 KiB
Vue
<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 asset-number-font"
|
||
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 asset-number-font">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;
|
||
}
|
||
|
||
.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 {
|
||
margin-left: 16rpx;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.target-card__phone {
|
||
margin-left: 18rpx;
|
||
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: 22rpx;
|
||
font-weight: 700;
|
||
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: 22rpx;
|
||
font-weight: 600;
|
||
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: 18rpx;
|
||
line-height: 1.65;
|
||
color: rgba(186, 194, 218, 0.88);
|
||
}
|
||
</style>
|