fix: 修复账单列表分页bug并优化日期和图标显示
- 修复分页并发加载bug,防止重复请求同一页数据 - 优化日期格式显示,从 HH:mm 改为 MM-DD HH:mm - 集成分类真实图标,支持 Iconify 格式,替换默认五角星图标 - 添加图标加载降级机制,确保兼容性
This commit is contained in:
@@ -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 右侧金额区域
|
||||||
|
|||||||
@@ -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 = []
|
||||||
|
|||||||
Reference in New Issue
Block a user