alipay-emulator/pages/index/index.nvue

711 lines
15 KiB
Plaintext
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="container" :style="{ height: data.windowHeight + 'px' }">
<image class="index-bg-img" :style="{ width: windowWidth + 'px' }" src="/static/image/index/index-bg.png"
mode="widthFix">
</image>
<view class="nav-bar-box">
<view class="status-box" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="nav-box">
<view class="left-box" @click="exit">
<image style="width: 40rpx; height: 40rpx;" src="/static/image/nav-bar/back-black.png"></image>
</view>
<view class="title">小宝模拟器</view>
<view class="right-box"></view>
</view>
</view>
<view class="content-box" :style="{ height: windowHeight + 'px' }">
<view>
<view style="background-color: transparent;" :style="{ height: (44 + statusBarHeight) + 'px' }"></view>
<view class="user-box">
<image class="user-bg" :style="{ width: (windowWidth - 32) + 'px' }"
:src="`/static/image/index/${userInfo.vip > 1 ? (userInfo.vip == 3 ? 'lifetime-vip-bg' : 'vip-bg') : 'no-vip-bg'}.png`"
mode=""></image>
<view class="user-info-box" :style="{ width: (windowWidth - 32) + 'px' }">
<image class="user-avatar" :src="userInfo.avater"></image>
<view class="user-info">
<view class="name-box">
<text class="phone-text">ID{{ userInfo.user_id }}</text>
<image v-if="userInfo.vip > 1" class="vip-logo" src="/static/image/index/vip-logo.png">
</image>
</view>
<view class="">
<text v-if="userInfo.vip > 1" class="vip-end-time">会员到期:{{ userInfo.vip_expire }}</text>
<text v-else class="vip-end-time">开通会员解锁全部功能</text>
</view>
</view>
<view v-if="userInfo.vip && userInfo.vip != 3" class="btn-box">
<image class="open-vip-btn"
:src="`/static/image/index/${userInfo.vip > 1 ? 'vip-btn' : 'open-vip-btn'}.png`">
</image>
</view>
</view>
</view>
<view class="notice-box" @click="clickNotice">
<view class="sound-box">
<uni-icons type="sound" size="18" color="#D8D8D8"></uni-icons>
</view>
<view ref="noticeContainer" class="notice-content-wrapper">
<view ref="noticeInner" class="notice-inner">
<text ref="noticeBox" class="notice-content" style="margin-right: 30rpx;">{{ noticeInfo.text
}}</text>
<text class="notice-content" style="margin-right: 30rpx;">{{ noticeInfo.text }}</text>
</view>
</view>
</view>
<view class="group-box">
<image class="title-img" src="/static/image/index/shipingjiaocheng.png"></image>
<view class="video-help-box">
<view class="video-help-item" v-for="item in videoHelpList" :key="item.id"
@click="clickVideoHelp(item)">
<image class="video-help-img" :src="item.icon"></image>
<text class="video-help-title">{{ item.text }}教程</text>
</view>
</view>
</view>
<view class="group-box">
<image class="title-img" src="/static/image/index/monixiaobao.png"></image>
<view class="menu-box">
<view class="item-box" v-for="item in menuList" :key="item.name" @click="clickMenu(item)">
<view class="menu-item" :style="{ width: (windowWidth - 50) / 2 + 'px' }">
<!-- <text class="menu-item-name">{{ item.name }}</text> -->
<image class="name-img" :src="'/static/image/index/menu-name/' + item.icon + '.png'"
mode="heightFix">
</image>
<image style="width: 108rpx;height: 108rpx; flex-shrink: 0;"
:src="'/static/image/index/menu-icon/' + item.icon + '.png'"></image>
</view>
<image v-if="item.isHot" class="hot-icon" src="/static/image/index/hot-icon.png"></image>
</view>
</view>
</view>
<view class="activity-box">
<image class="alipay-year-bill" :style="{ width: (windowWidth - 32) + 'px' }"
src="/static/image/index/alipay-year-bill.png" mode="widthFix"
@click="util.goPage(`/pages/common/alipay-annual-bill/alipay-annual-bill`)"></image>
</view>
</view>
<view class="footer-box" :class="{ 'ios-padding-bottom': platform === 'ios' }">
<text class="vision-text">版本:{{ vision }}</text>
</view>
</view>
</view>
</template>
<script setup>
import {
util,
uiUtil
} from '@/utils/common.js'
import {
storage
} from '@/utils/storage.js'
import {
get
} from '@/utils/requests.js'
import {
ref,
reactive,
toRefs
} from 'vue';
import {
onLoad,
onShow
} from '@dcloudio/uni-app';
import { getCurrentInstance } from 'vue'
// 菜单列表
const menuList = [
{
icon: "yuemoni",
name: "余额模拟",
isHot: false,
path: "/pages/balance/index"
},
{
icon: "zhangdanshencheng",
name: "账单生成",
isHot: false,
path: "/pages/bill/bill-list/bill-list"
},
{
icon: "licaiheika",
name: "理财黑卡",
isHot: true,
path: ""
},
{
icon: "huabei",
name: "花呗",
isHot: false,
path: ""
},
]
const data = reactive({
statusBarHeight: 0,
windowWidth: 0,
windowHeight: 0,
userInfo: {},
videoHelpList: [],
noticeInfo: {},
vision: "1.0.0",
platform: '' // 添加平台信息
})
const {
statusBarHeight,
windowWidth,
windowHeight,
userInfo,
videoHelpList,
noticeInfo,
vision,
platform
} = toRefs(data);
onLoad(async () => {
// 启动时获取数据
fetchUserData()
// 获取平台信息
const systemInfo = uni.getSystemInfoSync()
data.platform = systemInfo.platform
})
onShow(() => {
// 每次显示时刷新数据
setUserData()
// 获取系统信息
const systemInfo = uni.getSystemInfoSync();
data.statusBarHeight = systemInfo.statusBarHeight;
data.windowWidth = systemInfo.windowWidth;
data.windowHeight = systemInfo.windowHeight;
// #ifdef APP-PLUS
util.setAndroidSystemBarColor('#F0F4F9')
// #endif
})
/**
* 获取用户数据(从服务器)
*/
const fetchUserData = async () => {
try {
// 先设置默认值,避免页面显示异常
setUserData()
// 并行获取用户信息和配置
const [userResult, configResult] = await Promise.allSettled([
fetchUserInfo(),
fetchUserConfig()
])
// 处理用户信息结果
if (userResult.status === 'fulfilled') {
console.log('用户信息获取成功')
} else {
console.error('获取用户信息失败:', userResult.reason)
}
// 处理用户配置结果
if (configResult.status === 'fulfilled') {
console.log('用户配置获取成功')
} else {
console.error('获取用户配置失败:', configResult.reason)
}
// 刷新页面数据
setUserData()
} catch (error) {
console.error('获取用户数据异常:', error)
}
}
/**
* 获取用户信息
*/
const fetchUserInfo = async () => {
const data = await get('', 'api/user', {})
if (data.code === 0) {
uni.setStorageSync('userInfo', data.data)
return data.data
} else {
throw new Error(data.message || '获取用户信息失败')
}
}
/**
* 获取用户配置
*/
const fetchUserConfig = async () => {
const data = await get('', 'api/user/config', {})
if (data.code === 0) {
uni.setStorageSync('config', data.data)
return data.data
} else {
throw new Error(data.message || '获取用户配置失败')
}
}
/**
* 设置用户数据(从本地存储读取)
*/
const setUserData = () => {
// 用户信息 - 提供默认值
const userInfoData = storage.get("userInfo")
data.userInfo = userInfoData || {
user_id: '加载中...',
avater: '/static/default-avatar.png',
vip: 0,
vip_expire: ''
}
// 配置信息 - 安全访问
const configData = storage.get("config")
if (configData && configData.config) {
data.noticeInfo = configData.config['client.uniapp.notice'] || { text: '加载中...', url: '' }
data.noticeInfo = configData.config['client.uniapp.notice'] || { text: '加载中...', url: '' }
// 启动走马灯
startMarquee();
data.videoHelpList = configData.config['client.uniapp.alipay.video_help'] || []
} else {
data.noticeInfo = { text: '加载中...', url: '' }
data.videoHelpList = []
}
}
const clickMenu = (item) => {
if (!item.path) {
uiUtil.showError('开发中')
} else {
util.goPage(item.path)
}
}
/**
* 点击视频教程
* @param item
*/
const clickVideoHelp = (item) => {
const url = item.url
util.goPage(`/pages/common/webview/webview?url=${encodeURIComponent(url)}&title=${item.text}`)
}
/**
* 点击公告
*/
const clickNotice = () => {
if (!noticeInfo.value.url) return
const url = noticeInfo.value.url + `&uni_id=${userInfo.value.user_id}`
util.goPage(`/pages/common/webview/webview?url=${encodeURIComponent(url)}&title=${noticeInfo.value.title}`)
}
/**
* 退出模拟器
*/
const exit = () => {
plus.runtime.quit()
}
const noticeContainer = ref(null);
const noticeInner = ref(null);
const noticeBox = ref(null);
// #ifndef H5
const animation = uni.requireNativePlugin('animation');
const dom = uni.requireNativePlugin('dom');
// #endif
let marqueeTimer = null;
const currentMarqueeId = ref(0);
const lastMarqueeText = ref('');
/**
* 开始走马灯
*/
const startMarquee = () => {
// 避免不必要的重置:如果文本没有变化且正在运行,则忽略
if (lastMarqueeText.value === noticeInfo.value.text && currentMarqueeId.value > 0) {
return;
}
lastMarqueeText.value = noticeInfo.value.text;
// 清除待执行的启动定时器
if (marqueeTimer) {
clearTimeout(marqueeTimer);
marqueeTimer = null;
}
// 增加 ID 以使之前的动画循环失效
currentMarqueeId.value++;
const myId = currentMarqueeId.value;
// 清除旧动画状态 (重置位置)
if (noticeInner.value) {
animation.transition(noticeInner.value, {
styles: { transform: 'translateX(0)' },
duration: 0,
delay: 0
});
}
marqueeTimer = setTimeout(() => {
if (!noticeContainer.value || !noticeInner.value) return;
// 如果ID不匹配说明有新的启动请求放弃当前
if (myId !== currentMarqueeId.value) return;
// 获取容器宽度
dom.getComponentRect(noticeContainer.value, (resContainer) => {
const containerWidth = resContainer.size.width;
if (!containerWidth) return;
// 获取文本/内部容器宽度
dom.getComponentRect(noticeInner.value, (resText) => {
const textWidth = resText.size.width / 2; // 因为复制了一份
if (!textWidth) return;
// 执行滚动
runMarqueeAnimation(containerWidth, textWidth, myId);
});
});
}, 1000); // 增加延时确保渲染
}
/**
* 执行滚动动画循环
*/
const runMarqueeAnimation = (containerWidth, textWidth, myId) => {
// ID 校验如果当前ID不匹配说明已被新动画取代停止递归
if (myId !== currentMarqueeId.value) return;
if (!noticeInner.value) return;
// 动画的目标:移动 Inner 容器
const target = noticeInner.value;
const realScrollDistance = textWidth;
// 1. 重置位置到 0 (无感重置)
animation.transition(target, {
styles: { transform: `translateX(0)` },
duration: 0,
delay: 0
}, () => {
// 再次校验ID
if (myId !== currentMarqueeId.value) return;
// 计算时间
const speed = 50; // px/s
const duration = (realScrollDistance / speed) * 1000;
// 2. 向左滚动一个文本宽度的距离
animation.transition(target, {
styles: { transform: `translateX(-${realScrollDistance}px)` },
duration: duration,
timingFunction: 'linear',
delay: 0
}, () => {
// 3. 循环
runMarqueeAnimation(containerWidth, textWidth, myId);
});
});
}
</script>
<style>
.container {
background-color: #F0F4F9;
}
.index-bg-img {
position: fixed;
top: 0;
left: 0;
z-index: 1;
}
.nav-bar-box {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background-color: transparent;
}
.content-box {
position: fixed;
top: 0rpx;
left: 0;
right: 0;
z-index: 999;
background-color: transparent;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.status-box {
width: 100%;
}
.nav-box {
height: 44px;
background-color: transparent;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.left-box {
width: 60px;
height: 44px;
background-color: transparent;
display: flex;
align-items: center;
justify-content: center;
}
.title {
flex: 1;
height: 44px;
font-size: 32rpx;
font-weight: 500;
color: #1A1A1A;
font-weight: bold;
background-color: transparent;
display: flex;
align-items: center;
justify-content: center;
}
.right-box {
width: 60px;
height: 44px;
background-color: transparent;
display: flex;
align-items: center;
justify-content: center;
}
.user-box {
position: relative;
margin: 24rpx 32rpx 0;
height: 120rpx;
z-index: 10;
}
.user-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 120rpx;
}
.user-info-box {
position: absolute;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
top: 0;
left: 0;
width: 100%;
height: 120rpx;
z-index: 1;
}
.user-info {
flex: 1;
}
.user-avatar {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
margin-right: 20rpx;
}
.name-box {
display: flex;
flex-direction: row;
align-items: center;
}
.phone-text {
font-size: 28rpx;
color: #FFFFFF;
font-weight: bold;
margin-right: 12rpx;
}
.vip-logo {
width: 60rpx;
height: 20rpx;
}
.vip-end-time {
font-size: 24rpx;
color: #FFFFFF;
margin-top: 8rpx;
}
.open-vip-btn {
height: 40rpx;
width: 116rpx;
}
.notice-box {
display: flex;
flex-direction: row;
align-items: center;
margin: 16rpx 32rpx 0;
background-color: #FFFFFF;
border-radius: 16rpx 16rpx 16rpx 16rpx;
padding: 0 16rpx;
height: 64rpx;
overflow: hidden;
}
.sound-box {
height: 64rpx;
width: 50rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 10;
}
.notice-content-wrapper {
flex: 1;
flex-direction: row;
overflow: hidden;
}
.notice-inner {
flex-direction: row;
align-items: center;
}
.notice-content {
font-size: 24rpx;
color: #767676;
}
.group-box {
margin: 32rpx;
margin-bottom: 0;
}
.title-img {
width: 140rpx;
height: 44rpx;
}
.video-help-box {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background-color: #FFFFFF;
padding: 24rpx 32rpx;
border-radius: 24rpx;
margin-top: 16rpx;
}
.video-help-item {
text-align: center;
}
.video-help-img {
width: 96rpx;
height: 96rpx;
}
.video-help-title {
font-size: 24rpx;
color: #1A1A1A;
}
.menu-box {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
margin-top: 16rpx;
}
.item-box {
position: relative;
height: 156rpx;
display: flex;
flex-direction: column;
justify-content: flex-end;
margin-bottom: 32rpx;
}
.menu-item {
display: flex;
position: relative;
flex-direction: row;
align-items: center;
justify-content: space-between;
background-color: #FFFFFF;
border-radius: 16rpx;
padding: 16rpx 32rpx;
height: 140rpx;
}
.menu-item-name {
font-weight: bold;
font-size: 32rpx;
color: #000000;
}
.name-img {
height: 28rpx;
}
.hot-icon {
position: absolute;
top: 0;
left: 0;
width: 68rpx;
height: 30rpx;
z-index: 99;
}
.activity-box {
margin: 0 32rpx;
}
.footer-box {
display: flex;
align-items: center;
justify-content: center;
margin-top: 40rpx;
margin-bottom: 10rpx;
}
.vision-text {
font-size: 24rpx;
color: #767676;
}
.ios-padding-bottom {
margin-bottom: 50rpx;
}
</style>