698 lines
14 KiB
Vue
698 lines
14 KiB
Vue
<template>
|
|
<view class="container" :style="{ height: data.windowHeight + 'px' }">
|
|
<view class="bg-container"></view>
|
|
<NavBar class="nav-bar" isRightIcon title="" :bgColor="data.navBar.bgColor" :buttonGroup="buttonGroup"
|
|
@button-click="clickTitlePopupButton">
|
|
<template v-slot:left>
|
|
<view class="nav-bar-left" @click="util.goBack()">
|
|
<image class="nav-icon" src="/static/image/nav-bar/back-white.png" mode=""></image>
|
|
<text class="nav-text">余额</text>
|
|
</view>
|
|
</template>
|
|
<template v-slot:right>
|
|
<view class="nav-bar-right mr-1">
|
|
<image class="nav-icon" src="/static/image/nav-bar/more-white.png" mode=""></image>
|
|
</view>
|
|
</template>
|
|
</NavBar>
|
|
<scroll-view class="scroll-view" :style="{ height: (data.windowHeight - 44 - data.statusBarHeight) + 'px' }"
|
|
scroll-y="true">
|
|
<view class="h100 w100 flex-between" style="flex-direction: column;">
|
|
<view class="main-container w100 flex-1">
|
|
<view class="background-container" :style="{ 'padding-top': '36rpx' }">
|
|
<view class="balance-box">
|
|
<view class="top-box">
|
|
<image class="img" src="/static/image/balance/safe-icon-blue.png" mode=""></image>
|
|
<text class="text">资金安全有保障</text>
|
|
<image class="img" src="/static/image/balance/right-blue.png" mode=""></image>
|
|
</view>
|
|
<view class="balance-title flex-cneter">
|
|
<text>可用余额(元)</text>
|
|
<image class="img" src="/static/image/balance/eye.png" mode=""></image>
|
|
</view>
|
|
<view class="balance flex-cneter" @click="editBalance">
|
|
<text class="text alipay-font">{{ Number(balance).toFixed(2) }}</text>
|
|
</view>
|
|
<view class="button-group">
|
|
<view class="flex-1 btn-box">
|
|
<view class="left btn flex-cneter">
|
|
<text class="text">提现</text>
|
|
</view>
|
|
</view>
|
|
<view class="flex-1 btn-box">
|
|
<view class="right btn flex-cneter">
|
|
<text class="text">充值</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view class="menu-box">
|
|
<view class="item menu-icon-box" v-for="item in menuList">
|
|
<image class="menu-icon" :src="`/static/image/balance/menu-icon/${item.label}.png`" mode="">
|
|
</image>
|
|
<text class="icon-name">{{ item.name }}</text>
|
|
</view>
|
|
</view>
|
|
<view class="balance-change-detail-list">
|
|
<view class="title-box">
|
|
<view class="text font-w500" style="font-weight: 500;">余额变动明细</view>
|
|
<view class="title-right">
|
|
<text class="text">全部</text>
|
|
<image class="right-icon" src="/static/image/common/right-grey.png" mode=""></image>
|
|
</view>
|
|
</view>
|
|
<view class="item">
|
|
<BalanceList :list="changeDetailList" @longPress="onLongPress" />
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view class="footer-box">
|
|
<view class="blue-text">我的客服</view>
|
|
<view class="footer-text">余额升级服务由支付宝和网商银行提供</view>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<!-- Custom Context Menu (Positioned at touch point) -->
|
|
<view v-if="contextMenu.visible" class="context-menu-mask" @touchstart="closeContextMenu">
|
|
<view class="context-menu" :style="{ top: contextMenu.y + 'px', left: contextMenu.x + 'px' }"
|
|
@touchstart.stop>
|
|
<view class="menu-item" @click="handleEdit">编辑</view>
|
|
<view class="menu-item delete" @click="handleDelete">删除</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<!-- 输入框示例 -->
|
|
<uni-popup ref="inputDialog" type="dialog">
|
|
<uni-popup-dialog before-close mode="input" title="修改余额" @confirm="dialogInputConfirm"
|
|
@close="dialogInputCancle">
|
|
<uni-easyinput type="digit" v-model="data.balance" focus placeholder="请输入余额"></uni-easyinput>
|
|
</uni-popup-dialog>
|
|
</uni-popup>
|
|
</template>
|
|
|
|
<script setup>
|
|
import NavBar from '@/components/nav-bar/nav-bar'
|
|
import BalanceList from '@/components/balance-list/balance-list.vue'
|
|
import { fastEntranceList } from '@/static/json/initial.json'
|
|
|
|
import {
|
|
util,
|
|
deviceUtil
|
|
} from '@/utils/common';
|
|
import {
|
|
storage
|
|
} from '@/utils/storage';
|
|
|
|
import {
|
|
reactive,
|
|
ref,
|
|
onMounted,
|
|
watch,
|
|
toRefs
|
|
} from 'vue'
|
|
|
|
import {
|
|
onLoad,
|
|
onShow
|
|
} from '@dcloudio/uni-app'
|
|
|
|
// 导入状态管理
|
|
import {
|
|
useStore,
|
|
} from '@/store'
|
|
|
|
import {
|
|
dateUtil
|
|
} from '@/utils/common.js'
|
|
|
|
// 获取store
|
|
const {
|
|
store,
|
|
getBillList,
|
|
deleteBill
|
|
} = useStore()
|
|
|
|
const inputDialog = ref(null)
|
|
|
|
const buttonGroup = [
|
|
{
|
|
name: "修改余额",
|
|
click: () => {
|
|
editBalance()
|
|
}
|
|
},
|
|
{
|
|
name: "快捷入口管理",
|
|
click: () => {
|
|
util.goPage('/pages/balance/fast-entrance-management/fast-entrance-management')
|
|
}
|
|
}, {
|
|
name: "添加余额变动明细",
|
|
click: () => {
|
|
util.goPage(`/pages/bill/add-bill/add-bill?type=${1}&payMethod=余额`)
|
|
}
|
|
}
|
|
]
|
|
|
|
const data = reactive({
|
|
navBar: {
|
|
bgColor: "#00000000"
|
|
},
|
|
statusBarHeight: 0,
|
|
balance: 0,
|
|
windowHeight: 0,
|
|
changeDetailList: [],
|
|
menuList: []
|
|
})
|
|
|
|
let {
|
|
balance,
|
|
changeDetailList,
|
|
menuList
|
|
} = toRefs(data)
|
|
|
|
onLoad(async () => {
|
|
// 初始获取状态栏高度和屏幕高度
|
|
updateStatusBarHeight()
|
|
data.windowHeight = await deviceUtil.getWindowHeight()
|
|
// 从缓存读取余额
|
|
const cachedBalance = storage.get('balance')
|
|
if (cachedBalance !== null) {
|
|
data.balance = cachedBalance
|
|
}
|
|
})
|
|
|
|
onShow(() => {
|
|
// #ifdef APP-PLUS
|
|
util.setAndroidSystemBarColor('#F0F3F8', "#ffffff")
|
|
// #endif
|
|
|
|
// 获取快速入口配置
|
|
data.menuList = fastEntranceData()
|
|
|
|
getRecentBills()
|
|
})
|
|
|
|
const fastEntranceData = () => {
|
|
// 优先从缓存读取
|
|
const cachedList = storage.get('fastEntranceList')
|
|
if (cachedList && cachedList.length > 0) {
|
|
return cachedList
|
|
} else {
|
|
return JSON.parse(JSON.stringify(fastEntranceList))
|
|
}
|
|
}
|
|
|
|
// 获取账单最新的三条余额相关的数据
|
|
const getRecentBills = () => {
|
|
const allBillList = getBillList()
|
|
// 按时间降序排序
|
|
const sortedList = [...allBillList].sort((a, b) => {
|
|
const timeA = a.itemInfoList.find(info => info.key == 'createTime')?.value || 0
|
|
const timeB = b.itemInfoList.find(info => info.key == 'createTime')?.value || 0
|
|
return new Date(timeB) - new Date(timeA)
|
|
})
|
|
|
|
// 获取前3条
|
|
const recentBills = sortedList.filter(item => item.payMethod == '余额').slice(0, 3)
|
|
|
|
// 转换数据
|
|
data.changeDetailList = recentBills.map(item => {
|
|
const createTime = item.itemInfoList.find(info => info.key == 'createTime')?.value || new Date()
|
|
return {
|
|
time: dateUtil.format(createTime),
|
|
type: item.itemInfoList.find(info => info.key == 'billCategory')?.value || '消费',
|
|
money: parseFloat(item.money).toFixed(2),
|
|
isAdd: item.isAdd,
|
|
name: item.name,
|
|
balance: item.balance,
|
|
imgUrl: item.imgUrl,
|
|
id: item.id
|
|
}
|
|
})
|
|
}
|
|
|
|
onMounted(() => {
|
|
updateStatusBarHeight()
|
|
})
|
|
|
|
// 更新状态栏高度
|
|
const updateStatusBarHeight = () => {
|
|
uni.getSystemInfo({
|
|
success: (res) => {
|
|
data.statusBarHeight = res.statusBarHeight || 0
|
|
console.log('直接获取状态栏高度:', data.statusBarHeight)
|
|
}
|
|
})
|
|
}
|
|
|
|
// 监听store中系统信息的变化
|
|
watch(() => store.systemInfo, (newVal) => {
|
|
if (newVal && newVal.statusBarHeight !== undefined) {
|
|
data.statusBarHeight = newVal.statusBarHeight
|
|
console.log('监听状态管理变化,更新状态栏高度:', data.statusBarHeight)
|
|
}
|
|
}, {
|
|
deep: true
|
|
})
|
|
|
|
|
|
// 点击标题弹出按钮
|
|
const clickTitlePopupButton = (button) => {
|
|
button.click()
|
|
}
|
|
|
|
//修改余额
|
|
const editBalance = () => {
|
|
data.balance = Number(data.balance).toFixed(2)
|
|
inputDialog.value.open()
|
|
}
|
|
|
|
//输入框确认事件
|
|
const dialogInputConfirm = () => {
|
|
if (data.balance > 999999999) {
|
|
uni.showToast({
|
|
title: '余额不能超过999999999',
|
|
icon: 'none'
|
|
})
|
|
data.balance = 999999999.00
|
|
}
|
|
storage.set('balance', data.balance)
|
|
inputDialog.value.close()
|
|
}
|
|
|
|
/**
|
|
* 点击关闭输入框弹窗
|
|
*/
|
|
const dialogInputCancle = () => {
|
|
data.balance = storage.get('balance')
|
|
inputDialog.value.close()
|
|
}
|
|
|
|
|
|
const contextMenu = reactive({
|
|
visible: false,
|
|
x: 0,
|
|
y: 0,
|
|
item: null
|
|
})
|
|
|
|
const onLongPress = ({
|
|
event,
|
|
item
|
|
}) => {
|
|
// 根据触摸事件计算位置
|
|
let touchX = 0
|
|
let touchY = 0
|
|
|
|
if (event.touches && event.touches.length > 0) {
|
|
touchX = event.touches[0].clientX
|
|
touchY = event.touches[0].clientY
|
|
} else if (event.changedTouches && event.changedTouches.length > 0) {
|
|
touchX = event.changedTouches[0].clientX
|
|
touchY = event.changedTouches[0].clientY
|
|
} else {
|
|
// 如果没有触摸信息,使用默认位置
|
|
touchX = 100
|
|
touchY = 100
|
|
}
|
|
|
|
// 边界检查确保菜单显示在屏幕内
|
|
const screenWidth = data.windowWidth || 375
|
|
const screenHeight = data.windowHeight || 667
|
|
|
|
if (touchX + 100 > screenWidth) {
|
|
touchX = screenWidth - 110
|
|
}
|
|
if (touchY + 90 > screenHeight) {
|
|
touchY = screenHeight - 100
|
|
}
|
|
|
|
contextMenu.x = touchX
|
|
contextMenu.y = touchY
|
|
contextMenu.item = item
|
|
contextMenu.visible = true
|
|
|
|
// 震动反馈
|
|
uni.vibrateShort()
|
|
}
|
|
|
|
/**
|
|
* 关闭上下文菜单
|
|
*/
|
|
const closeContextMenu = () => {
|
|
contextMenu.visible = false
|
|
contextMenu.item = null
|
|
}
|
|
|
|
/**
|
|
* 修改明细
|
|
*/
|
|
const handleEdit = () => {
|
|
if (contextMenu.item) {
|
|
util.goPage(`/pages/bill/add-bill/add-bill?id=${contextMenu.item.id}&type=${1}&isEdit=${true}`)
|
|
}
|
|
closeContextMenu()
|
|
}
|
|
|
|
/**
|
|
* 删除明细
|
|
*/
|
|
const handleDelete = () => {
|
|
const itemToDelete = contextMenu.item
|
|
if (itemToDelete) {
|
|
uni.showModal({
|
|
title: '提示',
|
|
content: '确定要删除这条账单吗?',
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
console.log('Deleting item:', itemToDelete)
|
|
const success = deleteBill(itemToDelete.id)
|
|
if (success) {
|
|
uni.showToast({
|
|
title: '删除成功',
|
|
icon: 'success'
|
|
})
|
|
getRecentBills()
|
|
} else {
|
|
uni.showToast({
|
|
title: '删除失败',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
closeContextMenu()
|
|
}
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
/* 直接在页面导入公共样式 */
|
|
@import '/common/main.css';
|
|
|
|
.container {
|
|
background-color: #F0F3F8;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.scroll-view {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
background-color: transparent;
|
|
}
|
|
|
|
.bg-container {
|
|
position: absolute;
|
|
/* z-index: -1; */
|
|
width: 100%;
|
|
height: 600rpx;
|
|
background: linear-gradient(181deg, #1E76FE 0%, rgba(30, 118, 254, 0.74) 39%, rgba(240, 243, 248, 0.84) 90%, #F0F3F8 100%);
|
|
|
|
}
|
|
|
|
.flex-cneter {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.flex-1 {
|
|
flex: 1;
|
|
}
|
|
|
|
.nav-bar {
|
|
position: absolute;
|
|
width: 100%;
|
|
}
|
|
|
|
.nav-bar-left {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
}
|
|
|
|
.nav-icon {
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
|
|
.nav-text {
|
|
font-size: 18px;
|
|
margin-right: 4px;
|
|
color: #FFFFFF;
|
|
height: 24px;
|
|
line-height: 24px;
|
|
}
|
|
|
|
::v-deep .uni-navbar__header-btns-left {
|
|
flex: 1;
|
|
}
|
|
|
|
::v-deep .uni-navbar__header-btns-right {
|
|
flex: 1;
|
|
}
|
|
|
|
.background-container {
|
|
padding: 12px;
|
|
padding-bottom: 0;
|
|
padding-top: 62px;
|
|
}
|
|
|
|
.balance-box {
|
|
background-color: #FFFFFF;
|
|
border-radius: 12px;
|
|
padding-bottom: 20px;
|
|
}
|
|
|
|
.top-box {
|
|
background-color: #E3EFFF;
|
|
border-radius: 12px 12px 0 0;
|
|
text-align: center;
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 6px;
|
|
}
|
|
|
|
.top-box>.text {
|
|
color: #2977E6;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.top-box>.img {
|
|
width: 14px;
|
|
height: 14px;
|
|
}
|
|
|
|
.balance-title {
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.balance-title>.img {
|
|
width: 16px;
|
|
height: 16px;
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.balance-title>.text {
|
|
font-size: 14px;
|
|
color: #1A1A1A;
|
|
}
|
|
|
|
.balance {
|
|
margin-top: 21px;
|
|
}
|
|
|
|
.balance>.text {
|
|
color: #1A1A1A;
|
|
font-size: 40px;
|
|
font-weight: 500;
|
|
line-height: 32px;
|
|
}
|
|
|
|
.button-group {
|
|
margin: 0 12px;
|
|
margin-top: 60px;
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
/* box-sizing: border-box; */
|
|
}
|
|
|
|
.btn-box {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.btn {
|
|
border-radius: 24px;
|
|
height: 49px;
|
|
width: 150px;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.btn>.text {
|
|
color: #1A1A1A;
|
|
}
|
|
|
|
.left {
|
|
border: 1px solid #E2E2E2;
|
|
color: #1A1A1A;
|
|
}
|
|
|
|
.right {
|
|
color: #fff;
|
|
background-color: #1777FF;
|
|
}
|
|
|
|
.right>.text {
|
|
color: #fff;
|
|
}
|
|
|
|
.menu-box {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-top: 8px;
|
|
border-radius: 12px;
|
|
background-color: #FFFFFF;
|
|
padding: 20px 0;
|
|
margin-left: 12px;
|
|
margin-right: 12px;
|
|
overflow: hidden;
|
|
overflow-x: scroll;
|
|
|
|
.menu-icon-box {
|
|
width: 20%;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
&::-webkit-scrollbar {
|
|
display: none;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
}
|
|
|
|
.item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.menu-icon {
|
|
width: 48rpx;
|
|
height: 48rpx;
|
|
}
|
|
|
|
.icon-name {
|
|
font-size: 24rpx;
|
|
color: var(--text-color);
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.balance-change-detail-list {
|
|
border-radius: 12px;
|
|
margin-top: 8px;
|
|
margin-left: 12px;
|
|
margin-right: 12px;
|
|
background-color: #FFFFFF;
|
|
}
|
|
|
|
.title-box {
|
|
display: flex;
|
|
flex-direction: row;
|
|
padding: 13px 12px;
|
|
justify-content: space-between;
|
|
box-shadow: 0 0.3px 0 0 #F0F0EE;
|
|
}
|
|
|
|
.title-box>.text {
|
|
font-size: 14px;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.title-right {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-direction: row;
|
|
}
|
|
|
|
.title-right>.text {
|
|
font-size: 14px;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.right-icon {
|
|
width: 10px;
|
|
height: 10px;
|
|
margin-left: 5px;
|
|
}
|
|
|
|
.footer-box {
|
|
margin-top: 18px;
|
|
text-align: center;
|
|
margin-bottom: 17px;
|
|
}
|
|
|
|
.blue-text {
|
|
color: #507295;
|
|
}
|
|
|
|
.footer-text {
|
|
font-size: 13px;
|
|
color: var(--footer-text-color);
|
|
margin-top: 18px;
|
|
}
|
|
|
|
/* Context Menu Styles */
|
|
.context-menu-mask {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
z-index: 999;
|
|
/* transparent mask to catch clicks */
|
|
}
|
|
|
|
.context-menu {
|
|
position: fixed;
|
|
background-color: rgba(255, 255, 255, 0.95);
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
padding: 4px 0;
|
|
min-width: 100px;
|
|
z-index: 1000;
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.menu-item {
|
|
padding: 10px 16px;
|
|
font-size: 14px;
|
|
color: #333;
|
|
text-align: center;
|
|
border-bottom: 0.5px solid rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.menu-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.menu-item:active {
|
|
background-color: rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.menu-item.delete {
|
|
color: #ff3b30;
|
|
}
|
|
</style> |