Penny Lens 资产管理模块详解

2025年1月27日
7 分钟阅读
作者:Penny Lens Team

资产管理模块

🏦 概述

资产管理模块是 Penny Lens 应用的核心功能模块,负责用户各类资产账户的管理、余额跟踪、资产变化记录等功能。支持现金、银行卡、信用卡、投资账户等多种资产类型。

🏗️ 模块架构

核心组件

src/
├── pages/assets/ # 资产管理页面
│ ├── index.vue # 资产列表主页
│ ├── addAssetForm.vue # 添加资产表单
│ ├── addAssetType.vue # 添加资产类型
│ └── assetDetail.vue # 资产详情页面
├── components/AssetSelector/ # 资产选择器组件
├── store/asset.ts # 资产状态管理
├── services/api/asset.ts # 资产API服务
├── services/asset.ts # 资产服务
├── utils/assetUtils.ts # 资产工具函数
├── types/asset.ts # 资产类型定义
├── types/assetTypeModel.ts # 资产类型模型
└── types/assetChange.ts # 资产变化类型

💰 资产类型

支持的资产类型

// 资产类型枚举
const AssetType = {
  CASH: 'cash',           // 现金
  BANK: 'bank',           // 银行卡
  CREDIT: 'credit',       // 信用卡
  INVESTMENT: 'investment', // 投资账户
  OTHER: 'other'          // 其他
} as const
 
type AssetType = typeof AssetType[keyof typeof AssetType]

资产数据结构

// 资产接口定义
interface Asset {
  id: string;              // 资产ID
  name: string;            // 资产名称
  type: AssetType;         // 资产类型
  balanceCents: number;    // 余额(分)
  currency: string;        // 货币类型
  description?: string;    // 描述
  isActive: boolean;       // 是否激活
  createdAt: number;       // 创建时间
  updatedAt: number;       // 更新时间
}
 
// 资产类型配置
interface AssetTypeConfig {
  type: AssetType;
  name: string;
  icon: string;
  color: string;
  description: string;
  defaultCurrency: string;
}

🔧 核心功能

1. 资产列表管理

功能描述: 显示和管理用户的所有资产账户

实现流程:

  1. 从后端获取用户资产列表
  2. 按类型分组显示
  3. 计算总余额
  4. 提供添加、编辑、删除操作

代码示例:

// 获取资产列表
async function getAssetList(): Promise<Asset[]> {
  const response = await assetApi.getAssetList()
  return response.data.list
}
 
// 计算总余额
function calculateTotalBalance(assets: Asset[]): number {
  return assets.reduce((total, asset) => {
    return total + (asset.type === AssetType.CREDIT ? -asset.balanceCents : asset.balanceCents)
  }, 0)
}
 
// 按类型分组
function groupAssetsByType(assets: Asset[]): Record<AssetType, Asset[]> {
  return assets.reduce((groups, asset) => {
    if (!groups[asset.type]) {
      groups[asset.type] = []
    }
    groups[asset.type].push(asset)
    return groups
  }, {} as Record<AssetType, Asset[]>)
}

2. 添加资产

功能描述: 创建新的资产账户

实现流程:

  1. 用户选择资产类型
  2. 填写资产信息
  3. 设置初始余额
  4. 保存到后端

代码示例:

// 添加资产请求
interface CreateAssetRequest {
  name: string;
  type: AssetType;
  balanceCents: number;
  currency: string;
  description?: string;
}
 
// 添加资产实现
async function createAsset(assetData: CreateAssetRequest): Promise<Asset> {
  const response = await assetApi.createAsset(assetData)
  if (response.code === 200) {
    // 更新本地状态
    await refreshAssetList()
    return response.data
  }
  throw new Error(response.message)
}
 
// 表单验证
function validateAssetForm(data: CreateAssetRequest): ValidationResult {
  const errors: string[] = []
  
  if (!data.name.trim()) {
    errors.push('资产名称不能为空')
  }
  
  if (data.name.length > 20) {
    errors.push('资产名称不能超过20个字符')
  }
  
  if (data.balanceCents < 0) {
    errors.push('初始余额不能为负数')
  }
  
  return {
    isValid: errors.length === 0,
    errors
  }
}

3. 资产详情

功能描述: 查看单个资产的详细信息和交易记录

实现流程:

  1. 获取资产基本信息
  2. 获取资产交易记录
  3. 计算资产变化趋势
  4. 显示余额历史

代码示例:

// 资产详情请求
interface AssetDetailRequest {
  id: string;
  startDate?: number;
  endDate?: number;
}
 
// 资产详情响应
interface AssetDetailResponse {
  asset: Asset;
  transactions: AccountingRecord[];
  balanceHistory: BalanceHistory[];
  totalIncome: number;
  totalExpense: number;
  netChange: number;
}
 
// 获取资产详情
async function getAssetDetail(params: AssetDetailRequest): Promise<AssetDetailResponse> {
  const response = await assetApi.getAssetDetail(params)
  return response.data
}
 
// 计算余额历史
function calculateBalanceHistory(
  transactions: AccountingRecord[],
  initialBalance: number
): BalanceHistory[] {
  let currentBalance = initialBalance
  const history: BalanceHistory[] = []
  
  // 按时间排序
  const sortedTransactions = transactions.sort((a, b) => a.date - b.date)
  
  sortedTransactions.forEach(transaction => {
    if (transaction.type === 'income') {
      currentBalance += transaction.amountCents
    } else if (transaction.type === 'expense') {
      currentBalance -= transaction.amountCents
    }
    
    history.push({
      date: transaction.date,
      balance: currentBalance,
      change: transaction.amountCents,
      type: transaction.type
    })
  })
  
  return history
}

4. 资产转账

功能描述: 在不同资产账户之间进行转账

实现流程:

  1. 选择转出和转入资产
  2. 输入转账金额
  3. 添加转账备注
  4. 创建转账记录
  5. 更新资产余额

代码示例:

// 转账请求
interface TransferRequest {
  fromAssetId: string;
  toAssetId: string;
  amountCents: number;
  remark?: string;
  date: number;
}
 
// 转账实现
async function transferAssets(transferData: TransferRequest): Promise<void> {
  // 验证转账条件
  const validation = validateTransfer(transferData)
  if (!validation.isValid) {
    throw new Error(validation.errors.join(', '))
  }
  
  // 创建转账记录
  const response = await accountingApi.createTransfer(transferData)
  if (response.code === 200) {
    // 更新资产余额
    await refreshAssetList()
    return
  }
  throw new Error(response.message)
}
 
// 转账验证
function validateTransfer(data: TransferRequest): ValidationResult {
  const errors: string[] = []
  
  if (data.fromAssetId === data.toAssetId) {
    errors.push('转出和转入资产不能相同')
  }
  
  if (data.amountCents <= 0) {
    errors.push('转账金额必须大于0')
  }
  
  // 检查转出资产余额
  const fromAsset = getAssetById(data.fromAssetId)
  if (fromAsset && fromAsset.balanceCents < data.amountCents) {
    errors.push('转出资产余额不足')
  }
  
  return {
    isValid: errors.length === 0,
    errors
  }
}

🎯 资产选择器组件

功能描述: 提供统一的资产选择界面

代码示例:

<template>
  <view class="asset-selector">
    <view class="selector-header">
      <text class="title">{{ title }}</text>
      <text class="subtitle">{{ subtitle }}</text>
    </view>
    
    <view class="asset-list">
      <view
        v-for="asset in filteredAssets"
        :key="asset.id"
        class="asset-item"
        :class="{ active: selectedAssetId === asset.id }"
        @click="selectAsset(asset)"
      >
        <view class="asset-icon">
          <Iconify :icon="getAssetIcon(asset.type)" size="24" />
        </view>
        <view class="asset-info">
          <text class="asset-name">{{ asset.name }}</text>
          <text class="asset-balance">{{ formatBalance(asset.balanceCents) }}</text>
        </view>
        <view class="asset-type">
          <text class="type-text">{{ getAssetTypeName(asset.type) }}</text>
        </view>
      </view>
    </view>
    
    <view class="selector-actions">
      <button class="btn-secondary" @click="handleCancel">取消</button>
      <button class="btn-primary" @click="handleConfirm">确定</button>
    </view>
  </view>
</template>
 
<script setup lang="ts">
interface Props {
  title: string;
  subtitle?: string;
  selectedAssetId?: string;
  filterType?: AssetType[];
  showBalance?: boolean;
}
 
interface Emits {
  select: [asset: Asset];
  cancel: [];
}
 
const props = withDefaults(defineProps<Props>(), {
  showBalance: true
})
 
const emit = defineEmits<Emits>()
 
const assetStore = useAssetStore()
const { assets } = storeToRefs(assetStore)
 
// 过滤资产
const filteredAssets = computed(() => {
  let filtered = assets.value
  
  if (props.filterType && props.filterType.length > 0) {
    filtered = filtered.filter(asset => props.filterType!.includes(asset.type))
  }
  
  return filtered
})
 
// 选择资产
function selectAsset(asset: Asset) {
  emit('select', asset)
}
 
// 确认选择
function handleConfirm() {
  if (props.selectedAssetId) {
    const selectedAsset = assets.value.find(asset => asset.id === props.selectedAssetId)
    if (selectedAsset) {
      emit('select', selectedAsset)
    }
  }
}
 
// 取消选择
function handleCancel() {
  emit('cancel')
}
</script>

📊 资产统计

功能描述: 提供资产相关的统计分析

代码示例:

// 资产统计接口
interface AssetStatistics {
  totalBalance: number;           // 总余额
  totalAssets: number;           // 资产总数
  assetTypeDistribution: {       // 资产类型分布
    type: AssetType;
    count: number;
    totalBalance: number;
    percentage: number;
  }[];
  balanceTrend: {               // 余额趋势
    date: number;
    balance: number;
  }[];
  topAssets: Asset[];           // 余额最高的资产
}
 
// 计算资产统计
function calculateAssetStatistics(assets: Asset[]): AssetStatistics {
  const totalBalance = calculateTotalBalance(assets)
  const totalAssets = assets.length
  
  // 按类型分组统计
  const typeGroups = groupAssetsByType(assets)
  const assetTypeDistribution = Object.entries(typeGroups).map(([type, typeAssets]) => {
    const typeBalance = typeAssets.reduce((sum, asset) => sum + asset.balanceCents, 0)
    return {
      type: type as AssetType,
      count: typeAssets.length,
      totalBalance: typeBalance,
      percentage: totalBalance > 0 ? (typeBalance / totalBalance) * 100 : 0
    }
  })
  
  // 余额最高的资产
  const topAssets = [...assets]
    .sort((a, b) => b.balanceCents - a.balanceCents)
    .slice(0, 5)
  
  return {
    totalBalance,
    totalAssets,
    assetTypeDistribution,
    balanceTrend: [], // 需要从历史数据计算
    topAssets
  }
}

🔄 状态管理

Pinia Store

功能描述: 使用Pinia管理资产状态

代码示例:

// 资产状态管理
export const useAssetStore = defineStore('asset', () => {
  // 状态
  const assets = ref<Asset[]>([])
  const isLoading = ref(false)
  const totalBalance = computed(() => calculateTotalBalance(assets.value))
  
  // 操作
  const fetchAssets = async (): Promise<void> => {
    isLoading.value = true
    try {
      const assetList = await assetService.getAssetList()
      assets.value = assetList
    } catch (error) {
      console.error('获取资产列表失败:', error)
      throw error
    } finally {
      isLoading.value = false
    }
  }
  
  const addAsset = async (assetData: CreateAssetRequest): Promise<Asset> => {
    try {
      const newAsset = await assetService.createAsset(assetData)
      assets.value.push(newAsset)
      return newAsset
    } catch (error) {
      console.error('添加资产失败:', error)
      throw error
    }
  }
  
  const updateAsset = async (id: string, data: Partial<Asset>): Promise<Asset> => {
    try {
      const updatedAsset = await assetService.updateAsset(id, data)
      const index = assets.value.findIndex(asset => asset.id === id)
      if (index !== -1) {
        assets.value[index] = updatedAsset
      }
      return updatedAsset
    } catch (error) {
      console.error('更新资产失败:', error)
      throw error
    }
  }
  
  const deleteAsset = async (id: string): Promise<void> => {
    try {
      await assetService.deleteAsset(id)
      assets.value = assets.value.filter(asset => asset.id !== id)
    } catch (error) {
      console.error('删除资产失败:', error)
      throw error
    }
  }
  
  const getAssetById = (id: string): Asset | undefined => {
    return assets.value.find(asset => asset.id === id)
  }
  
  const getAssetsByType = (type: AssetType): Asset[] => {
    return assets.value.filter(asset => asset.type === type)
  }
  
  return {
    assets,
    isLoading,
    totalBalance,
    fetchAssets,
    addAsset,
    updateAsset,
    deleteAsset,
    getAssetById,
    getAssetsByType
  }
})

🛠️ 工具函数

资产工具

功能描述: 提供资产相关的工具函数

代码示例:

// 资产工具函数
export const assetUtils = {
  // 格式化余额显示
  formatBalance(cents: number, currency: string = 'CNY'): string {
    const amount = cents / 100
    return new Intl.NumberFormat('zh-CN', {
      style: 'currency',
      currency: currency,
      minimumFractionDigits: 2
    }).format(amount)
  },
  
  // 获取资产类型配置
  getAssetTypeConfig(type: AssetType): AssetTypeConfig {
    const configs: Record<AssetType, AssetTypeConfig> = {
      [AssetType.CASH]: {
        type: AssetType.CASH,
        name: '现金',
        icon: 'i-mdi-cash',
        color: '#10b981',
        description: '现金账户',
        defaultCurrency: 'CNY'
      },
      [AssetType.BANK]: {
        type: AssetType.BANK,
        name: '银行卡',
        icon: 'i-mdi-credit-card',
        color: '#3b82f6',
        description: '银行储蓄卡',
        defaultCurrency: 'CNY'
      },
      [AssetType.CREDIT]: {
        type: AssetType.CREDIT,
        name: '信用卡',
        icon: 'i-mdi-credit-card-outline',
        color: '#ef4444',
        description: '信用卡账户',
        defaultCurrency: 'CNY'
      },
      [AssetType.INVESTMENT]: {
        type: AssetType.INVESTMENT,
        name: '投资账户',
        icon: 'i-mdi-trending-up',
        color: '#8b5cf6',
        description: '投资理财账户',
        defaultCurrency: 'CNY'
      },
      [AssetType.OTHER]: {
        type: AssetType.OTHER,
        name: '其他',
        icon: 'i-mdi-wallet',
        color: '#6b7280',
        description: '其他类型资产',
        defaultCurrency: 'CNY'
      }
    }
    
    return configs[type]
  },
  
  // 获取资产图标
  getAssetIcon(type: AssetType): string {
    return this.getAssetTypeConfig(type).icon
  },
  
  // 获取资产类型名称
  getAssetTypeName(type: AssetType): string {
    return this.getAssetTypeConfig(type).name
  },
  
  // 计算资产净值
  calculateNetWorth(assets: Asset[]): number {
    return assets.reduce((total, asset) => {
      if (asset.type === AssetType.CREDIT) {
        return total - asset.balanceCents
      }
      return total + asset.balanceCents
    }, 0)
  },
  
  // 验证资产名称
  validateAssetName(name: string): ValidationResult {
    const errors: string[] = []
    
    if (!name.trim()) {
      errors.push('资产名称不能为空')
    }
    
    if (name.length > 20) {
      errors.push('资产名称不能超过20个字符')
    }
    
    if (!/^[\u4e00-\u9fa5a-zA-Z0-9\s]+$/.test(name)) {
      errors.push('资产名称只能包含中文、英文、数字和空格')
    }
    
    return {
      isValid: errors.length === 0,
      errors
    }
  }
}

📚 最佳实践

1. 数据一致性

  • 实时更新资产余额
  • 同步记账记录和资产变化
  • 处理并发操作
  • 数据验证和错误处理

2. 用户体验

  • 快速加载资产列表
  • 直观的资产类型图标
  • 清晰的余额显示
  • 便捷的资产操作

3. 性能优化

  • 缓存资产数据
  • 懒加载资产详情
  • 分页加载交易记录
  • 优化图片资源

4. 安全考虑

  • 验证用户权限
  • 防止数据篡改
  • 敏感信息加密
  • 操作日志记录

Penny Lens 资产管理模块 - 让资产账户管理更清晰、更高效 🏦