fix: 修复账单列表分页bug并优化日期和图标显示
- 修复分页并发加载bug,防止重复请求同一页数据 - 优化日期格式显示,从 HH:mm 改为 MM-DD HH:mm - 集成分类真实图标,支持 Iconify 格式,替换默认五角星图标 - 添加图标加载降级机制,确保兼容性
This commit is contained in:
@@ -104,7 +104,16 @@
|
||||
class="card-icon"
|
||||
: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
|
||||
v-else
|
||||
:name="getIconByClassify(transaction.classify)"
|
||||
:color="getIconColor(transaction.type)"
|
||||
size="20"
|
||||
@@ -222,6 +231,8 @@
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { showConfirmDialog, showToast } from 'vant'
|
||||
import { getTransactionList, deleteTransaction } from '@/api/transactionRecord'
|
||||
import { getCategoryList } from '@/api/transactionCategory'
|
||||
import Icon from '@/components/Icon.vue'
|
||||
|
||||
/**
|
||||
* @typedef {Object} Transaction
|
||||
@@ -288,6 +299,10 @@ const finished = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
|
||||
// 分类列表及图标映射
|
||||
const categories = ref([]) // 所有分类列表
|
||||
const categoryIconMap = ref({}) // 分类名称 -> 图标的映射
|
||||
|
||||
// 筛选状态管理
|
||||
const selectedType = ref(null) // null=全部, 0=支出, 1=收入, 2=不计入
|
||||
const selectedCategory = ref(null) // null=全部
|
||||
@@ -326,7 +341,9 @@ const showCalendar = ref(false)
|
||||
const dateDropdown = ref(null)
|
||||
|
||||
const dateRangeText = computed(() => {
|
||||
if (!dateRange.value) {return '选择日期范围'}
|
||||
if (!dateRange.value) {
|
||||
return '选择日期范围'
|
||||
}
|
||||
return `${dateRange.value[0]} 至 ${dateRange.value[1]}`
|
||||
})
|
||||
|
||||
@@ -424,6 +441,12 @@ const displayTransactions = computed(() => {
|
||||
|
||||
// 5.3 根据分类获取图标
|
||||
const getIconByClassify = (classify) => {
|
||||
// 优先使用从API加载的分类图标
|
||||
if (categoryIconMap.value[classify]) {
|
||||
return categoryIconMap.value[classify]
|
||||
}
|
||||
|
||||
// 降级:使用本地映射(向后兼容)
|
||||
const iconMap = {
|
||||
餐饮: 'food-o',
|
||||
购物: 'shopping-cart-o',
|
||||
@@ -437,32 +460,53 @@ const getIconByClassify = (classify) => {
|
||||
return iconMap[classify || ''] || 'star-o'
|
||||
}
|
||||
|
||||
// 判断是否为 Iconify 格式(collection:name)
|
||||
const isIconifyFormat = (icon) => {
|
||||
return icon && icon.includes(':')
|
||||
}
|
||||
|
||||
// 5.3 根据类型获取图标背景色
|
||||
const getIconBg = (type) => {
|
||||
if (type === 0) {return '#FEE2E2'} // 支出 - 浅红色
|
||||
if (type === 1) {return '#D1FAE5'} // 收入 - 浅绿色
|
||||
if (type === 0) {
|
||||
return '#FEE2E2'
|
||||
} // 支出 - 浅红色
|
||||
if (type === 1) {
|
||||
return '#D1FAE5'
|
||||
} // 收入 - 浅绿色
|
||||
return '#E5E7EB' // 不计入 - 灰色
|
||||
}
|
||||
|
||||
// 5.3 根据类型获取图标颜色
|
||||
const getIconColor = (type) => {
|
||||
if (type === 0) {return '#EF4444'} // 支出 - 红色
|
||||
if (type === 1) {return '#10B981'} // 收入 - 绿色
|
||||
if (type === 0) {
|
||||
return '#EF4444'
|
||||
} // 支出 - 红色
|
||||
if (type === 1) {
|
||||
return '#10B981'
|
||||
} // 收入 - 绿色
|
||||
return '#6B7280' // 不计入 - 灰色
|
||||
}
|
||||
|
||||
// 5.4 格式化金额
|
||||
const formatAmount = (amount, type) => {
|
||||
const formatted = `¥${Number(amount).toFixed(2)}`
|
||||
if (type === 0) {return `- ${formatted}`}
|
||||
if (type === 1) {return `+ ${formatted}`}
|
||||
if (type === 0) {
|
||||
return `- ${formatted}`
|
||||
}
|
||||
if (type === 1) {
|
||||
return `+ ${formatted}`
|
||||
}
|
||||
return formatted
|
||||
}
|
||||
|
||||
// 5.4 获取金额样式类
|
||||
const getAmountClass = (type) => {
|
||||
if (type === 0) {return 'amount-expense'}
|
||||
if (type === 1) {return 'amount-income'}
|
||||
if (type === 0) {
|
||||
return 'amount-expense'
|
||||
}
|
||||
if (type === 1) {
|
||||
return 'amount-income'
|
||||
}
|
||||
return 'amount-neutral'
|
||||
}
|
||||
|
||||
@@ -478,32 +522,46 @@ const getTypeName = (type) => {
|
||||
|
||||
// 5.5 获取类型标签类型
|
||||
const getTypeTagType = (type) => {
|
||||
if (type === 0) {return 'danger'}
|
||||
if (type === 1) {return 'success'}
|
||||
if (type === 0) {
|
||||
return 'danger'
|
||||
}
|
||||
if (type === 1) {
|
||||
return 'success'
|
||||
}
|
||||
return 'default'
|
||||
}
|
||||
|
||||
// 5.5 获取分类标签样式类
|
||||
const getClassifyTagClass = (type) => {
|
||||
if (type === 0) {return 'tag-expense'}
|
||||
if (type === 1) {return 'tag-income'}
|
||||
if (type === 0) {
|
||||
return 'tag-expense'
|
||||
}
|
||||
if (type === 1) {
|
||||
return 'tag-income'
|
||||
}
|
||||
return 'tag-neutral'
|
||||
}
|
||||
|
||||
// 5.6 格式化时间
|
||||
const formatTime = (dateString) => {
|
||||
if (!dateString) {return ''}
|
||||
if (!dateString) {
|
||||
return ''
|
||||
}
|
||||
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 minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${hours}:${minutes}`
|
||||
return `${month}-${day} ${hours}:${minutes}`
|
||||
}
|
||||
|
||||
// ========== API 数据加载 ==========
|
||||
|
||||
// 3.2 初始加载逻辑
|
||||
const fetchTransactions = async () => {
|
||||
if (props.dataSource !== 'api') {return}
|
||||
if (props.dataSource !== 'api') {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
@@ -560,7 +618,9 @@ const onLoad = () => {
|
||||
|
||||
// 3.4 筛选条件变更时的数据重载逻辑
|
||||
const resetAndReload = () => {
|
||||
if (props.dataSource !== 'api') {return}
|
||||
if (props.dataSource !== 'api') {
|
||||
return
|
||||
}
|
||||
|
||||
page.value = 1
|
||||
rawTransactions.value = []
|
||||
@@ -582,8 +642,33 @@ watch(
|
||||
{ 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(() => {
|
||||
// 加载分类列表(用于图标映射)
|
||||
loadCategories()
|
||||
|
||||
if (props.dataSource === 'api') {
|
||||
fetchTransactions()
|
||||
}
|
||||
@@ -748,17 +833,17 @@ const toggleSelection = (transaction) => {
|
||||
|
||||
.tag-expense {
|
||||
background-color: rgba(239, 68, 68, 0.1);
|
||||
color: #EF4444;
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.tag-income {
|
||||
background-color: rgba(16, 185, 129, 0.1);
|
||||
color: #10B981;
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.tag-neutral {
|
||||
background-color: rgba(107, 114, 128, 0.1);
|
||||
color: #6B7280;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
// 5.1 右侧金额区域
|
||||
|
||||
@@ -74,6 +74,11 @@ let searchTimer = null
|
||||
|
||||
// 加载数据
|
||||
const loadData = async (isRefresh = false) => {
|
||||
// 防止并发加载:如果正在加载中且不是刷新操作,则直接返回
|
||||
if (loading.value && !isRefresh) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isRefresh) {
|
||||
pageIndex.value = 1
|
||||
transactionList.value = []
|
||||
|
||||
Reference in New Issue
Block a user