feat: 添加收入分配规则管理页面及账户、转账、交易、分配规则等相关组件的图标渲染

This commit is contained in:
2026-01-26 02:02:03 +08:00
parent 2cb596f7af
commit 38eeb4a425
8 changed files with 72 additions and 14 deletions

View File

@@ -7,6 +7,7 @@ import React from 'react';
import type { Account, AccountType } from '../../../types'; import type { Account, AccountType } from '../../../types';
import { formatCurrency } from '../../../utils/format'; import { formatCurrency } from '../../../utils/format';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { isIconifyIcon } from '../../../utils/iconUtils';
import './AccountCard.css'; import './AccountCard.css';
interface AccountCardProps { interface AccountCardProps {
@@ -101,7 +102,9 @@ export const AccountCard: React.FC<AccountCardProps> = ({
<div className="account-card__header"> <div className="account-card__header">
<div className="account-card__icon-wrapper"> <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>
<div className="account-card__actions-overlay"> <div className="account-card__actions-overlay">
{onEdit && ( {onEdit && (

View File

@@ -6,6 +6,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import type { Account, TransferFormInput } from '../../../types'; import type { Account, TransferFormInput } from '../../../types';
import { formatCurrency } from '../../../utils/format'; import { formatCurrency } from '../../../utils/format';
import { getDisplayIcon } from '../../../utils/iconUtils';
import './TransferForm.css'; import './TransferForm.css';
interface TransferFormProps { interface TransferFormProps {
@@ -136,7 +137,7 @@ export const TransferForm: React.FC<TransferFormProps> = ({
<option value={0}></option> <option value={0}></option>
{accounts.map((account) => ( {accounts.map((account) => (
<option key={account.id} value={account.id}> <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> </option>
))} ))}
</select> </select>
@@ -179,7 +180,7 @@ export const TransferForm: React.FC<TransferFormProps> = ({
<option value={0}></option> <option value={0}></option>
{availableToAccounts.map((account) => ( {availableToAccounts.map((account) => (
<option key={account.id} value={account.id}> <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> </option>
))} ))}
</select> </select>

View File

@@ -12,6 +12,7 @@ import type {
AllocationTargetInput, AllocationTargetInput,
} from '../../../services/allocationRuleService'; } from '../../../services/allocationRuleService';
import { getTargetTypeLabel } from '../../../services/allocationRuleService'; import { getTargetTypeLabel } from '../../../services/allocationRuleService';
import { getDisplayIcon } from '../../../utils/iconUtils';
import './AllocationRuleForm.css'; import './AllocationRuleForm.css';
interface AllocationRuleFormProps { interface AllocationRuleFormProps {
@@ -283,7 +284,7 @@ export const AllocationRuleForm: React.FC<AllocationRuleFormProps> = ({
<option value=""></option> <option value=""></option>
{accounts.map((account) => ( {accounts.map((account) => (
<option key={account.id} value={account.id}> <option key={account.id} value={account.id}>
{account.icon} {account.name} {getDisplayIcon(account.icon)} {account.name}
</option> </option>
))} ))}
</select> </select>
@@ -382,7 +383,7 @@ export const AllocationRuleForm: React.FC<AllocationRuleFormProps> = ({
) : ( ) : (
accounts.map((account) => ( accounts.map((account) => (
<option key={account.id} value={account.id}> <option key={account.id} value={account.id}>
{account.icon} {account.name} {getDisplayIcon(account.icon)} {account.name}
</option> </option>
)) ))
) )

View File

@@ -21,6 +21,7 @@ import {
calculateNextOccurrence, calculateNextOccurrence,
} from '../../../services/recurringTransactionService'; } from '../../../services/recurringTransactionService';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { getDisplayIcon } from '../../../utils/iconUtils';
import './RecurringTransactionForm.css'; import './RecurringTransactionForm.css';
interface RecurringTransactionFormProps { interface RecurringTransactionFormProps {
@@ -417,7 +418,7 @@ export const RecurringTransactionForm: React.FC<RecurringTransactionFormProps> =
<option value={0}></option> <option value={0}></option>
{accounts.map((account) => ( {accounts.map((account) => (
<option key={account.id} value={account.id}> <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)}) {account.balance.toFixed(2)})
</option> </option>
))} ))}

View File

@@ -9,6 +9,7 @@ import { Icon } from '@iconify/react';
import type { Category, Account, TransactionType } from '../../../types'; import type { Category, Account, TransactionType } from '../../../types';
import { getCategories } from '../../../services/categoryService'; import { getCategories } from '../../../services/categoryService';
import { getAccounts } from '../../../services/accountService'; import { getAccounts } from '../../../services/accountService';
import { getDisplayIcon } from '../../../utils/iconUtils';
import './TransactionFilter.css'; import './TransactionFilter.css';
export interface FilterValues { export interface FilterValues {
@@ -301,7 +302,7 @@ export const TransactionFilter: React.FC<TransactionFilterProps> = ({
<option value=""></option> <option value=""></option>
{accounts.map((account) => ( {accounts.map((account) => (
<option key={account.id} value={account.id}> <option key={account.id} value={account.id}>
{account.icon} {account.name} {getDisplayIcon(account.icon)} {account.name}
</option> </option>
))} ))}
</select> </select>

View File

@@ -7,6 +7,7 @@
import React from 'react'; import React from 'react';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import type { Transaction, Category } from '../../../types'; import type { Transaction, Category } from '../../../types';
import { isIconifyIcon } from '../../../utils/iconUtils';
import './TransactionItem.css'; import './TransactionItem.css';
interface TransactionItemProps { interface TransactionItemProps {
@@ -108,7 +109,11 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
> >
{/* 图标 */} {/* 图标 */}
<div className={`transaction-item-icon ${transaction.type}`}> <div className={`transaction-item-icon ${transaction.type}`}>
{isIconifyIcon(getCategoryIcon()) ? (
<Icon icon={getCategoryIcon()} width={compact ? 18 : 22} /> <Icon icon={getCategoryIcon()} width={compact ? 18 : 22} />
) : (
<span style={{ fontSize: compact ? 18 : 22 }}>{getCategoryIcon()}</span>
)}
</div> </div>
{/* 主要信息 */} {/* 主要信息 */}

View File

@@ -19,6 +19,7 @@ import {
} from '../../services/allocationRuleService'; } from '../../services/allocationRuleService';
import { getAccounts } from '../../services/accountService'; import { getAccounts } from '../../services/accountService';
import { getPiggyBanks } from '../../services/piggyBankService'; import { getPiggyBanks } from '../../services/piggyBankService';
import { getDisplayIcon } from '../../utils/iconUtils';
import './AllocationRules.css'; import './AllocationRules.css';
function AllocationRules() { function AllocationRules() {
@@ -110,7 +111,7 @@ function AllocationRules() {
const getTargetName = (targetType: string, targetId: number): string => { const getTargetName = (targetType: string, targetId: number): string => {
if (targetType === 'account') { if (targetType === 'account') {
const account = accounts.find((a) => a.id === targetId); const account = accounts.find((a) => a.id === targetId);
return account ? `${account.icon} ${account.name}` : '未知账户'; return account ? `${getDisplayIcon(account.icon)} ${account.name}` : '未知账户';
} else { } else {
const piggyBank = piggyBanks.find((p) => p.id === targetId); const piggyBank = piggyBanks.find((p) => p.id === targetId);
return piggyBank ? `🐷 ${piggyBank.name}` : '未知存钱罐'; return piggyBank ? `🐷 ${piggyBank.name}` : '未知存钱罐';

45
src/utils/iconUtils.ts Normal file
View 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] || '💰';
}