alipay-emulator/pages/bill/bill-list/bill-list.vue

704 lines
16 KiB
Vue

<template>
<view style="overflow: hidden;height: 100vh;;">
<navBar isRightIcon :bgColor="data.navBar.bgColor" :buttonGroup="data.navBar.buttonGroup"
@button-click="clickTitlePopupButton">
<template v-slot:center>
<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>
</template>
</navBar>
<view class="filter-box">
<view class="filter-line">
<view class="filter-item w100" :class="{ 'active-text': item.value == currentFilterType }"
v-for="item in filterType" :key="item.value">
{{ item.name }}
</view>
<view style="width: 100rpx; flex-shrink: 0;"></view>
</view>
<view class="filter-button blur"></view>
<view class="filter-button">
筛选
<image class="filter-icon" src="/static/image/bill/bill-list/down-black.png" mode=""></image>
</view>
</view>
<view style="height: 48px;"></view>
<scroll-view :scroll-y="true" class="bill-list-container"
:style="{ height: `calc(100vh - ${92 + data.statusBarHeight}px)` }" @scroll="scrollList">
<view class="sticky-conatianer" v-if="data.currentScrollTop > 100">
<view class="month">
<text class="font-w500">{{ currentMonthData.month }}</text>月
<image class="down" src="/static/image/bill/bill-list/down-black.png" mode=""></image>
</view>
<view class="income-ande-outCome flex-between">
<view class="flex">
<view class="item"><text>支出¥</text><text class="money wx-font-regular">{{
Number(currentMonthData.outCome).toFixed(2) }}</text></view>
<view class="item"><text>收入¥</text><text class="money wx-font-regular">{{
Number(currentMonthData.inCome).toFixed(2) }}</text></view>
</view>
<view class="">
<text>收支分析</text>
<uni-icons type="right" color="#555555" size="14"></uni-icons>
</view>
</view>
</view>
<view class="bill-list-card" v-for="(item, index) in billList" :key="index"
:ref="el => cardRefs[index] = el">
<view class="list-title-card"
:class="{ 'current-month': item.month == new Date().getMonth() + 1 && item.year == new Date().getFullYear() }">
<view class="list-title">
<text class="month alipay-font">{{ item.month }}</text>
<text>月</text>
<image class="filter-icon down " src="/static/image/bill/bill-list/down-black.png" mode="">
</image>
</view>
<view class="flex-between analysis-box">
<view class="income-ande-outCome">
<view class="income item">
<text class="title">收入</text>
<text class="amount alipay-font"><text class="font-11 wx-font-regular">¥</text>{{
Number(item.inCome).toFixed(2)
}}</text>
</view>
<view class="outCome item">
<text class="title">支出</text>
<text class="amount alipay-font"><text class="font-11 wx-font-regular">¥</text>{{
Number(item.outCome).toFixed(2)
}}</text>
</view>
</view>
<view v-if="!(item.month == new Date().getMonth() + 1 && item.year == new Date().getFullYear())"
class="analysis-button">
收支分析
</view>
</view>
<view v-if="item.month == new Date().getMonth() + 1 && item.year == new Date().getFullYear()"
class="income-ande-outCome-analysis">
<view class="left-box">
<text class="text">设置支出预算</text>
<image class="right-icon" src="/static/image/common/right-black.png" mode=""></image>
</view>
<view class="analysis-button">
收支分析
</view>
</view>
</view>
<view class="bill-list">
<BalanceList :isBalance="false" :list="item.list" />
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import navBar from '@/components/nav-bar/nav-bar.vue'
import BalanceList from '@/components/balance-list/balance-list.vue'
import {
reactive,
toRefs,
ref,
onMounted
} from 'vue'
import {
onShow,
} from '@dcloudio/uni-app'
// 存储卡片的 ref
const cardRefs = ref([])
/**
* 筛选类型
*/
const filterType = [{
name: '全部',
value: 'all'
}, {
name: '支出',
value: 'income'
}, {
name: '转账',
value: 'transfer'
}, {
name: '退款',
value: 'refund'
}, {
name: '订单',
value: 'order'
}, {
name: '还款',
value: 'repayment'
}, {
name: '线下消费',
value: 'offline-consumption'
}, {
name: '充值缴费',
value: 'recharge'
}, {
name: '网购',
value: 'online-shopping'
}, {
name: '二维码收款',
value: 'qr-code-receiving'
}]
const buttonGroup = [{
name: "新增账单",
click: () => {
uni.navigateTo({
url: '/pages/bill/add-bill/add-bill'
})
console.log("新增账单")
}
}
]
const data = reactive({
navBar: {
bgColor: '#F5F5F5',
buttonGroup: buttonGroup
},
statusBarHeight: 0,
currentScrollTop: 0,
currentFilterType: 'all', // 当前筛选类型
currentMonthData: {
month: '',
year: '',
inCome: 0,
outCome: 0,
}, // 当前滚动到的月份
cardPositions: [], // 存储每个卡片距离 scroll-view 顶部的距离
billList: [{
year: '2025',
month: '12',
inCome: 999999999.00,
outCome: 999999999.00,
list: [{
orderId: '1234567890',
imgUrl: 'https://picsum.photos/200/200?random=1',
name: '测试',
amount: 999999999.00,
classification: '日荣百货',
isRefund: false,
isAdd: false,
money: "200",
time: '2025-12-27 18:18:18'
}, {
orderId: '1234567890',
imgUrl: 'https://picsum.photos/200/200?random=1',
name: '测试',
amount: 999999999.00,
classification: '日荣百货',
isRefund: true,
isAdd: false,
money: "200",
time: '2025-12-27 18:18:18'
}, {
orderId: '1234567890',
imgUrl: 'https://picsum.photos/200/200?random=1',
name: '测试',
amount: 999999999.00,
classification: '日荣百货',
isRefund: false,
isAdd: true,
money: "200",
time: '2025-12-27 18:18:18'
}]
}, {
year: '2025',
month: '11',
inCome: 999999999.00,
outCome: 999999999.00,
list: [{
orderId: '1234567890',
imgUrl: 'https://picsum.photos/200/200?random=1',
name: '测试',
amount: 999999999.00,
classification: '日荣百货',
isRefund: false,
isAdd: false,
money: "200",
time: '2025-12-27 18:18:18'
}, {
orderId: '1234567890',
imgUrl: 'https://picsum.photos/200/200?random=1',
name: '测试',
amount: 999999999.00,
classification: '日荣百货',
isRefund: true,
isAdd: false,
money: "200",
time: '2025-12-27 18:18:18'
}, {
orderId: '1234567890',
imgUrl: 'https://picsum.photos/200/200?random=1',
name: '测试',
amount: 999999999.00,
classification: '日荣百货',
isRefund: false,
isAdd: true,
money: "200",
time: '2025-12-27 18:18:18'
}]
}, {
year: '2025',
month: '10',
inCome: 999999999.00,
outCome: 999999999.00,
list: [{
orderId: '1234567890',
imgUrl: 'https://picsum.photos/200/200?random=1',
name: '测试',
amount: 999999999.00,
classification: '日荣百货',
isRefund: false,
isAdd: false,
money: "200",
time: '2025-12-27 18:18:18'
}, {
orderId: '1234567890',
imgUrl: 'https://picsum.photos/200/200?random=1',
name: '测试',
amount: 999999999.00,
classification: '日荣百货',
isRefund: true,
isAdd: false,
money: "200",
time: '2025-12-27 18:18:18'
}, {
orderId: '1234567890',
imgUrl: 'https://picsum.photos/200/200?random=1',
name: '测试',
amount: 999999999.00,
classification: '日荣百货',
isRefund: false,
isAdd: true,
money: "200",
time: '2025-12-27 18:18:18'
}]
}, {
year: '2025',
month: '9',
inCome: 999999999.00,
outCome: 999999999.00,
list: [{
orderId: '1234567890',
imgUrl: 'https://picsum.photos/200/200?random=1',
name: '测试',
amount: 999999999.00,
classification: '日荣百货',
isRefund: false,
isAdd: false,
money: "200",
time: '2025-12-27 18:18:18'
}, {
orderId: '1234567890',
imgUrl: 'https://picsum.photos/200/200?random=1',
name: '测试',
amount: 999999999.00,
classification: '日荣百货',
isRefund: true,
isAdd: false,
money: "200",
time: '2025-12-27 18:18:18'
}, {
orderId: '1234567890',
imgUrl: 'https://picsum.photos/200/200?random=1',
name: '测试',
amount: 999999999.00,
classification: '日荣百货',
isRefund: false,
isAdd: true,
money: "200",
time: '2025-12-27 18:18:18'
}]
}]
})
let {
billList,
currentMonthData,
currentFilterType
} = toRefs(data)
onShow(() => {
// 初始获取状态栏高度和屏幕高度
updateStatusBarHeight()
})
onMounted(() => {
// 在组件挂载后获取卡片位置
getCardPositions()
})
// 更新状态栏高度
const updateStatusBarHeight = () => {
uni.getSystemInfo({
success: (res) => {
data.statusBarHeight = res.statusBarHeight || 0
console.log('直接获取状态栏高度:', data.statusBarHeight)
}
})
}
/**
* 获取所有卡片相对于 scroll-view 顶部的距离
*/
const getCardPositions = () => {
uni.createSelectorQuery().select('.bill-list-container').boundingClientRect(scrollViewRect => {
uni.createSelectorQuery().selectAll('.bill-list-card').boundingClientRect(cardRects => {
if (cardRects && cardRects.length > 0 && scrollViewRect) {
// 计算每个卡片相对于 scroll-view 顶部的距离
data.cardPositions = cardRects.map(rect => rect.top - scrollViewRect.top)
console.log('卡片位置数据:', data.cardPositions)
}
}).exec()
}).exec()
}
/**
* 滚动列表
*/
const scrollList = (e) => {
data.currentScrollTop = e.detail.scrollTop
console.log("滚动高度", data.currentScrollTop)
// 使用卡片位置数据
if (data.cardPositions && data.cardPositions.length > 0) {
// 找出当前可见的卡片(示例)
const visibleCards = data.cardPositions.map((position, index) => ({
index,
position,
isVisible: position <= data.currentScrollTop + data.statusBarHeight + 100
}))
console.log("可见卡片:", visibleCards)
// 找出当前最顶部的卡片索引
const currentCardIndex = data.cardPositions.findIndex(position =>
position > data.currentScrollTop
)
console.log("当前最顶部卡片索引:", currentCardIndex)
// 获取当前最顶部卡片的月份
let currentMonthIndex = currentCardIndex
if (currentCardIndex === -1) {
// 所有卡片都在视口上方,取最后一个卡片
currentMonthIndex = data.billList.length - 1
} else if (currentCardIndex > 0) {
// 当前视口最顶部的卡片是currentCardIndex - 1
currentMonthIndex = currentCardIndex - 1
}
// 设置当前滚动到的月份
if (currentMonthIndex >= 0 && currentMonthIndex < data.billList.length) {
data.currentMonthData.month = parseInt(data.billList[currentMonthIndex].month)
data.currentMonthData.year = data.billList[currentMonthIndex].year
data.currentMonthData.inCome = data.billList[currentMonthIndex].inCome
data.currentMonthData.outCome = data.billList[currentMonthIndex].outCome
console.log("当前滚动到的月份:", data.currentMonthData)
}
}
}
/**
* 点击标题弹出按钮
* @param e
*/
const clickTitlePopupButton = (button) => {
button.click()
}
</script>
<style>
@import "@/common/main.css";
page {
background-color: #F5F5F5;
}
</style>
<style lang="less" scoped>
.nav-bar-search {
background-color: #ffffff;
border-radius: 4px;
height: 32px;
padding: 4px 0;
.search-icon {
width: 32rpx;
height: 32rpx;
margin-left: 4px;
margin-right: 6px;
}
.search-input {
font-size: 14px;
}
::v-deep .input-placeholder {
color: var(--text-secondary);
}
.line {
width: 1px;
background-color: var(--border-color);
}
.search-button {
font-size: 14px;
padding: 0 12px;
color: var(--text-secondary);
font-weight: 500;
}
}
::v-deep .uni-navbar__header-btns-right {
width: auto !important;
}
::v-deep .uni-navbar__header-btns-left {
width: auto !important;
}
.filter-box {
position: fixed;
height: 48px;
width: 100%;
z-index: 1;
background-color: #F5F5F5;
.filter-button {
display: flex;
align-items: center;
justify-content: center;
width: 120rpx;
height: 28px;
position: absolute;
top: 10px;
right: 0;
}
.blur {
background-color: #F5F5F5;
filter: blur(5px);
}
}
.filter-icon {
width: 24rpx;
height: 24rpx;
}
.filter-line {
display: flex;
flex-wrap: nowrap;
margin: 0 12px;
margin-top: 10px;
overflow: hidden;
overflow-x: scroll;
.filter-item {
background-color: #ffffff;
color: var(--text-primary);
font-size: 14px;
padding: 4px 16px;
width: auto;
border-radius: 14px;
margin-right: 8px;
white-space: nowrap;
flex-shrink: 0;
}
.active-text {
color: #1B71F8;
}
}
.bill-list-container {
position: relative;
// padding: 0 12px;
flex: 1;
padding-bottom: 12px;
.sticky-conatianer {
position: fixed;
width: 100%;
background-color: #F5F5F5;
padding: 12px;
z-index: 1;
.month {
display: flex;
align-items: center;
font-size: 13px;
.down {
width: 16px;
height: 16px;
margin-left: 4px;
}
}
.income-ande-outCome {
display: flex;
font-size: 13px;
margin-top: 6px;
.item {
margin-right: 16px;
.money {
font-size: 12px;
margin-left: 2px;
}
}
}
}
}
.bill-list-card {
border-radius: 8px;
margin: 0 12px;
margin-bottom: 12px;
background-color: #FFFFFF;
.list-title-card {
background-color: #FFFFFF;
border-radius: 8px 8px 0 0;
padding: 10px 12px;
.month {
font-size: 32px;
color: var(--text-primary);
font-weight: 500;
}
.down {
margin-left: 3px;
}
.income-ande-outCome {
margin-top: 8px;
display: flex;
.item {
display: flex;
flex-direction: column;
min-width: 80px;
max-width: 120px;
margin-right: 10px;
.font-11 {
font-size: 12px;
margin-right: 4px;
}
}
.title {
font-size: 12px;
color: var(--text-secondary);
}
.amount {
font-size: 16px;
color: var(--text-primary);
font-weight: 500;
}
}
.analysis-box {
align-items: flex-end;
.analysis-button {
padding: 8px 12px;
background: linear-gradient(90deg, #187AFF 0%, #3295FC 100%);
border-radius: 16px 16px 16px 16px;
color: #FFFFFF;
font-size: 12px;
line-height: 16px;
height: 32px;
flex-shrink: 0;
}
}
}
.current-month {
padding: 10px 12px 18px;
background: url('/static/image/bill/bill-list/current-month-bill-bg.png') no-repeat center center;
background-size: 100% 100%;
.income-ande-outCome {
margin-top: 8px;
display: flex;
.item {
display: flex;
flex-direction: column;
min-width: 80px;
max-width: 120px;
margin-right: 10px;
.font-11 {
font-size: 12px;
margin-right: 4px;
}
}
.title {
font-size: 12px;
color: var(--text-secondary);
}
.amount {
font-size: 16px;
color: var(--text-primary);
font-weight: 500;
}
}
.income-ande-outCome-analysis {
margin-top: 3px;
display: flex;
justify-content: space-between;
.left-box {
display: flex;
align-items: flex-end;
margin-bottom: 3px;
.text {
font-size: 13px;
line-height: 14px;
color: var(--text-primary);
}
}
.right-icon {
width: 14px;
height: 14px;
margin-left: 5px;
}
.analysis-button {
padding: 8px 12px;
background: linear-gradient(90deg, #187AFF 0%, #3295FC 100%);
border-radius: 16px 16px 16px 16px;
color: #FFFFFF;
font-size: 12px;
line-height: 16px;
}
}
}
}
</style>