704 lines
16 KiB
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> |