fix: 修复账单列表分页bug并优化日期和图标显示

- 修复分页并发加载bug,防止重复请求同一页数据
- 优化日期格式显示,从 HH:mm 改为 MM-DD HH:mm
- 集成分类真实图标,支持 Iconify 格式,替换默认五角星图标
- 添加图标加载降级机制,确保兼容性
This commit is contained in:
SunCheng
2026-02-18 21:14:54 +08:00
parent 61aa19b3d2
commit a21c533ba5
2 changed files with 110 additions and 20 deletions

View File

@@ -104,7 +104,16 @@
class="card-icon" class="card-icon"
:style="{ backgroundColor: getIconBg(transaction.type) }" :style="{ backgroundColor: getIconBg(transaction.type) }"
> >
<!-- 使用 Iconify 图标格式collection:name -->
<Icon
v-if="isIconifyFormat(getIconByClassify(transaction.classify))"
:icon-identifier="getIconByClassify(transaction.classify)"
:color="getIconColor(transaction.type)"
size="20"
/>
<!-- 降级使用 Vant 图标 -->
<van-icon <van-icon
v-else
:name="getIconByClassify(transaction.classify)" :name="getIconByClassify(transaction.classify)"
:color="getIconColor(transaction.type)" :color="getIconColor(transaction.type)"
size="20" size="20"
@@ -222,6 +231,8 @@
import { ref, computed, watch, onMounted } from 'vue' import { ref, computed, watch, onMounted } from 'vue'
import { showConfirmDialog, showToast } from 'vant' import { showConfirmDialog, showToast } from 'vant'
import { getTransactionList, deleteTransaction } from '@/api/transactionRecord' import { getTransactionList, deleteTransaction } from '@/api/transactionRecord'
import { getCategoryList } from '@/api/transactionCategory'
import Icon from '@/components/Icon.vue'
/** /**
* @typedef {Object} Transaction * @typedef {Object} Transaction
@@ -288,6 +299,10 @@ const finished = ref(false)
const page = ref(1) const page = ref(1)
const pageSize = ref(20) const pageSize = ref(20)
// 分类列表及图标映射
const categories = ref([]) // 所有分类列表
const categoryIconMap = ref({}) // 分类名称 -> 图标的映射
// 筛选状态管理 // 筛选状态管理
const selectedType = ref(null) // null=全部, 0=支出, 1=收入, 2=不计入 const selectedType = ref(null) // null=全部, 0=支出, 1=收入, 2=不计入
const selectedCategory = ref(null) // null=全部 const selectedCategory = ref(null) // null=全部
@@ -326,7 +341,9 @@ const showCalendar = ref(false)
const dateDropdown = ref(null) const dateDropdown = ref(null)
const dateRangeText = computed(() => { const dateRangeText = computed(() => {
if (!dateRange.value) {return '选择日期范围'} if (!dateRange.value) {
return '选择日期范围'
}
return `${dateRange.value[0]}${dateRange.value[1]}` return `${dateRange.value[0]}${dateRange.value[1]}`
}) })
@@ -424,6 +441,12 @@ const displayTransactions = computed(() => {
// 5.3 根据分类获取图标 // 5.3 根据分类获取图标
const getIconByClassify = (classify) => { const getIconByClassify = (classify) => {
// 优先使用从API加载的分类图标
if (categoryIconMap.value[classify]) {
return categoryIconMap.value[classify]
}
// 降级:使用本地映射(向后兼容)
const iconMap = { const iconMap = {
餐饮: 'food-o', 餐饮: 'food-o',
购物: 'shopping-cart-o', 购物: 'shopping-cart-o',
@@ -437,32 +460,53 @@ const getIconByClassify = (classify) => {
return iconMap[classify || ''] || 'star-o' return iconMap[classify || ''] || 'star-o'
} }
// 判断是否为 Iconify 格式collection:name
const isIconifyFormat = (icon) => {
return icon && icon.includes(':')
}
// 5.3 根据类型获取图标背景色 // 5.3 根据类型获取图标背景色
const getIconBg = (type) => { const getIconBg = (type) => {
if (type === 0) {return '#FEE2E2'} // 支出 - 浅红色 if (type === 0) {
if (type === 1) {return '#D1FAE5'} // 收入 - 浅绿色 return '#FEE2E2'
} // 支出 - 浅红色
if (type === 1) {
return '#D1FAE5'
} // 收入 - 浅绿色
return '#E5E7EB' // 不计入 - 灰色 return '#E5E7EB' // 不计入 - 灰色
} }
// 5.3 根据类型获取图标颜色 // 5.3 根据类型获取图标颜色
const getIconColor = (type) => { const getIconColor = (type) => {
if (type === 0) {return '#EF4444'} // 支出 - 红色 if (type === 0) {
if (type === 1) {return '#10B981'} // 收入 - 绿色 return '#EF4444'
} // 支出 - 红色
if (type === 1) {
return '#10B981'
} // 收入 - 绿色
return '#6B7280' // 不计入 - 灰色 return '#6B7280' // 不计入 - 灰色
} }
// 5.4 格式化金额 // 5.4 格式化金额
const formatAmount = (amount, type) => { const formatAmount = (amount, type) => {
const formatted = `¥${Number(amount).toFixed(2)}` const formatted = `¥${Number(amount).toFixed(2)}`
if (type === 0) {return `- ${formatted}`} if (type === 0) {
if (type === 1) {return `+ ${formatted}`} return `- ${formatted}`
}
if (type === 1) {
return `+ ${formatted}`
}
return formatted return formatted
} }
// 5.4 获取金额样式类 // 5.4 获取金额样式类
const getAmountClass = (type) => { const getAmountClass = (type) => {
if (type === 0) {return 'amount-expense'} if (type === 0) {
if (type === 1) {return 'amount-income'} return 'amount-expense'
}
if (type === 1) {
return 'amount-income'
}
return 'amount-neutral' return 'amount-neutral'
} }
@@ -478,32 +522,46 @@ const getTypeName = (type) => {
// 5.5 获取类型标签类型 // 5.5 获取类型标签类型
const getTypeTagType = (type) => { const getTypeTagType = (type) => {
if (type === 0) {return 'danger'} if (type === 0) {
if (type === 1) {return 'success'} return 'danger'
}
if (type === 1) {
return 'success'
}
return 'default' return 'default'
} }
// 5.5 获取分类标签样式类 // 5.5 获取分类标签样式类
const getClassifyTagClass = (type) => { const getClassifyTagClass = (type) => {
if (type === 0) {return 'tag-expense'} if (type === 0) {
if (type === 1) {return 'tag-income'} return 'tag-expense'
}
if (type === 1) {
return 'tag-income'
}
return 'tag-neutral' return 'tag-neutral'
} }
// 5.6 格式化时间 // 5.6 格式化时间
const formatTime = (dateString) => { const formatTime = (dateString) => {
if (!dateString) {return ''} if (!dateString) {
return ''
}
const date = new Date(dateString) const date = new Date(dateString)
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0') const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0') const minutes = String(date.getMinutes()).padStart(2, '0')
return `${hours}:${minutes}` return `${month}-${day} ${hours}:${minutes}`
} }
// ========== API 数据加载 ========== // ========== API 数据加载 ==========
// 3.2 初始加载逻辑 // 3.2 初始加载逻辑
const fetchTransactions = async () => { const fetchTransactions = async () => {
if (props.dataSource !== 'api') {return} if (props.dataSource !== 'api') {
return
}
try { try {
loading.value = true loading.value = true
@@ -560,7 +618,9 @@ const onLoad = () => {
// 3.4 筛选条件变更时的数据重载逻辑 // 3.4 筛选条件变更时的数据重载逻辑
const resetAndReload = () => { const resetAndReload = () => {
if (props.dataSource !== 'api') {return} if (props.dataSource !== 'api') {
return
}
page.value = 1 page.value = 1
rawTransactions.value = [] rawTransactions.value = []
@@ -582,8 +642,33 @@ watch(
{ deep: true } { deep: true }
) )
// 加载分类列表及图标映射
const loadCategories = async () => {
try {
const response = await getCategoryList()
if (response && response.success) {
categories.value = response.data || []
// 构建分类名称 -> 图标的映射
const iconMap = {}
categories.value.forEach(category => {
if (category.name && category.icon) {
iconMap[category.name] = category.icon
}
})
categoryIconMap.value = iconMap
}
} catch (error) {
console.error('加载分类列表失败:', error)
// 静默失败,使用降级图标
}
}
// 组件挂载时初始加载 // 组件挂载时初始加载
onMounted(() => { onMounted(() => {
// 加载分类列表(用于图标映射)
loadCategories()
if (props.dataSource === 'api') { if (props.dataSource === 'api') {
fetchTransactions() fetchTransactions()
} }
@@ -748,17 +833,17 @@ const toggleSelection = (transaction) => {
.tag-expense { .tag-expense {
background-color: rgba(239, 68, 68, 0.1); background-color: rgba(239, 68, 68, 0.1);
color: #EF4444; color: #ef4444;
} }
.tag-income { .tag-income {
background-color: rgba(16, 185, 129, 0.1); background-color: rgba(16, 185, 129, 0.1);
color: #10B981; color: #10b981;
} }
.tag-neutral { .tag-neutral {
background-color: rgba(107, 114, 128, 0.1); background-color: rgba(107, 114, 128, 0.1);
color: #6B7280; color: #6b7280;
} }
// 5.1 右侧金额区域 // 5.1 右侧金额区域

View File

@@ -74,6 +74,11 @@ let searchTimer = null
// 加载数据 // 加载数据
const loadData = async (isRefresh = false) => { const loadData = async (isRefresh = false) => {
// 防止并发加载:如果正在加载中且不是刷新操作,则直接返回
if (loading.value && !isRefresh) {
return
}
if (isRefresh) { if (isRefresh) {
pageIndex.value = 1 pageIndex.value = 1
transactionList.value = [] transactionList.value = []