alipay-emulator/pages/index/index.nvue

863 lines
19 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" :style="{ backgroundColor: data.navBarBgColor }">
<view class="status-box" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="nav-box">
<view class="left-box" @click="exit" @tap="exit" @touchstart.stop="exit">
<image style="width: 40rpx; height: 40rpx;" src="/static/image/nav-bar/back-black.png" @click="exit"
@tap="exit"></image>
</view>
<view class="title">小宝模拟器</view>
<view class="right-box"></view>
</view>
</view>
<view class="content-box" :style="{ height: windowHeight + 'px' }">
<scroll-view scroll-y="true"
:style="{ height: (windowHeight - statusBarHeight - 44) + 'px', marginTop: (statusBarHeight + 44) + 'px' }"
@scroll="handleScroll">
<view>
<!-- iOS专用透明点击区域覆盖导航栏左上角 -->
<view @click="exit" @tap="exit" :style="{
position: 'fixed',
top: statusBarHeight + 'px',
left: '0px',
width: '60px',
height: '44px',
zIndex: 10000,
backgroundColor: 'transparent'
}">
</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" @click="openVip">
<image class="open-vip-btn"
:src="`/static/image/index/${userInfo.vip > 1 ? 'vip-btn' : 'open-vip-btn'}.png`">
</image>
</view>
</view>
</view>
<view v-if="noticeInfo.enable" 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 class="group-box">
<image class="title-img" src="/static/image/index/qita.png"></image>
<view class="video-help-box">
<view class="video-help-item" v-for="item in otherList" :key="item.id"
@click="clickMenu(item)">
<image class="video-help-img" :src="item.icon"></image>
<text class="video-help-title">{{ item.name }}</text>
</view>
</view>
</view>
</view>
<view class="footer-box" :class="{ 'ios-padding-bottom': platform === 'ios' }">
<text class="vision-text">版本:{{ vision }}</text>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup>
import {
util,
uiUtil
} from '@/utils/common.js'
import {
storage
} from '@/utils/storage.js'
import {
get,
postJson
} from '@/utils/requests.js'
import {
ref,
reactive,
toRefs
} from 'vue';
import {
onLoad,
onShow
} from '@dcloudio/uni-app';
// 内部埋点方法
const apiUserEvent = async (type, adminData) => {
let uni_version = uni.getStorageSync("version")
if (type != 'uni') {
await postJson('a', 'api/user/event', {
type: adminData.type,
key: adminData.type + ".uni.alipay." + adminData.key,
value: adminData.value,
extra: JSON.stringify({
uni_version: uni_version,
...adminData.extra
}),
})
}
}
// 内部跳转充值页方法
const goRechargePage = () => {
// 进入页面
apiUserEvent('all', {
type: "event",
key: "payment_onload",
value: "进入充值页面",
extra: {
from: "支付宝模拟器首页"
}
})
uni.navigateTo({
url: '/pages/common/recharge/index'
});
}
// 菜单列表
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: "/pages/finance-management/index"
// path: ""
},
{
icon: "huabei",
name: "花呗",
isHot: false,
path: "/pages/ant-credit-pay/index"
},
]
const otherList = [{
icon: "/static/image/index/qita/jipiao.png",
name: "机票",
path: ""
},
{
icon: "/static/image/index/qita/huochepiao.png",
name: "火车票",
path: ""
},
{
icon: "/static/image/index/qita/gongzidan.png",
name: "工资单",
path: "/pages/other/splash/splash"
},
{
icon: "/static/image/index/qita/shipinqunliao.png",
name: "视频群聊",
path: "/pages/other/video-group-chat/video-group-chat"
},
]
const data = reactive({
navBarBgColor: 'transparent',
statusBarHeight: 0,
windowWidth: 0,
windowHeight: 0,
userInfo: {},
videoHelpList: [],
noticeInfo: {},
vision: "",
platform: '' // 添加平台信息
})
const {
statusBarHeight,
windowWidth,
windowHeight,
userInfo,
videoHelpList,
noticeInfo,
vision,
platform
} = toRefs(data);
/**
* 处理页面滚动事件
*/
const handleScroll = (e) => {
const scrollTop = e.detail.scrollTop
// 滚动超过20px时显示蓝色背景否则显示透明背景
if (scrollTop > 20) {
data.navBarBgColor = '#DDF0FD'
} else {
data.navBarBgColor = 'transparent'
}
}
onLoad(async () => {
// 获取平台信息
const systemInfo = uni.getSystemInfoSync()
data.platform = systemInfo.platform
data.vision = uni.getStorageSync('version')
})
onShow(() => {
// 启动时获取数据
fetchUserData()
// 每次显示时刷新数据
setUserData()
// 获取系统信息
const systemInfo = uni.getSystemInfoSync();
data.statusBarHeight = systemInfo.statusBarHeight;
data.windowWidth = systemInfo.windowWidth;
data.windowHeight = systemInfo.windowHeight;
// #ifdef APP-PLUS
util.setAndroidSystemBarColor('#F0F4F9')
setTimeout(() => {
plus.navigator.setStatusBarStyle("dark");
}, 500)
// #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) {
console.log("公告牌配置信息", configData.config['client.uniapp.notice'])
data.noticeInfo = configData.config['client.uniapp.notice'] || {
text: '加载中...',
url: '',
enable: false
}
data.noticeInfo = configData.config['client.uniapp.notice'] || {
text: '加载中...',
url: '',
enable: false
}
// 启动走马灯
startMarquee();
data.videoHelpList = configData.config['client.uniapp.alipay.video_help'] || []
} else {
data.noticeInfo = {
text: '加载中...',
url: '',
enable: false
}
data.videoHelpList = []
}
}
const clickMenu = (item) => {
if (!item.path) {
uiUtil.showError('开发中')
} else {
const isOverdue = storage.get("huabei_info_storage").isOverdue || false
let url = item.path
if (item.name == "花呗" && isOverdue) {
url = "/pages/ant-credit-pay/overdue-payment/overdue-payment"
}
util.goPage(url)
}
}
// 开通vip
const openVip = () => {
goRechargePage()
}
/**
* 点击视频教程
* @param item
*/
const clickVideoHelp = (item) => {
const url = item.url
util.goPage(`/pages/common/webview/webview?url=${encodeURIComponent(url)}&title=${item.text}`)
}
/**
* 点击公告
*/
const clickNotice = () => {
console.log("点击公告", noticeInfo.value)
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 = () => {
console.log("点击退出按钮")
// #ifdef APP-PLUS
if (typeof plus !== 'undefined' && plus.runtime) {
console.log("执行退出应用")
plus.runtime.quit()
} else {
console.log("plus对象未定义")
}
// #endif
// #ifndef APP-PLUS
console.log("非APP环境无法退出")
uni.showToast({
title: '仅APP环境支持退出',
icon: 'none'
})
// #endif
}
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: 0;
}
.nav-bar-box {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 9999;
background-color: transparent;
}
.content-box {
position: fixed;
top: 0rpx;
left: 0;
right: 0;
z-index: 1;
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;
position: relative;
z-index: 10;
}
.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;
flex-direction: column;
}
.video-help-img {
width: 96rpx;
height: 96rpx;
}
.video-help-title {
font-size: 24rpx;
color: #1A1A1A;
text-align: center;
}
.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>