feat: 迭代三大模块 + 全系统 Bug 修复
新功能: - 账单筛选统计:多维度筛选面板 + 保存方案 + 结果统计 - 通知公告:系统公告/群组通知/个人提醒 + 通知中心页面 - 管理员后台:数据看板 + 用户管理 + 发布公告 Bug 修复: - 统计页 fetchPrevMonthData/fetchMaxSingle 添加 group_id - 群组视图添加 t.group_id 过滤,防止私人数据泄露 - 通知列表添加群组通知条件 - 通知标记已读添加错误处理 - 管理员 API 添加 affectedRows 检查 - 筛选面板金额为 0 时正确回填 - profile onShow 补充 userStore.fetchUserInfo - 全系统样式修复(overflow/box-sizing)
This commit is contained in:
@@ -11,8 +11,22 @@
|
||||
<view class="tab" :class="{ active: filterType === 'expense' }" @tap="switchFilter('expense')">支出</view>
|
||||
<view class="tab" :class="{ active: filterType === 'income' }" @tap="switchFilter('income')">收入</view>
|
||||
</view>
|
||||
<view class="filter-btn" :class="{ active: hasAdvancedFilter }" @tap="showFilter = true">
|
||||
<Icon name="filter" :size="28" :color="hasAdvancedFilter ? '#FF8C69' : '#8B7E7E'" />
|
||||
<text class="filter-btn-text">筛选</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选结果统计 -->
|
||||
<view class="filter-stats" v-if="hasAdvancedFilter && !loading">
|
||||
<text class="filter-stats-text">共 {{ total }} 笔</text>
|
||||
<text class="filter-stats-text" v-if="filteredExpense > 0">支出 {{ formatAmount(filteredExpense) }}</text>
|
||||
<text class="filter-stats-text" v-if="filteredIncome > 0">收入 {{ formatAmount(filteredIncome) }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 筛选面板 -->
|
||||
<FilterPanel v-if="showFilter" :initialFilters="advancedFilters" @close="showFilter = false" @apply="onFilterApply" />
|
||||
|
||||
<view class="tx-list" v-if="!txStore.loading || txList.length > 0">
|
||||
<view v-for="group in groupedTx" :key="group.date" class="date-group">
|
||||
<view class="group-header">
|
||||
@@ -81,8 +95,10 @@ import { waitForReady } from '@/utils/app-ready'
|
||||
import TransactionItem from '@/components/TransactionItem/TransactionItem.vue'
|
||||
import Icon from '@/components/Icon/Icon.vue'
|
||||
import Skeleton from '@/components/Skeleton/Skeleton.vue'
|
||||
import FilterPanel from './filter-panel.vue'
|
||||
import { statusBarHeight } from '@/utils/system'
|
||||
import { formatAmount, formatDate } from '@/utils/format'
|
||||
import type { FilterParams } from '@/api/filter'
|
||||
|
||||
const txStore = useTransactionStore()
|
||||
const userStore = useUserStore()
|
||||
@@ -95,6 +111,16 @@ const total = ref(0)
|
||||
const loadError = ref(false)
|
||||
let loadSeq = 0 // 请求序号,防止并发覆盖
|
||||
|
||||
// 高级筛选
|
||||
const showFilter = ref(false)
|
||||
const advancedFilters = ref<FilterParams>({})
|
||||
const filteredExpense = ref(0)
|
||||
const filteredIncome = ref(0)
|
||||
const hasAdvancedFilter = computed(() => {
|
||||
const f = advancedFilters.value
|
||||
return !!(f.category_ids?.length || f.startDate || f.endDate || f.minAmount || f.maxAmount || f.keyword)
|
||||
})
|
||||
|
||||
const noMore = computed(() => txList.value.length >= total.value && total.value > 0)
|
||||
|
||||
const groupedTx = computed(() => {
|
||||
@@ -138,9 +164,17 @@ async function loadTx(reset = false, silent = false) {
|
||||
const seq = ++loadSeq
|
||||
const params: any = { page: page.value, pageSize }
|
||||
if (filterType.value !== 'all') params.type = filterType.value
|
||||
// 合并高级筛选参数
|
||||
const af = advancedFilters.value
|
||||
if (af.category_ids?.length === 1) params.category_id = af.category_ids[0]
|
||||
if (af.startDate) params.startDate = af.startDate
|
||||
if (af.endDate) params.endDate = af.endDate
|
||||
if (af.minAmount) params.minAmount = af.minAmount
|
||||
if (af.maxAmount) params.maxAmount = af.maxAmount
|
||||
if (af.keyword) params.keyword = af.keyword
|
||||
try {
|
||||
loadError.value = false
|
||||
await txStore.fetchTransactions(params)
|
||||
const data = await txStore.fetchTransactions(params)
|
||||
// 丢弃过期请求的结果
|
||||
if (seq !== loadSeq) return
|
||||
if (reset) {
|
||||
@@ -149,6 +183,10 @@ async function loadTx(reset = false, silent = false) {
|
||||
txList.value = [...txList.value, ...txStore.transactions]
|
||||
}
|
||||
total.value = txStore.total
|
||||
if (data) {
|
||||
filteredExpense.value = data.filteredExpense || 0
|
||||
filteredIncome.value = data.filteredIncome || 0
|
||||
}
|
||||
} catch (e) {
|
||||
if (seq !== loadSeq) return
|
||||
console.error('Load bills error:', e)
|
||||
@@ -156,6 +194,12 @@ async function loadTx(reset = false, silent = false) {
|
||||
}
|
||||
}
|
||||
|
||||
function onFilterApply(filters: FilterParams) {
|
||||
advancedFilters.value = filters
|
||||
showFilter.value = false
|
||||
loadTx(true)
|
||||
}
|
||||
|
||||
function onEdit(item: any) {
|
||||
if (groupStore.isGroupMode && item.user_id !== userStore.userInfo?.id) {
|
||||
uni.showToast({ title: '只能编辑自己的记录', icon: 'none' })
|
||||
@@ -227,7 +271,13 @@ onPullDownRefresh(() => {
|
||||
padding: 16rpx 0 24rpx;
|
||||
}
|
||||
|
||||
.tabs-wrap { display: flex; justify-content: center; margin: 0 0 32rpx; }
|
||||
.tabs-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 0 24rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: inline-flex;
|
||||
@@ -251,6 +301,41 @@ onPullDownRefresh(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
background: #FFF0E6;
|
||||
border-radius: 20rpx;
|
||||
border: 2rpx solid #F0E0D6;
|
||||
|
||||
&.active {
|
||||
border-color: #FF8C69;
|
||||
background: #FFE8E0;
|
||||
}
|
||||
|
||||
&:active { opacity: 0.7; }
|
||||
}
|
||||
|
||||
.filter-btn-text {
|
||||
font-size: 24rpx;
|
||||
color: #8B7E7E;
|
||||
.active & { color: #FF8C69; }
|
||||
}
|
||||
|
||||
.filter-stats {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding: 0 40rpx 16rpx;
|
||||
}
|
||||
|
||||
.filter-stats-text {
|
||||
font-size: 24rpx;
|
||||
font-family: 'Fredoka', sans-serif;
|
||||
color: #8B7E7E;
|
||||
}
|
||||
|
||||
.tx-list { padding: 0 40rpx; }
|
||||
|
||||
.date-group {
|
||||
|
||||
Reference in New Issue
Block a user