diff --git a/Web/src/components/Bill/BillListComponent.vue b/Web/src/components/Bill/BillListComponent.vue index c0a0243..1f75e15 100644 --- a/Web/src/components/Bill/BillListComponent.vue +++ b/Web/src/components/Bill/BillListComponent.vue @@ -104,7 +104,16 @@ class="card-icon" :style="{ backgroundColor: getIconBg(transaction.type) }" > + + + 图标的映射 + // 筛选状态管理 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 右侧金额区域 diff --git a/Web/src/views/TransactionsRecord.vue b/Web/src/views/TransactionsRecord.vue index cce30ea..970edb3 100644 --- a/Web/src/views/TransactionsRecord.vue +++ b/Web/src/views/TransactionsRecord.vue @@ -74,6 +74,11 @@ let searchTimer = null // 加载数据 const loadData = async (isRefresh = false) => { + // 防止并发加载:如果正在加载中且不是刷新操作,则直接返回 + if (loading.value && !isRefresh) { + return + } + if (isRefresh) { pageIndex.value = 1 transactionList.value = []