feat: 添加收入分配规则管理页面及账户、转账、交易、分配规则等相关组件的图标渲染
This commit is contained in:
@@ -7,6 +7,7 @@ import React from 'react';
|
||||
import type { Account, AccountType } from '../../../types';
|
||||
import { formatCurrency } from '../../../utils/format';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { isIconifyIcon } from '../../../utils/iconUtils';
|
||||
import './AccountCard.css';
|
||||
|
||||
interface AccountCardProps {
|
||||
@@ -101,7 +102,9 @@ export const AccountCard: React.FC<AccountCardProps> = ({
|
||||
|
||||
<div className="account-card__header">
|
||||
<div className="account-card__icon-wrapper">
|
||||
<div className="account-card__icon">{icon}</div>
|
||||
<div className="account-card__icon">
|
||||
{isIconifyIcon(icon) ? <Icon icon={icon} width="24" /> : icon}
|
||||
</div>
|
||||
</div>
|
||||
<div className="account-card__actions-overlay">
|
||||
{onEdit && (
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import type { Account, TransferFormInput } from '../../../types';
|
||||
import { formatCurrency } from '../../../utils/format';
|
||||
import { getDisplayIcon } from '../../../utils/iconUtils';
|
||||
import './TransferForm.css';
|
||||
|
||||
interface TransferFormProps {
|
||||
@@ -136,7 +137,7 @@ export const TransferForm: React.FC<TransferFormProps> = ({
|
||||
<option value={0}>请选择转出账户</option>
|
||||
{accounts.map((account) => (
|
||||
<option key={account.id} value={account.id}>
|
||||
{account.icon} {account.name} ({formatCurrency(account.balance, account.currency)})
|
||||
{getDisplayIcon(account.icon)} {account.name} ({formatCurrency(account.balance, account.currency)})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -179,7 +180,7 @@ export const TransferForm: React.FC<TransferFormProps> = ({
|
||||
<option value={0}>请选择转入账户</option>
|
||||
{availableToAccounts.map((account) => (
|
||||
<option key={account.id} value={account.id}>
|
||||
{account.icon} {account.name} ({formatCurrency(account.balance, account.currency)})
|
||||
{getDisplayIcon(account.icon)} {account.name} ({formatCurrency(account.balance, account.currency)})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
AllocationTargetInput,
|
||||
} from '../../../services/allocationRuleService';
|
||||
import { getTargetTypeLabel } from '../../../services/allocationRuleService';
|
||||
import { getDisplayIcon } from '../../../utils/iconUtils';
|
||||
import './AllocationRuleForm.css';
|
||||
|
||||
interface AllocationRuleFormProps {
|
||||
@@ -283,7 +284,7 @@ export const AllocationRuleForm: React.FC<AllocationRuleFormProps> = ({
|
||||
<option value="">所有账户(任意账户收入都触发)</option>
|
||||
{accounts.map((account) => (
|
||||
<option key={account.id} value={account.id}>
|
||||
{account.icon} {account.name}
|
||||
{getDisplayIcon(account.icon)} {account.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -382,7 +383,7 @@ export const AllocationRuleForm: React.FC<AllocationRuleFormProps> = ({
|
||||
) : (
|
||||
accounts.map((account) => (
|
||||
<option key={account.id} value={account.id}>
|
||||
{account.icon} {account.name}
|
||||
{getDisplayIcon(account.icon)} {account.name}
|
||||
</option>
|
||||
))
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
calculateNextOccurrence,
|
||||
} from '../../../services/recurringTransactionService';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { getDisplayIcon } from '../../../utils/iconUtils';
|
||||
import './RecurringTransactionForm.css';
|
||||
|
||||
interface RecurringTransactionFormProps {
|
||||
@@ -417,7 +418,7 @@ export const RecurringTransactionForm: React.FC<RecurringTransactionFormProps> =
|
||||
<option value={0}>请选择账户</option>
|
||||
{accounts.map((account) => (
|
||||
<option key={account.id} value={account.id}>
|
||||
{account.icon} {account.name} ({getCurrencySymbol(account.currency)}
|
||||
{getDisplayIcon(account.icon)} {account.name} ({getCurrencySymbol(account.currency)}
|
||||
{account.balance.toFixed(2)})
|
||||
</option>
|
||||
))}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Icon } from '@iconify/react';
|
||||
import type { Category, Account, TransactionType } from '../../../types';
|
||||
import { getCategories } from '../../../services/categoryService';
|
||||
import { getAccounts } from '../../../services/accountService';
|
||||
import { getDisplayIcon } from '../../../utils/iconUtils';
|
||||
import './TransactionFilter.css';
|
||||
|
||||
export interface FilterValues {
|
||||
@@ -256,8 +257,8 @@ export const TransactionFilter: React.FC<TransactionFilterProps> = ({
|
||||
<button
|
||||
key={option.value || 'all'}
|
||||
className={`transaction-filter__type-btn ${(values.type || '') === option.value
|
||||
? 'transaction-filter__type-btn--active'
|
||||
: ''
|
||||
? 'transaction-filter__type-btn--active'
|
||||
: ''
|
||||
}`}
|
||||
onClick={() => handleChange('type', option.value as TransactionType | undefined)}
|
||||
>
|
||||
@@ -301,7 +302,7 @@ export const TransactionFilter: React.FC<TransactionFilterProps> = ({
|
||||
<option value="">全部账户</option>
|
||||
{accounts.map((account) => (
|
||||
<option key={account.id} value={account.id}>
|
||||
{account.icon} {account.name}
|
||||
{getDisplayIcon(account.icon)} {account.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '@iconify/react';
|
||||
import type { Transaction, Category } from '../../../types';
|
||||
import { isIconifyIcon } from '../../../utils/iconUtils';
|
||||
import './TransactionItem.css';
|
||||
|
||||
interface TransactionItemProps {
|
||||
@@ -102,13 +103,17 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<div
|
||||
className={`transaction-item ${transaction.type} ${compact ? 'compact' : ''} ${onClick ? 'clickable' : ''} ${selected ? 'selected' : ''}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{/* 图标 */}
|
||||
<div className={`transaction-item-icon ${transaction.type}`}>
|
||||
<Icon icon={getCategoryIcon()} width={compact ? 18 : 22} />
|
||||
{isIconifyIcon(getCategoryIcon()) ? (
|
||||
<Icon icon={getCategoryIcon()} width={compact ? 18 : 22} />
|
||||
) : (
|
||||
<span style={{ fontSize: compact ? 18 : 22 }}>{getCategoryIcon()}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 主要信息 */}
|
||||
@@ -121,7 +126,7 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||
<span className="transaction-item-note">{transaction.note}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
{showDate && (
|
||||
<div className="transaction-item-meta">
|
||||
<span className="transaction-item-date">
|
||||
@@ -142,7 +147,7 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||
{transaction.type === 'income' ? '+' : transaction.type === 'expense' ? '-' : ''}
|
||||
{formatAmount(transaction.amount)}
|
||||
</span>
|
||||
|
||||
|
||||
{/* 状态标签 */}
|
||||
{(transaction.reimbursementStatus !== 'none' || transaction.refundStatus !== 'none') && (
|
||||
<div className="transaction-item-badges">
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from '../../services/allocationRuleService';
|
||||
import { getAccounts } from '../../services/accountService';
|
||||
import { getPiggyBanks } from '../../services/piggyBankService';
|
||||
import { getDisplayIcon } from '../../utils/iconUtils';
|
||||
import './AllocationRules.css';
|
||||
|
||||
function AllocationRules() {
|
||||
@@ -110,7 +111,7 @@ function AllocationRules() {
|
||||
const getTargetName = (targetType: string, targetId: number): string => {
|
||||
if (targetType === 'account') {
|
||||
const account = accounts.find((a) => a.id === targetId);
|
||||
return account ? `${account.icon} ${account.name}` : '未知账户';
|
||||
return account ? `${getDisplayIcon(account.icon)} ${account.name}` : '未知账户';
|
||||
} else {
|
||||
const piggyBank = piggyBanks.find((p) => p.id === targetId);
|
||||
return piggyBank ? `🐷 ${piggyBank.name}` : '未知存钱罐';
|
||||
|
||||
45
src/utils/iconUtils.ts
Normal file
45
src/utils/iconUtils.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Icon Utilities
|
||||
* Helper functions for rendering account/category icons consistently
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if an icon string is an Iconify icon format (contains :)
|
||||
*/
|
||||
export function isIconifyIcon(icon: string | undefined | null): boolean {
|
||||
return Boolean(icon && icon.includes(':'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a display-safe icon string for use in contexts where React components can't be used
|
||||
* (e.g., <option> elements in <select>)
|
||||
* Returns emoji or empty string for iconify icons
|
||||
*/
|
||||
export function getDisplayIcon(icon: string | undefined | null): string {
|
||||
if (!icon) return '';
|
||||
// If it's an iconify icon, return empty string (can't display in <option>)
|
||||
if (icon.includes(':')) return '';
|
||||
// Otherwise return the icon (likely an emoji)
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default account type icons
|
||||
*/
|
||||
export const ACCOUNT_TYPE_ICONS: Record<string, string> = {
|
||||
bank: '🏦',
|
||||
cash: '💵',
|
||||
credit_card: '💳',
|
||||
alipay: '📱',
|
||||
wechat: '📱',
|
||||
investment: '📈',
|
||||
other: '💰',
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a fallback icon for an account based on its type
|
||||
*/
|
||||
export function getAccountFallbackIcon(accountType?: string): string {
|
||||
if (!accountType) return '💰';
|
||||
return ACCOUNT_TYPE_ICONS[accountType] || '💰';
|
||||
}
|
||||
Reference in New Issue
Block a user