alipay-emulator/pages/finance-management/index.vue

799 lines
20 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 v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
<view class="container flex flex-column" @touchstart="handleTouchStart" @touchmove.stop.prevent="handleTouchMove"
@touchend="handleTouchEnd">
<view class="main-info-container" :class="{ 'open': isOpen }"
:style="{ 'padding-bottom': `${cardArrowHeight}px` }">
<!-- 导航栏 placeholder -->
<NavBar bgColor="transparent">
<view class="nav-bar flex-align-center h100">
<view class="left flex-align-center">
<image src="/static/image/finance-management/search/search-left.png"></image>
</view>
<view class="nav-bar-search flex-align-center flex-1">
<image class="search-icon" src="/static/image/bill/bill-list/search-black.png" mode=""></image>
<input type="text" class="search-input flex-1" placeholder="小而美的基金" />
<view class="line h100"></view>
<view class="search-button">搜索</view>
</view>
<view class="right flex-align-center">
<image src="/static/image/finance-management/search/search-right.png"></image>
</view>
</view>
</NavBar>
<view class="total-money-box flex-align-center flex-justify-between">
<view class="revenue-box flex-1">
<view class="total-money flex-align-center">
<view class="total-money-title flex-align-center">
<text>总资产()</text>
<image class="eye-icon" src="/static/image/finance-management/eye.png"></image>
<view class="flex-align-center" style="color: #A39797;font-size: 22rpx;">
<image class="safe-icon" src="/static/image/finance-management/safe.png"></image>
<text>免费升级保障</text>
</view>
</view>
<view class="total-money-title flex-align-center">
<text>昨日收益</text>
</view>
</view>
<view class="total-money flex-align-center" style="margin-top: 10px;">
<view class="total-money-value flex-align-center">
<text class="alipay-font">{{ numberUtil.formatMoneyWithThousand(totalMoney) }}</text>
</view>
<view class="total-money-value flex-align-center">
<text class="alipay-font">{{ yesterdayIncome > 0 ? "+" +
numberUtil.formatMoneyWithThousand(yesterdayIncome) :
numberUtil.formatMoneyWithThousand(yesterdayIncome)
}}</text>
</view>
</view>
</view>
<view class="family-protection">
<view class="total-money-title flex-align-center">
<text>家庭保障</text>
</view>
<view class=" flex-align-end" style="margin-top: 10px;">
<text class="value alipay-font">{{ familyProtection }}</text>
<text class="text"></text>
</view>
</view>
</view>
<view class="data-box">
<view v-for="item in myAssets" :key="item.title">
<view v-if="item.title" class="title second-color">{{ item.title }}</view>
<view class="group-data flex-justify-between">
<template v-for="subItem in item.items" :key="subItem.label">
<view v-if="financeInfo[item.key][subItem.key].labelSub"
class="item flex-align-center flex-justify-between">
<view class="flex-align-center">
<text class="label primary-color">{{ subItem.label }}</text>
<text v-if="financeInfo[item.key][subItem.key].type == 'text'"
class="label-sub second-color">
{{ formatTextWithNumber(financeInfo[item.key][subItem.key].labelSub) }}
</text>
<text v-else class="label-sub second-color">
{{
numberUtil.formatMoneyWithThousand(financeInfo[item.key][subItem.key].labelSub)
}}</text>
</view>
<text v-if="financeInfo[item.key][subItem.key].value" class="value primary-color">{{
financeInfo[item.key][subItem.key].value > 0 ? "+" +
numberUtil.formatMoneyWithThousand(financeInfo[item.key][subItem.key].value) :
numberUtil.formatMoneyWithThousand(financeInfo[item.key][subItem.key].value)
}}</text>
</view>
</template>
<view v-if="item.groupId == '1'" class="item flex-align-center flex-justify-between">
<view class="flex-align-center">
<text class="label primary-color">更多资产服务</text>
</view>
<text class="value primary-color">去看看</text>
</view>
</view>
</view>
</view>
</view>
<view class="image-service-box flex-column"
:style="{ top: topHeight + 'px', 'min-height': `calc(100vh - ${imageCardBoxtopHeight}px)` }">
<view class="card-arrow-box">
<view class="arrow-box">
<image @click="data.isOpen = !data.isOpen" class="arrow-icon"
:src="`/static/image/finance-management/arrow-${isOpen ? 'up' : 'down'}.png`">
</image>
</view>
<view class="card-box flex-column">
<view class="wealth-card-box flex">
<!-- 占位图片设置为透明 -->
<image class="wealth-card-opacity0" src="/static/image/finance-management/v1/bg.png"
mode="widthFix" @load="updateTopHeight" />
<!-- 背景图片高度自适应 -->
<image class="wealth-card"
:src="`/static/image/finance-management/${financeInfo.vipLevel}/bg.png`" mode="widthFix" />
<view class="card-info">
<image class="logo"
:src="`/static/image/finance-management/${financeInfo.vipLevel}/logo.png`"
mode="scaleToFill" />
<view class="name-box flex-align-center">
<image class="card-holder-icon"
:src="`/static/image/finance-management/${financeInfo.vipLevel}/card-holder.png`"
mode="scaleToFill" />
<view class="card-holder-name sacramento-font">{{ financeInfo.cardHolderName }}</view>
</view>
</view>
<view class="enjoy-benefits flex-align-center">
<text class="benefits-text">{{ financeInfo.benefitsText }}</text>
<image class="arrow-icon"
:src="`/static/image/finance-management/${financeInfo.vipLevel}/arrow.png`"></image>
</view>
</view>
<view class="content-box">
<view class="service-icons">
<view class="service-icon" v-for="item in serviceIcons" :key="item.name">
<image class="icon"
:src="`/static/image/finance-management/service-icon/${item.icon}.png`"
mode="scaleToFill" />
<text class="name">{{ item.name }}</text>
</view>
</view>
</view>
</view>
</view>
<view class="content flex-1 flex-align-center flex-justify-center">
<view class="flex-align-center flex-column">
<image style="width:92rpx; height: 92rpx;margin-top: 16rpx;"
src="/static/image/common/upload-screenshot.png">
</image>
<text style="font-size: 36rpx;color: #1777FF;">长按替换真实截图</text>
</view>
</view>
</view>
<view class="bottom-box"
:class="`navigation-menu-${financeInfo.navigationMenuStyle}`, { 'ios-bottom-box': $system == 'iOS' }">
<view class="bottom-item" v-for="item in navigationMenu" :key="item.name">
<image
:src="`/static/image/finance-management/navigation-menu/${financeInfo.navigationMenuStyle}/${item.icon}.png`">
</image>
<text>{{ item.name }}</text>
</view>
</view>
</view>
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue'
import { ref, toRefs, reactive, onMounted, watch, computed } from 'vue'
import { onLoad, onReady } from '@dcloudio/uni-app'
import { serviceIcons, navigationMenu, myAssets } from '@/static/json/fortune.json'
import { numberUtil } from '@/utils/common.js'
onLoad(() => {
// 理财页面埋点
// proxy.$apiUserEvent('all', {
// type: 'click',
// key: 'fortune',
// value: "理财"
// })
})
onReady(() => {
statusBarHeight.value = uni.getSystemInfoSync().statusBarHeight;
uni.createSelectorQuery().select('.total-money-box').boundingClientRect((data) => {
console.log("total-money-box height: " + (data ? data.height : "null"));
topHeight.value = data ? data.height + 44 + statusBarHeight.value : 44 + statusBarHeight.value;
imageCardBoxtopHeight.value = data ? data.height + 44 + statusBarHeight.value : 44 + statusBarHeight.value;
}).exec()
})
const updateTopHeight = () => {
setTimeout(() => {
uni.createSelectorQuery().select('.card-arrow-box').boundingClientRect((data) => {
console.log("card-arrow-box height: " + (data ? data.height : "null"));
cardArrowHeight.value = data ? data.height - 20 : 0;
}).exec()
}, 500)
}
// 总资产
const totalMoney = computed(() => {
let totalMoney = 0;
for (const key in financeInfo.value.fortune) {
if (!Object.hasOwn(financeInfo.value.fortune, key)) continue;
const element = financeInfo.value.fortune[key];
totalMoney += Number(element.labelSub)
}
return totalMoney
})
// 昨日收益
const yesterdayIncome = computed(() => {
let yesterdayIncome = 0;
for (const key in financeInfo.value.fortune) {
if (!Object.hasOwn(financeInfo.value.fortune, key)) continue;
const element = financeInfo.value.fortune[key];
yesterdayIncome += Number(element.value)
}
return yesterdayIncome
})
// 家庭保障
const familyProtection = computed(() => {
return parseInt(financeInfo.value.myAssets.baoxian.labelSub)
})
const data = reactive({
financeInfo: {
benefitsText: '尊享礼遇18000元/年',
cardHolderName: 'LiXing',
vipLevel: "v3",
navigationMenuStyle: "style-1",
fortune: {
yuebao: {
value: 3.53,
labelSub: "100000",
},
dingqi: {
value: 50,
labelSub: "100000",
},
jijin: {
value: 5000,
labelSub: "100000",
},
huangjin: {
value: -300,
labelSub: "100000",
},
yulingbao: {
value: 20,
labelSub: "100000",
}
},
myAssets: {
baoxian: {
labelSub: "3份保单保障中",
type: "text"
}
},
myQuota: {
huabei: {
labelSub: "可用100000",
type: "text"
},
jubei: {
labelSub: "可用100000",
type: "text"
},
beizhi: {
labelSub: "可用100000",
type: "text"
},
wangshangdai: {
labelSub: "预计可借100000",
type: "text"
}
}
},
topHeight: 0,
statusBarHeight: 0,
cardArrowHeight: 0,
imageCardBoxtopHeight: 0,
isOpen: false
})
let { financeInfo, topHeight, statusBarHeight, cardArrowHeight, imageCardBoxtopHeight, isOpen } = toRefs(data)
const formatTextWithNumber = (str) => {
if (!str) return '';
return str.replace(/(\d+(\.\d+)?)/g, (match) => {
return match > 100 ? numberUtil.formatMoneyWithThousand(match) : match;
});
}
let startY = 0;
let startTop = 0;
let minTop = 0;
let maxTop = 0;
// 监听 isOpen 变化来控制 topHeight
watch(() => isOpen.value, () => {
updateHeightByState();
})
const handleTouchStart = (e) => {
startY = e.touches[0].clientY;
startTop = topHeight.value;
// 如果未设置边界(或为了安全起见每次开始时),则计算边界
if (!minTop || !maxTop) {
const systemInfo = uni.getSystemInfoSync();
const statusBar = systemInfo.statusBarHeight;
// 创建查询以获取当前尺寸
const query = uni.createSelectorQuery();
query.select('.total-money-box').boundingClientRect();
query.select('.main-info-container').boundingClientRect();
query.exec(([totalMoneyBox, mainInfoContainer]) => {
if (totalMoneyBox) {
// 最小高度关闭状态total-money-box 高度 + 导航栏 + 状态栏 - 卡片箭头偏移量
minTop = totalMoneyBox.height + 44 + statusBar;
}
if (mainInfoContainer) {
// 最大高度打开状态main-info-container 高度 - 卡片箭头高度
// 注意isOpen 会切换内边距/布局,因此这可能需要根据当前状态进行调整。
// 为简单起见,我们假设我们在初始化/切换期间捕获了正确的值或对其进行了估算。
// 更稳健的方法:
// 关闭状态顶部:~160px示例
// 打开状态顶部:~400px示例
// 我们可以从之前使用的 'isOpen=true' 计算逻辑推断出 maxTop
// data.height (main-info-container 的高度)。
// 如果当前已关闭main-info-container 可能很小。
// 但我们知道 'isOpen=true' 会将 topHeight 设置为 main-info-container 的高度(打开时)。
// 让我们依赖之前记录/使用的值。
// 如果当前已关闭minTop 是当前 topHeight。
// 如果当前已打开maxTop 是当前 topHeight。
// 我们需要 *另一个* 值。
// 让我们在 onReady/updateTopHeight 中动态重新测量以确信,
// 但在这里我们可以尝试估算或依赖之前的逻辑。
// 更好的方法:根据 DOM 预先计算两者或使用固定逻辑。
// minTop 大约是 44 + statusBar + totalMoneyBoxHeight
// maxTop 是 mainContainerHeight展开时
}
// 目前,让我们使用 handleTouchEnd 中的逻辑来设置这些动态限制
// 我们将在 handleTouchEnd 或 init 中更新它们。
});
}
}
const handleTouchMove = (e) => {
let currentY = e.touches[0].clientY;
let delta = currentY - startY;
let newTop = startTop + delta;
// 如果为 0让我们获取它们。
if (minTop === 0 && !isOpen.value) minTop = startTop;
if (maxTop === 0 && isOpen.value) maxTop = startTop;
// 如果有值,则进行简单钳位
if (minTop && maxTop) {
if (newTop < minTop) newTop = minTop;
if (newTop > maxTop) newTop = maxTop;
}
// 应用
topHeight.value = newTop;
}
const updateHeightByState = () => {
statusBarHeight.value = uni.getSystemInfoSync().statusBarHeight;
if (isOpen.value) {
// 打开状态:计算 main-info-container 高度
uni.createSelectorQuery().select('.main-info-container').boundingClientRect((data) => {
if (data) {
const targetH = data.height - cardArrowHeight.value;
topHeight.value = targetH;
maxTop = targetH; // 更新 maxTop
}
}).exec()
} else {
// 关闭状态:计算 total-money-box 位置
uni.createSelectorQuery().select('.total-money-box').boundingClientRect((data) => {
if (data) {
const targetH = data.height + 44 + statusBarHeight.value;
topHeight.value = targetH;
minTop = targetH; // 更新 minTop
}
}).exec()
}
}
const handleTouchEnd = (e) => {
let endY = e.changedTouches[0].clientY;
let diff = endY - startY;
let targetState = isOpen.value;
// 判断滑动意图
if (diff > 50) {
targetState = true;
} else if (diff < -50) {
targetState = false;
}
// 如果状态改变watch 会处理动画/高度更新
if (targetState !== isOpen.value) {
isOpen.value = targetState;
} else {
// 如果状态未改变,手动恢复高度(吸附回原位)
updateHeightByState();
}
}
</script>
<style>
@import "@/common/main.css";
</style>
<style lang="less" scoped>
.container {
height: 100vh;
background-color: #ffffff;
overflow: hidden;
}
.main-info-container {
background: linear-gradient(348deg, #0B1028 49%, #0B1028 76.26%, #18336C 86.18%, #18336C 100%);
.nav-bar {
padding: 20rpx;
.left {
image {
height: 48rpx;
width: 48rpx;
}
}
.right {
image {
height: 36rpx;
width: 36rpx;
}
}
.nav-bar-search {
background-color: #ffffff;
border-radius: 300rpx;
height: 56rpx;
padding: 4px 0;
margin: 0 26rpx 0 14rpx;
.search-icon {
width: 32rpx;
height: 32rpx;
margin-left: 4px;
margin-right: 6px;
}
.search-input {
font-size: 14px;
}
::v-deep .input-placeholder {
color: #767676;
}
.line {
width: 1px;
background-color: #D8D8D8;
}
.search-button {
font-size: 14px;
padding: 0 12px;
color: #8B634E;
font-weight: 500;
}
}
}
.total-money-box {
padding-top: 10rpx;
padding-bottom: 6rpx;
margin-bottom: 32rpx;
.revenue-box {
padding: 0 24rpx;
.total-money {
font-size: 24rpx;
display: flex;
justify-content: space-between;
}
.total-money-value {
color: #F1CFAB;
}
}
.total-money-title {
color: #A39797;
.eye-icon {
width: 44rpx;
height: 44rpx;
margin: 0 32rpx 0 20rpx;
}
.safe-icon {
width: 24rpx;
height: 24rpx;
margin-right: 4rpx;
}
}
.total-money-value {
font-size: 50rpx;
font-weight: 500;
line-height: 64rpx;
}
.family-protection {
display: flex;
flex-direction: column;
flex-shrink: 0;
height: 100%;
padding: 0 28rpx;
border-left: 1rpx solid #172139;
align-items: center;
font-size: 24rpx;
.value {
color: #F1CFAB;
font-size: 56rpx;
font-weight: 500;
line-height: 64rpx;
}
.text {
color: #F1CFAB;
font-size: 24rpx;
margin-left: 8rpx;
}
}
}
.data-box {
padding: 0 26rpx 0;
font-size: 26rpx;
.title {
margin-bottom: 40rpx;
}
.group-data {
// margin-top: 8rpx;
flex-wrap: wrap;
.item {
width: calc(50% - 15rpx);
flex-shrink: 0;
margin-bottom: 40rpx;
.label {
margin-right: 18rpx;
flex-shrink: 0;
}
}
}
.second-color {
color: #787174;
}
.primary-color {
color: #EAD2B8;
}
}
}
.open {
// background: #08112E;
background: linear-gradient(146deg, #0B1028 49%, #08112E 71.26%, #18336C 83.18%, #18336C 96%)
}
.image-service-box {
position: absolute;
display: flex;
flex-direction: column;
top: 20%;
width: 100%;
.arrow-box {
display: flex;
align-items: center;
justify-content: center;
padding: 4rpx;
.arrow-icon {
width: 28rpx;
height: 28rpx;
}
}
.card-box {
display: flex;
flex-direction: column;
.wealth-card-box {
position: relative;
.card-info {
display: flex;
justify-content: space-between;
align-items: flex-start;
position: absolute;
width: 100%;
top: 12%;
padding: 0 5%;
.logo {
width: 188rpx;
height: 40rpx;
}
.name-box {
display: flex;
align-items: flex-start;
}
.card-holder-icon {
width: 68rpx;
height: 28rpx;
margin-right: 8rpx;
}
.card-holder-name {
color: #F1CFAB;
font-size: 30rpx;
}
}
.enjoy-benefits {
position: absolute;
bottom: 25%;
right: 4%;
.benefits-text {
color: #F1CFAB;
font-size: 24rpx;
}
.arrow-icon {
width: 28rpx;
height: 28rpx;
margin-left: 6rpx;
}
}
}
.wealth-card-opacity0 {
width: 100%;
opacity: 0;
}
.wealth-card {
position: absolute;
width: 100%;
}
.content-box {
margin-top: -1px;
background: linear-gradient(180deg, #FFFFFF 56.37%, #F0F4F9 100%);
padding: 20rpx;
.service-icons {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.service-icon {
display: flex;
flex-direction: column;
align-items: center;
width: 20%;
margin-top: 22rpx;
margin-bottom: 8rpx;
.icon {
width: 62rpx;
height: 62rpx;
}
.name {
font-size: 26rpx;
color: #5D5D5D;
}
}
}
}
}
}
.content {
background-color: #ffffff;
padding: 20rpx;
margin-top: -1px;
}
.bottom-box {
position: fixed;
bottom: 0;
width: 100%;
height: 100rpx;
background-color: #ffffff;
display: flex;
justify-content: space-around;
align-items: center;
padding: 4rpx 0;
.bottom-item {
display: flex;
flex-direction: column;
align-items: center;
image {
width: 56rpx;
height: 56rpx;
}
text {
margin-top: 12rpx;
font-size: 20rpx;
line-height: 25rpx;
color: #333;
}
}
}
.ios-bottom-box {
padding-bottom: constant(safe-area-inset-bottom); // 兼容 IOS<11.2
padding-bottom: env(safe-area-inset-bottom); // 兼容 IOS>11.2
}
.navigation-menu-style-1 {
background-color: #ffffff;
.bottom-item:nth-child(2) {
text {
color: #A27140;
}
}
}
.navigation-menu-style-2 {
background-color: #F8F8F8;
border-top: 1rpx solid #C3CED1;
padding: 6rpx 0;
.bottom-item:nth-child(2) {
text {
color: #0075FF;
}
}
}
</style>