bm-bmt/pages/index/index.vue

977 lines
22 KiB
Vue

<template>
<view class="asset-page asset-theme home-page">
<view class="home-topbar">
<view class="home-topbar__side"></view>
<text class="home-topbar__title">{{ overview.title || "数字资产" }}</text>
<view class="home-wallet-btn" @click="openWallet">
<view class="home-wallet-btn__icon">
<image
src="https://imgs.agrimedia.cn/bm-bmt/qianbao.png"
mode="widthFix"
></image>
</view>
<text class="home-wallet-btn__text">钱包</text>
</view>
</view>
<view class="asset-scroll home-scroll">
<view v-if="isHomeLoading" class="home-skeleton">
<view class="home-skeleton__hero">
<view class="home-skeleton__banner skeleton-block"></view>
<view class="home-skeleton__stat-panel">
<view v-for="index in 2" :key="'home-skeleton-stat-' + index" class="home-skeleton__stat-card skeleton-card">
<view class="home-skeleton__line home-skeleton__line--label skeleton-block"></view>
<view class="home-skeleton__line home-skeleton__line--value skeleton-block"></view>
</view>
</view>
</view>
<view class="home-skeleton__section">
<view class="home-skeleton__title skeleton-block"></view>
<view class="home-skeleton__asset-grid">
<view v-for="index in 4" :key="'home-skeleton-asset-' + index" class="home-skeleton__asset-card skeleton-card">
<view class="home-skeleton__line home-skeleton__line--asset-label skeleton-block"></view>
<view class="home-skeleton__line home-skeleton__line--asset-value skeleton-block"></view>
</view>
</view>
</view>
<view class="home-skeleton__section">
<view class="home-skeleton__title skeleton-block"></view>
<view class="home-skeleton__feature-list">
<view v-for="index in 5" :key="'home-skeleton-feature-' + index" class="home-skeleton__feature-item skeleton-card">
<view class="home-skeleton__feature-main">
<view class="home-skeleton__feature-icon skeleton-block"></view>
<view class="home-skeleton__line home-skeleton__line--feature skeleton-block"></view>
</view>
<view class="home-skeleton__feature-arrow skeleton-block"></view>
</view>
</view>
</view>
<view class="home-skeleton__notice">
<view class="home-skeleton__notice-icon skeleton-block"></view>
<view class="home-skeleton__notice-lines">
<view class="home-skeleton__line home-skeleton__line--notice skeleton-block"></view>
<view class="home-skeleton__line home-skeleton__line--notice-short skeleton-block"></view>
</view>
</view>
</view>
<view v-show="!isHomeLoading">
<view class="home-hero">
<view class="brand-banner"></view>
<view class="stat-panel">
<view
v-for="item in overview.topStats"
:key="item.key"
class="stat-card"
:class="'stat-card--' + item.accent"
@click="openTopStat(item)"
>
<view class="stat-card__body">
<text class="stat-card__label">{{ item.title }}</text>
<view class="stat-card__value-row">
<text class="stat-card__value asset-number-font">{{ item.value }}</text>
<text class="stat-card__unit">{{ item.unit }}</text>
</view>
</view>
</view>
</view>
</view>
<view class="home-section">
<view class="home-section__head">
<text class="home-section__title">我的资产</text>
</view>
<view class="asset-grid">
<view
v-for="item in overview.quickAssets"
:key="item.key"
class="asset-mini-card"
:class="'asset-mini-card--' + item.accent"
@click="openQuickAsset(item)"
>
<image
class="asset-mini-card__bg"
:src="quickAssetBg(item.key)"
mode="aspectFill"
></image>
<view class="asset-mini-card__head">
<text class="asset-mini-card__label">{{ item.title }}</text>
</view>
<text class="asset-mini-card__value asset-number-font">{{ item.value }}</text>
</view>
</view>
</view>
<view class="home-section">
<view class="home-section__head">
<text class="home-section__title">功能中心</text>
</view>
<view class="feature-list">
<view
v-for="feature in overview.features"
:key="feature.key"
class="feature-list__item"
@click="openFeature(feature.key)"
>
<view class="feature-list__main">
<view class="feature-list__icon">
<image
class="feature-list__icon-image"
:src="featureIcon(feature.key)"
mode="aspectFit"
></image>
</view>
<text class="feature-list__title">{{ feature.title }}</text>
</view>
<image class="feature-list__arrow" src="https://imgs.agrimedia.cn/bm-bmt/more.png"></image>
</view>
</view>
</view>
<view class="notice-bar">
<image
class="notice-bar__icon"
src="https://imgs.agrimedia.cn/bm-bmt/tips.png"
mode="widthFix"
></image>
<text class="notice-bar__text">{{ overview.notice }}</text>
</view>
<image src="https://imgs.agrimedia.cn/bm-bmt/bottom.png" mode="widthFix" style="width: 100%; margin-top: 20rpx"></image>
</view>
</view>
</view>
</template>
<script>
import {
fetchAssetHome,
fetchCouponRedeemLinkData,
fetchVoucherBrokerLinkData,
} from "../../api/assets";
import serviceConfig from "../../config/service";
export default {
data() {
return {
isHomeLoading: true,
hasLoadedOnce: false,
isFetchingHome: false,
isOpeningCouponRedeem: false,
isOpeningVoucherBroker: false,
overview: {
title: "数字资产",
heroTitle: "BM数字资产管理",
topStats: [],
quickAssets: [],
features: [],
notice: "",
},
};
},
onLoad() {
this.loadPage({
showSkeleton: true,
});
},
onShow() {
this.loadPage({
showSkeleton: !this.hasLoadedOnce,
});
},
methods: {
async loadPage(options = {}) {
if (this.isFetchingHome) {
return;
}
const shouldShowSkeleton =
Boolean(options.showSkeleton) || !this.hasLoadedOnce;
if (shouldShowSkeleton) {
this.isHomeLoading = true;
}
this.isFetchingHome = true;
try {
const nextOverview = await fetchAssetHome();
this.overview = nextOverview;
this.hasLoadedOnce = true;
} catch (error) {
uni.showToast({
title: error.message || "首页加载失败",
icon: "none",
});
} finally {
if (shouldShowSkeleton || !this.hasLoadedOnce) {
this.isHomeLoading = false;
}
this.isFetchingHome = false;
}
},
quickAssetIcon(key) {
const iconMap = {
points: "https://imgs.agrimedia.cn/bm-bmt/jifen-icon.png",
voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-icon.png",
coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-icon.png",
power: "https://imgs.agrimedia.cn/bm-bmt/suanli-icon.png",
};
return iconMap[key] || "";
},
quickAssetBg(key) {
const bgMap = {
points: "https://imgs.agrimedia.cn/bm-bmt/jifen-bg.png",
voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-bg.png",
coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-bg.png",
power: "https://imgs.agrimedia.cn/bm-bmt/suanli-bg.png",
};
return bgMap[key] || "";
},
featureIcon(key) {
const iconMap = {
"bmt-exchange": "https://imgs.agrimedia.cn/bm-bmt/b.png",
"power-exchange": "https://imgs.agrimedia.cn/bm-bmt/s.png",
transfer: "https://imgs.agrimedia.cn/bm-bmt/z.png",
withdraw: "https://imgs.agrimedia.cn/bm-bmt/t.png",
"points-convert": "https://imgs.agrimedia.cn/bm-bmt/j.png",
};
return iconMap[key] || "";
},
openFeature(key) {
const urlMap = {
transfer: "/pages/assets/transfer",
"power-exchange": "/pages/assets/power-exchange",
"bmt-exchange": "/pages/assets/bmt-exchange",
withdraw: "/pages/assets/withdraw",
"points-convert": "/pages/assets/points-convert",
};
const targetUrl = urlMap[key];
if (!targetUrl) {
uni.showToast({
title: "功能开发中",
icon: "none",
});
return;
}
uni.navigateTo({
url: targetUrl,
});
},
openTopStat(item) {
const key = item && item.key ? item.key : "";
const urlMap = {
"wallet-bmt": "/pages/assets/ledger?type=withdraw",
};
const targetUrl = urlMap[key];
if (!targetUrl) {
return;
}
uni.navigateTo({
url: targetUrl,
});
},
openQuickAsset(item) {
if (item && item.key === "voucher") {
this.openVoucherBroker();
return;
}
if (item && item.key === "coupon") {
this.openCouponRedeem();
return;
}
const urlMap = {
points: "/pages/assets/ledger?type=points",
power: "/pages/assets/ledger?type=power-flow",
};
const targetUrl = urlMap[item.key];
if (!targetUrl) {
return;
}
uni.navigateTo({
url: targetUrl,
});
},
navigateToExternalUrl(targetUrl) {
if (!targetUrl) {
return;
}
if (typeof window !== "undefined" && window.location) {
window.location.href = targetUrl;
return;
}
if (
typeof plus !== "undefined" &&
plus.webview &&
plus.webview.currentWebview
) {
const currentWebview = plus.webview.currentWebview();
if (currentWebview && typeof currentWebview.loadURL === "function") {
currentWebview.loadURL(targetUrl);
return;
}
}
uni.navigateTo({
url: targetUrl,
});
},
async openVoucherBroker() {
if (this.isOpeningVoucherBroker) {
return;
}
this.isOpeningVoucherBroker = true;
try {
const result = await fetchVoucherBrokerLinkData({
showLoading: true,
loadingText: "加载中",
});
const targetUrl =
serviceConfig.HTTP_REQUEST_URL +
"/JXH5/pages/users/user_broker/index?user_id=" +
encodeURIComponent(result.userId) +
"&total=" +
encodeURIComponent(result.balance);
this.navigateToExternalUrl(targetUrl);
} catch (error) {
uni.showToast({
title: error.message || "跳转失败",
icon: "none",
});
} finally {
this.isOpeningVoucherBroker = false;
}
},
async openCouponRedeem() {
if (this.isOpeningCouponRedeem) {
return;
}
this.isOpeningCouponRedeem = true;
try {
const result = await fetchCouponRedeemLinkData({
showLoading: true,
loadingText: "加载中",
});
this.navigateToExternalUrl(result.targetUrl);
} catch (error) {
uni.showToast({
title: error.message || "跳转失败",
icon: "none",
});
} finally {
this.isOpeningCouponRedeem = false;
}
},
openWallet() {
uni.navigateTo({
url: "/pages/assets/wallet",
});
},
},
};
</script>
<style lang="scss" scoped>
@import "../../styles/tokens.scss";
@import "../../styles/common.scss";
.home-page {
background: #191e32;
}
.home-topbar {
display: grid;
grid-template-columns: 136rpx 1fr 136rpx;
align-items: center;
padding: calc(env(safe-area-inset-top) + 20rpx) 16rpx 8rpx;
}
.home-topbar__side {
width: 136rpx;
height: 1rpx;
}
.home-topbar__title {
text-align: center;
font-size: 36rpx;
font-weight: 700;
color: #ffffff;
letter-spacing: 1rpx;
}
.home-wallet-btn {
justify-self: end;
display: flex;
// align-items: center;
height: 52rpx;
color: rgba(229, 235, 255, 0.82);
padding-right: 10rpx;
}
.home-wallet-btn__icon {
width: 30rpx;
height: 32rpx;
margin-right: 8rpx;
image {
width: 30rpx;
height: 32rpx;
}
}
.home-wallet-btn__text {
font-size: 28rpx;
color: rgba(229, 235, 255, 0.82);
padding-top: 10rpx;
}
.home-scroll {
padding: 12rpx 20rpx 24rpx;
}
.home-skeleton {
padding-bottom: 6rpx;
}
.skeleton-block {
position: relative;
overflow: hidden;
background: rgba(86, 98, 135, 0.32);
}
.skeleton-block::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: -140rpx;
width: 140rpx;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.24) 50%,
rgba(255, 255, 255, 0) 100%
);
animation: home-skeleton-shimmer 1.35s linear infinite;
}
.skeleton-card {
border-radius: 12rpx;
background: rgba(36, 41, 68, 0.92);
border: 1px solid rgba(129, 141, 183, 0.16);
}
.home-skeleton__hero {
position: relative;
margin-bottom: 26rpx;
}
.home-skeleton__banner {
height: 380rpx;
border-radius: 28rpx;
}
.home-skeleton__stat-panel {
position: absolute;
left: 20rpx;
right: 20rpx;
bottom: 24rpx;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 20rpx;
}
.home-skeleton__stat-card {
padding: 22rpx 22rpx 20rpx;
min-height: 126rpx;
}
.home-skeleton__section {
margin-top: 28rpx;
}
.home-skeleton__title {
width: 154rpx;
height: 34rpx;
border-radius: 8rpx;
margin-bottom: 16rpx;
}
.home-skeleton__asset-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16rpx;
}
.home-skeleton__asset-card {
height: 80rpx;
padding: 14rpx 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.home-skeleton__feature-list {
display: grid;
gap: 16rpx;
}
.home-skeleton__feature-item {
min-height: 100rpx;
padding: 0 22rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.home-skeleton__feature-main {
display: flex;
align-items: center;
min-width: 0;
flex: 1;
}
.home-skeleton__feature-icon {
width: 36rpx;
height: 36rpx;
border-radius: 8rpx;
flex-shrink: 0;
}
.home-skeleton__feature-arrow {
width: 24rpx;
height: 24rpx;
border-radius: 999rpx;
margin-left: 20rpx;
}
.home-skeleton__notice {
display: flex;
align-items: flex-start;
margin-top: 20rpx;
padding: 0 2rpx;
}
.home-skeleton__notice-icon {
width: 28rpx;
height: 28rpx;
border-radius: 50%;
margin-right: 8rpx;
margin-top: 4rpx;
flex-shrink: 0;
}
.home-skeleton__notice-lines {
flex: 1;
}
.home-skeleton__line {
border-radius: 8rpx;
}
.home-skeleton__line--label {
width: 150rpx;
height: 24rpx;
}
.home-skeleton__line--value {
width: 200rpx;
height: 34rpx;
margin-top: 16rpx;
}
.home-skeleton__line--asset-label {
width: 86rpx;
height: 24rpx;
}
.home-skeleton__line--asset-value {
width: 118rpx;
height: 30rpx;
}
.home-skeleton__line--feature {
width: 200rpx;
height: 28rpx;
margin-left: 20rpx;
}
.home-skeleton__line--notice {
width: 100%;
height: 24rpx;
}
.home-skeleton__line--notice-short {
width: 72%;
height: 24rpx;
margin-top: 10rpx;
}
@keyframes home-skeleton-shimmer {
0% {
transform: translateX(0);
}
100% {
transform: translateX(900rpx);
}
}
.home-hero {
position: relative;
margin-bottom: 26rpx;
}
.brand-banner {
position: relative;
overflow: hidden;
height: 380rpx;
border-radius: 28rpx;
background:
linear-gradient(
180deg,
rgba(5, 12, 28, 0.06) 0%,
rgba(5, 12, 28, 0.16) 100%
),
url("https://imgs.agrimedia.cn/bm-bmt/home2-bg.png")
no-repeat;
border: 1px solid rgba(112, 135, 196, 0.18);
background-size: cover;
box-shadow: 0 24rpx 48rpx rgba(7, 12, 32, 0.28);
}
.brand-banner::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.02), transparent 26%);
pointer-events: none;
}
.brand-banner::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 120rpx;
background: linear-gradient(180deg, rgba(4, 10, 24, 0), rgba(4, 10, 24, 0.18));
pointer-events: none;
}
.stat-panel {
position: absolute;
z-index: 2;
left: 20rpx;
right: 20rpx;
bottom: 24rpx;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 20rpx;
padding: 0;
background: transparent;
}
.stat-card {
position: relative;
overflow: hidden;
min-height: 126rpx;
padding: 22rpx 22rpx 20rpx;
border-radius: 8rpx;
background: linear-gradient(
180deg,
rgba(42, 55, 82, 0.98) 0%,
rgba(39, 51, 76, 0.98) 100%
);
border: 1px solid rgba(121, 139, 190, 0.16);
box-shadow: 0 14rpx 24rpx rgba(5, 11, 29, 0.2);
}
.stat-card::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.05), transparent 34%);
pointer-events: none;
}
.stat-card__icon-box {
position: absolute;
right: 16rpx;
top: 16rpx;
display: flex;
align-items: center;
justify-content: center;
width: 56rpx;
height: 56rpx;
border-radius: 8rpx;
border: 1px solid rgba(255, 255, 255, 0.08);
flex-shrink: 0;
}
.stat-card__icon-box--gold {
background: linear-gradient(
135deg,
rgba(20, 182, 255, 0.22) 0%,
rgba(20, 182, 255, 0.08) 100%
);
}
.stat-card__icon-box--green {
background: linear-gradient(
135deg,
rgba(46, 233, 167, 0.22) 0%,
rgba(46, 233, 167, 0.08) 100%
);
}
.stat-card__body {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
flex: 1;
min-width: 0;
min-height: 84rpx;
margin-left: 0;
padding-right: 56rpx;
}
.stat-card__label {
display: block;
font-size: 26rpx;
font-weight: 500;
line-height: 1.25;
white-space: nowrap;
}
.stat-card--gold .stat-card__label {
color: #19b9ff;
}
.stat-card--green .stat-card__label {
color: #28e0a6;
}
.stat-card__value-row {
display: flex;
align-items: baseline;
flex-wrap: wrap;
margin-top: 16rpx;
min-width: 0;
}
.stat-card__value {
font-size: 32rpx;
font-weight: 800;
color: #ffffff;
line-height: 1;
}
.stat-card__unit {
margin-left: 8rpx;
font-size: 22rpx;
line-height: 1.2;
color: rgba(170, 181, 208, 0.92);
white-space: nowrap;
}
.home-section {
margin-top: 28rpx;
}
.home-section__head {
margin-bottom: 16rpx;
}
.home-section__title {
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
}
.asset-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16rpx;
}
.asset-mini-card {
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: space-between;
min-width: 0;
height: 80rpx;
line-height: 80rpx;
padding: 0rpx 20rpx;
border-radius: 12rpx;
box-shadow: 0 12rpx 24rpx rgba(10, 16, 37, 0.16);
background: rgba(35, 45, 79, 0.9);
}
.asset-mini-card__bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.asset-mini-card--blue {
background: linear-gradient(135deg, #10acee 0%, #1aa4ff 100%);
}
.asset-mini-card--green {
background: linear-gradient(135deg, #25d8a4 0%, #36e08f 100%);
}
.asset-mini-card--orange {
background: linear-gradient(135deg, #ff8d58 0%, #ff7d7d 100%);
}
.asset-mini-card--violet {
background: linear-gradient(135deg, #a667f3 0%, #b27cff 100%);
}
.asset-mini-card__head,
.asset-mini-card__value {
position: relative;
z-index: 1;
}
.asset-mini-card__head {
display: flex;
font-size: 26rpx;
align-items: center;
}
.asset-mini-card__icon {
display: flex;
align-items: center;
justify-content: center;
width: 38rpx;
height: 38rpx;
flex-shrink: 0;
}
.asset-mini-card__icon-image {
width: 32rpx;
height: 32rpx;
}
.asset-mini-card__label {
margin-left: 14rpx;
font-size: 26rpx;
font-weight: 500;
color: rgba(255, 255, 255, 0.94);
white-space: nowrap;
}
.asset-mini-card__value {
display: block;
font-size: 32rpx;
font-weight: 800;
text-align: right;
color: #ffffff;
}
.feature-list {
display: grid;
gap: 16rpx;
}
.feature-list__item {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 100rpx;
padding: 0 22rpx;
border-radius: 12rpx;
background: #242944;
box-shadow: 0 12rpx 22rpx rgba(8, 13, 30, 0.14);
}
.feature-list__main {
display: flex;
align-items: center;
min-width: 0;
}
.feature-list__icon {
display: flex;
align-items: center;
justify-content: center;
width: 36rpx;
height: 36rpx;
flex-shrink: 0;
}
.feature-list__icon-image {
width: 32rpx;
height: 32rpx;
}
.feature-list__title {
margin-left: 20rpx;
font-size: 28rpx;
font-weight: 400;
color: #f3f6ff;
}
.feature-list__arrow {
margin-left: 16rpx;
width: 26rpx;
height: 26rpx;
line-height: 1;
}
.notice-bar {
display: flex;
align-items: flex-start;
margin-top: 20rpx;
padding: 0 2rpx;
}
.notice-bar__icon {
display: flex;
align-items: center;
justify-content: center;
width: 28rpx;
height: 28rpx;
margin-right: 8rpx;
margin-top: 4rpx;
background: linear-gradient(180deg, #ffd764 0%, #ffbb2e 100%);
font-size: 16rpx;
font-weight: 700;
color: #ffffff;
flex-shrink: 0;
}
.notice-bar__text {
font-size: 24rpx;
line-height: 1.65;
color: rgba(200, 207, 227, 0.82);
}
</style>