Technical Architecture Document: asset-light

版本: 1.0.0
日期: 2025-12-20
架构师: Architect Agent


1. 架构概述

1.1 技术栈

层面技术选型版本说明
UI 框架Dioxus0.5.xRust 生态的声明式 UI 框架
语言Rust1.75+系统级编程语言
平台Desktop (macOS)-基于 WebView 渲染
数据库SQLite3.x本地嵌入式数据库
ORMrusqlite0.31.xSQLite Rust 绑定
序列化serde + serde_json1.xJSON 序列化
日期时间chrono0.4.x日期时间处理
UUIDuuid1.x唯一标识生成
精确数值rust_decimal1.x金融级精度计算

1.2 架构模式

采用分层架构 + 组件化 UI模式:

┌─────────────────────────────────────────────────────────────┐
│                    Presentation Layer                        │
│                  (Dioxus Components)                         │
├─────────────────────────────────────────────────────────────┤
│                    Application Layer                         │
│              (State Management + Hooks)                      │
├─────────────────────────────────────────────────────────────┤
│                     Domain Layer                             │
│              (Models + Business Logic)                       │
├─────────────────────────────────────────────────────────────┤
│                  Infrastructure Layer                        │
│             (Database + File System)                         │
└─────────────────────────────────────────────────────────────┘

2. 项目结构

asset-light/
├── Cargo.toml
├── Dioxus.toml                 # Dioxus 配置
├── assets/                     # 静态资源
│   ├── icons/
│   └── styles/
├── src/
│   ├── main.rs                 # 应用入口
│   ├── app.rs                  # 根组件
│   ├── router.rs               # 路由定义
│   │
│   ├── components/             # UI 组件
│   │   ├── mod.rs
│   │   ├── layout/             # 布局组件
│   │   │   ├── mod.rs
│   │   │   ├── sidebar.rs
│   │   │   └── page_container.rs
│   │   ├── common/             # 通用组件
│   │   │   ├── mod.rs
│   │   │   ├── button.rs
│   │   │   ├── input.rs
│   │   │   ├── modal.rs
│   │   │   ├── card.rs
│   │   │   ├── toast.rs
│   │   │   └── empty_state.rs
│   │   ├── asset/              # 资产相关组件
│   │   │   ├── mod.rs
│   │   │   ├── asset_list.rs
│   │   │   ├── asset_form.rs
│   │   │   ├── asset_item.rs
│   │   │   └── category_group.rs
│   │   ├── snapshot/           # 盘点相关组件
│   │   │   ├── mod.rs
│   │   │   ├── inventory_mode.rs
│   │   │   ├── snapshot_timeline.rs
│   │   │   └── snapshot_detail.rs
│   │   ├── plan/               # 配置方案组件
│   │   │   ├── mod.rs
│   │   │   ├── plan_list.rs
│   │   │   ├── plan_card.rs
│   │   │   └── plan_editor.rs
│   │   ├── dashboard/          # 首页仪表盘组件
│   │   │   ├── mod.rs
│   │   │   ├── total_card.rs
│   │   │   ├── pie_chart.rs
│   │   │   └── deviation_table.rs
│   │   └── analysis/           # 收益分析组件
│   │       ├── mod.rs
│   │       ├── period_selector.rs
│   │       ├── return_card.rs
│   │       ├── attribution_table.rs
│   │       └── trend_chart.rs
│   │
│   ├── pages/                  # 页面组件
│   │   ├── mod.rs
│   │   ├── home.rs             # 首页
│   │   ├── assets.rs           # 资产管理
│   │   ├── history.rs          # 盘点历史
│   │   ├── plans.rs            # 配置方案
│   │   └── analysis.rs         # 收益分析
│   │
│   ├── models/                 # 数据模型
│   │   ├── mod.rs
│   │   ├── asset.rs
│   │   ├── snapshot.rs
│   │   ├── plan.rs
│   │   └── category.rs
│   │
│   ├── services/               # 业务服务
│   │   ├── mod.rs
│   │   ├── asset_service.rs
│   │   ├── snapshot_service.rs
│   │   ├── plan_service.rs
│   │   └── analysis_service.rs
│   │
│   ├── state/                  # 状态管理
│   │   ├── mod.rs
│   │   └── app_state.rs
│   │
│   ├── db/                     # 数据库层
│   │   ├── mod.rs
│   │   ├── connection.rs
│   │   ├── migrations.rs
│   │   ├── asset_repo.rs
│   │   ├── snapshot_repo.rs
│   │   └── plan_repo.rs
│   │
│   └── utils/                  # 工具函数
│       ├── mod.rs
│       ├── decimal.rs
│       ├── date.rs
│       └── format.rs
│
├── migrations/                 # SQL 迁移文件
│   ├── 001_create_assets.sql
│   ├── 002_create_snapshots.sql
│   └── 003_create_plans.sql
│
└── docs/                       # 项目文档

3. 数据模型

3.1 核心实体

Asset (资产条目)

#![allow(unused)]
fn main() {
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Category {
    Cash,      // 现金类
    Stable,    // 稳健类
    Advanced,  // 进阶类
}

impl Category {
    pub fn display_name(&self) -> &str {
        match self {
            Category::Cash => "现金类",
            Category::Stable => "稳健类",
            Category::Advanced => "进阶类",
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Asset {
    pub id: Uuid,
    pub name: String,
    pub category: Category,
    pub sub_category: Option<String>,
    pub current_value: Decimal,
    pub notes: Option<String>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

impl Asset {
    pub fn new(name: String, category: Category, current_value: Decimal) -> Self {
        let now = Utc::now();
        Self {
            id: Uuid::new_v4(),
            name,
            category,
            sub_category: None,
            current_value,
            notes: None,
            created_at: now,
            updated_at: now,
        }
    }
}
}

Snapshot (盘点快照)

#![allow(unused)]
fn main() {
use chrono::{DateTime, NaiveDate, Utc};
use rust_decimal::Decimal;
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Snapshot {
    pub id: Uuid,
    pub snapshot_date: NaiveDate,
    pub created_at: DateTime<Utc>,
    pub total_value: Decimal,
    pub items: Vec<SnapshotItem>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SnapshotItem {
    pub asset_id: Uuid,
    pub asset_name: String,
    pub category: Category,
    pub sub_category: Option<String>,
    pub value: Decimal,
}

impl Snapshot {
    pub fn new(items: Vec<SnapshotItem>) -> Self {
        let total_value = items.iter()
            .map(|item| item.value)
            .sum();
        
        Self {
            id: Uuid::new_v4(),
            snapshot_date: Utc::now().date_naive(),
            created_at: Utc::now(),
            total_value,
            items,
        }
    }
    
    pub fn category_total(&self, category: &Category) -> Decimal {
        self.items
            .iter()
            .filter(|item| &item.category == category)
            .map(|item| item.value)
            .sum()
    }
}
}

AllocationPlan (配置方案)

#![allow(unused)]
fn main() {
use rust_decimal::Decimal;
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AllocationPlan {
    pub id: Uuid,
    pub name: String,
    pub description: Option<String>,
    pub is_active: bool,
    pub allocations: Vec<Allocation>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Allocation {
    pub category: Category,
    pub target_percentage: Decimal,
    pub min_percentage: Option<Decimal>,
    pub max_percentage: Option<Decimal>,
}

impl AllocationPlan {
    pub fn default_balanced() -> Self {
        Self {
            id: Uuid::new_v4(),
            name: "平衡型".to_string(),
            description: Some("适合追求稳健增长的投资者".to_string()),
            is_active: true,
            allocations: vec![
                Allocation {
                    category: Category::Cash,
                    target_percentage: Decimal::new(20, 0),
                    min_percentage: None,
                    max_percentage: None,
                },
                Allocation {
                    category: Category::Stable,
                    target_percentage: Decimal::new(40, 0),
                    min_percentage: None,
                    max_percentage: None,
                },
                Allocation {
                    category: Category::Advanced,
                    target_percentage: Decimal::new(40, 0),
                    min_percentage: None,
                    max_percentage: None,
                },
            ],
            created_at: Utc::now(),
            updated_at: Utc::now(),
        }
    }
}
}

3.2 派生类型

#![allow(unused)]
fn main() {
// 偏离分析结果
#[derive(Debug, Clone)]
pub struct DeviationResult {
    pub category: Category,
    pub current_percentage: Decimal,
    pub target_percentage: Decimal,
    pub deviation: Decimal,
    pub status: DeviationStatus,
    pub direction: DeviationDirection,
}

#[derive(Debug, Clone, PartialEq)]
pub enum DeviationStatus {
    Normal,  // |偏离| <= 5%
    Mild,    // 5% < |偏离| <= 10%
    Severe,  // |偏离| > 10%
}

#[derive(Debug, Clone, PartialEq)]
pub enum DeviationDirection {
    Overweight,   // 超配
    Underweight,  // 低配
    Balanced,     // 平衡
}

// 收益分析结果
#[derive(Debug, Clone)]
pub struct ReturnAnalysis {
    pub start_date: NaiveDate,
    pub end_date: NaiveDate,
    pub start_value: Decimal,
    pub end_value: Decimal,
    pub absolute_return: Decimal,
    pub return_rate: Decimal,
    pub category_breakdown: Vec<CategoryReturn>,
}

#[derive(Debug, Clone)]
pub struct CategoryReturn {
    pub category: Category,
    pub start_value: Decimal,
    pub end_value: Decimal,
    pub absolute_return: Decimal,
    pub return_rate: Decimal,
    pub contribution_rate: Decimal,
}
}

4. 数据库设计

4.1 表结构

assets 表

CREATE TABLE IF NOT EXISTS assets (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    category TEXT NOT NULL CHECK (category IN ('Cash', 'Stable', 'Advanced')),
    sub_category TEXT,
    current_value TEXT NOT NULL,  -- 使用 TEXT 存储 Decimal
    notes TEXT,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE INDEX idx_assets_category ON assets(category);

snapshots 表

CREATE TABLE IF NOT EXISTS snapshots (
    id TEXT PRIMARY KEY,
    snapshot_date TEXT NOT NULL,
    created_at TEXT NOT NULL,
    total_value TEXT NOT NULL
);

CREATE INDEX idx_snapshots_date ON snapshots(snapshot_date);

snapshot_items 表

CREATE TABLE IF NOT EXISTS snapshot_items (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    snapshot_id TEXT NOT NULL,
    asset_id TEXT NOT NULL,
    asset_name TEXT NOT NULL,
    category TEXT NOT NULL,
    sub_category TEXT,
    value TEXT NOT NULL,
    FOREIGN KEY (snapshot_id) REFERENCES snapshots(id) ON DELETE CASCADE
);

CREATE INDEX idx_snapshot_items_snapshot ON snapshot_items(snapshot_id);

allocation_plans 表

CREATE TABLE IF NOT EXISTS allocation_plans (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    description TEXT,
    is_active INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

allocations 表

CREATE TABLE IF NOT EXISTS allocations (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    plan_id TEXT NOT NULL,
    category TEXT NOT NULL,
    target_percentage TEXT NOT NULL,
    min_percentage TEXT,
    max_percentage TEXT,
    FOREIGN KEY (plan_id) REFERENCES allocation_plans(id) ON DELETE CASCADE
);

CREATE INDEX idx_allocations_plan ON allocations(plan_id);

4.2 数据库连接管理

#![allow(unused)]
fn main() {
// src/db/connection.rs
use rusqlite::{Connection, Result};
use std::path::PathBuf;

pub struct Database {
    conn: Connection,
}

impl Database {
    pub fn new() -> Result<Self> {
        let path = Self::db_path();
        std::fs::create_dir_all(path.parent().unwrap()).ok();
        let conn = Connection::open(&path)?;
        conn.execute_batch("PRAGMA foreign_keys = ON;")?;
        Ok(Self { conn })
    }
    
    fn db_path() -> PathBuf {
        let mut path = dirs::data_local_dir()
            .unwrap_or_else(|| PathBuf::from("."));
        path.push("asset-light");
        path.push("data.db");
        path
    }
    
    pub fn conn(&self) -> &Connection {
        &self.conn
    }
    
    pub fn run_migrations(&self) -> Result<()> {
        // 执行迁移脚本
        self.conn.execute_batch(include_str!("../../migrations/001_create_assets.sql"))?;
        self.conn.execute_batch(include_str!("../../migrations/002_create_snapshots.sql"))?;
        self.conn.execute_batch(include_str!("../../migrations/003_create_plans.sql"))?;
        Ok(())
    }
}
}

5. 状态管理

5.1 全局状态定义

#![allow(unused)]
fn main() {
// src/state/app_state.rs
use dioxus::prelude::*;
use crate::models::{Asset, Snapshot, AllocationPlan};

#[derive(Clone)]
pub struct AppState {
    pub assets: Vec<Asset>,
    pub snapshots: Vec<Snapshot>,
    pub plans: Vec<AllocationPlan>,
    pub active_plan: Option<AllocationPlan>,
    pub is_inventory_mode: bool,
    pub loading: bool,
    pub error: Option<String>,
}

impl Default for AppState {
    fn default() -> Self {
        Self {
            assets: Vec::new(),
            snapshots: Vec::new(),
            plans: Vec::new(),
            active_plan: None,
            is_inventory_mode: false,
            loading: false,
            error: None,
        }
    }
}

// 全局状态 Context
pub fn use_app_state() -> Signal<AppState> {
    use_context::<Signal<AppState>>()
}

pub fn provide_app_state() -> Signal<AppState> {
    use_context_provider(|| Signal::new(AppState::default()))
}
}

5.2 状态操作

#![allow(unused)]
fn main() {
// src/state/actions.rs

impl AppState {
    // 资产操作
    pub fn add_asset(&mut self, asset: Asset) {
        self.assets.push(asset);
    }
    
    pub fn update_asset(&mut self, asset: Asset) {
        if let Some(idx) = self.assets.iter().position(|a| a.id == asset.id) {
            self.assets[idx] = asset;
        }
    }
    
    pub fn remove_asset(&mut self, id: Uuid) {
        self.assets.retain(|a| a.id != id);
    }
    
    // 快照操作
    pub fn add_snapshot(&mut self, snapshot: Snapshot) {
        self.snapshots.insert(0, snapshot); // 最新在前
    }
    
    // 方案操作
    pub fn set_active_plan(&mut self, plan_id: Uuid) {
        for plan in &mut self.plans {
            plan.is_active = plan.id == plan_id;
        }
        self.active_plan = self.plans.iter()
            .find(|p| p.is_active)
            .cloned();
    }
    
    // 计算属性
    pub fn total_value(&self) -> Decimal {
        self.assets.iter().map(|a| a.current_value).sum()
    }
    
    pub fn category_value(&self, category: &Category) -> Decimal {
        self.assets
            .iter()
            .filter(|a| &a.category == category)
            .map(|a| a.current_value)
            .sum()
    }
    
    pub fn category_percentage(&self, category: &Category) -> Decimal {
        let total = self.total_value();
        if total.is_zero() {
            return Decimal::ZERO;
        }
        (self.category_value(category) / total) * Decimal::new(100, 0)
    }
}
}

6. 服务层

6.1 资产服务

#![allow(unused)]
fn main() {
// src/services/asset_service.rs
use crate::db::Database;
use crate::models::Asset;

pub struct AssetService {
    db: Database,
}

impl AssetService {
    pub fn new(db: Database) -> Self {
        Self { db }
    }
    
    pub fn list_all(&self) -> Result<Vec<Asset>, Error> {
        AssetRepository::new(self.db.conn()).find_all()
    }
    
    pub fn create(&self, asset: &Asset) -> Result<(), Error> {
        AssetRepository::new(self.db.conn()).insert(asset)
    }
    
    pub fn update(&self, asset: &Asset) -> Result<(), Error> {
        AssetRepository::new(self.db.conn()).update(asset)
    }
    
    pub fn delete(&self, id: Uuid) -> Result<(), Error> {
        AssetRepository::new(self.db.conn()).delete(id)
    }
}
}

6.2 快照服务

#![allow(unused)]
fn main() {
// src/services/snapshot_service.rs

pub struct SnapshotService {
    db: Database,
}

impl SnapshotService {
    pub fn create_snapshot(&self, assets: &[Asset]) -> Result<Snapshot, Error> {
        // 检查每日盘点次数限制
        let today_count = self.count_today_snapshots()?;
        if today_count >= 5 {
            return Err(Error::LimitExceeded("每天最多盘点5次".to_string()));
        }
        
        // 创建快照
        let items: Vec<SnapshotItem> = assets
            .iter()
            .map(|a| SnapshotItem {
                asset_id: a.id,
                asset_name: a.name.clone(),
                category: a.category.clone(),
                sub_category: a.sub_category.clone(),
                value: a.current_value,
            })
            .collect();
        
        let snapshot = Snapshot::new(items);
        SnapshotRepository::new(self.db.conn()).insert(&snapshot)?;
        
        Ok(snapshot)
    }
    
    pub fn list_all(&self) -> Result<Vec<Snapshot>, Error> {
        SnapshotRepository::new(self.db.conn()).find_all_ordered()
    }
    
    pub fn get_by_id(&self, id: Uuid) -> Result<Option<Snapshot>, Error> {
        SnapshotRepository::new(self.db.conn()).find_by_id(id)
    }
}
}

6.3 分析服务

#![allow(unused)]
fn main() {
// src/services/analysis_service.rs

pub struct AnalysisService;

impl AnalysisService {
    pub fn calculate_deviation(
        assets: &[Asset],
        plan: &AllocationPlan,
    ) -> Vec<DeviationResult> {
        let total: Decimal = assets.iter().map(|a| a.current_value).sum();
        
        if total.is_zero() {
            return vec![];
        }
        
        plan.allocations
            .iter()
            .map(|alloc| {
                let category_total: Decimal = assets
                    .iter()
                    .filter(|a| a.category == alloc.category)
                    .map(|a| a.current_value)
                    .sum();
                
                let current_pct = (category_total / total) * Decimal::new(100, 0);
                let target_pct = alloc.target_percentage;
                let deviation = current_pct - target_pct;
                let abs_deviation = deviation.abs();
                
                let status = if abs_deviation <= Decimal::new(5, 0) {
                    DeviationStatus::Normal
                } else if abs_deviation <= Decimal::new(10, 0) {
                    DeviationStatus::Mild
                } else {
                    DeviationStatus::Severe
                };
                
                let direction = if deviation > Decimal::ZERO {
                    DeviationDirection::Overweight
                } else if deviation < Decimal::ZERO {
                    DeviationDirection::Underweight
                } else {
                    DeviationDirection::Balanced
                };
                
                DeviationResult {
                    category: alloc.category.clone(),
                    current_percentage: current_pct,
                    target_percentage: target_pct,
                    deviation,
                    status,
                    direction,
                }
            })
            .collect()
    }
    
    pub fn calculate_return(
        start_snapshot: &Snapshot,
        end_snapshot: &Snapshot,
    ) -> ReturnAnalysis {
        let absolute_return = end_snapshot.total_value - start_snapshot.total_value;
        
        let return_rate = if start_snapshot.total_value > Decimal::ZERO {
            (absolute_return / start_snapshot.total_value) * Decimal::new(100, 0)
        } else {
            Decimal::ZERO
        };
        
        let categories = vec![Category::Cash, Category::Stable, Category::Advanced];
        
        let category_breakdown: Vec<CategoryReturn> = categories
            .into_iter()
            .map(|cat| {
                let start_val = start_snapshot.category_total(&cat);
                let end_val = end_snapshot.category_total(&cat);
                let cat_return = end_val - start_val;
                
                let cat_rate = if start_val > Decimal::ZERO {
                    (cat_return / start_val) * Decimal::new(100, 0)
                } else {
                    Decimal::ZERO
                };
                
                let contribution = if absolute_return != Decimal::ZERO {
                    (cat_return / absolute_return) * Decimal::new(100, 0)
                } else {
                    Decimal::ZERO
                };
                
                CategoryReturn {
                    category: cat,
                    start_value: start_val,
                    end_value: end_val,
                    absolute_return: cat_return,
                    return_rate: cat_rate,
                    contribution_rate: contribution,
                }
            })
            .collect();
        
        ReturnAnalysis {
            start_date: start_snapshot.snapshot_date,
            end_date: end_snapshot.snapshot_date,
            start_value: start_snapshot.total_value,
            end_value: end_snapshot.total_value,
            absolute_return,
            return_rate,
            category_breakdown,
        }
    }
}
}

7. 组件架构

7.1 组件层次

App
├── Router
│   ├── Layout
│   │   ├── Sidebar
│   │   │   └── NavItem
│   │   └── PageContainer
│   │       ├── HomePage
│   │       │   ├── TotalCard
│   │       │   ├── PieChartPair
│   │       │   └── DeviationTable
│   │       │
│   │       ├── AssetsPage
│   │       │   ├── AssetList
│   │       │   │   └── CategoryGroup
│   │       │   │       └── AssetItem
│   │       │   ├── AssetFormModal
│   │       │   └── InventoryMode
│   │       │
│   │       ├── HistoryPage
│   │       │   ├── SnapshotTimeline
│   │       │   │   └── SnapshotCard
│   │       │   └── SnapshotDetailModal
│   │       │
│   │       ├── PlansPage
│   │       │   ├── PlanList
│   │       │   │   └── PlanCard
│   │       │   └── PlanEditorModal
│   │       │
│   │       └── AnalysisPage
│   │           ├── PeriodSelector
│   │           ├── ReturnCards
│   │           ├── AttributionTable
│   │           └── TrendChart
│   │
│   └── ModalPortal
│       └── [各类模态框]

7.2 核心组件示例

根组件

#![allow(unused)]
fn main() {
// src/app.rs
use dioxus::prelude::*;
use crate::router::Route;
use crate::state::provide_app_state;
use crate::components::layout::Layout;

pub fn App() -> Element {
    // 提供全局状态
    let _state = provide_app_state();
    
    // 初始化数据
    use_effect(|| {
        spawn(async {
            // 从数据库加载数据
            load_initial_data().await;
        });
    });
    
    rsx! {
        Router::<Route> {}
    }
}
}

布局组件

#![allow(unused)]
fn main() {
// src/components/layout/mod.rs
use dioxus::prelude::*;
use crate::components::layout::sidebar::Sidebar;

#[component]
pub fn Layout() -> Element {
    rsx! {
        div {
            class: "flex h-screen bg-gray-50",
            
            Sidebar {}
            
            main {
                class: "flex-1 overflow-auto p-8",
                Outlet::<Route> {}
            }
        }
    }
}
}

侧边栏

#![allow(unused)]
fn main() {
// src/components/layout/sidebar.rs
use dioxus::prelude::*;
use crate::router::Route;

#[component]
pub fn Sidebar() -> Element {
    rsx! {
        nav {
            class: "w-56 bg-slate-900 text-white flex flex-col",
            
            // Logo
            div {
                class: "p-6 text-xl font-bold",
                "asset-light"
            }
            
            // Navigation
            div {
                class: "flex-1 px-4 space-y-2",
                
                NavItem { to: Route::Home {}, icon: "home", label: "首页" }
                NavItem { to: Route::Assets {}, icon: "wallet", label: "资产" }
                NavItem { to: Route::History {}, icon: "clock", label: "历史" }
                NavItem { to: Route::Plans {}, icon: "target", label: "配置" }
                NavItem { to: Route::Analysis {}, icon: "chart", label: "收益" }
            }
        }
    }
}

#[component]
fn NavItem(to: Route, icon: &'static str, label: &'static str) -> Element {
    let current_route = use_route::<Route>();
    let is_active = current_route == to;
    
    rsx! {
        Link {
            to: to,
            class: if is_active { 
                "flex items-center px-4 py-3 rounded-lg bg-blue-600" 
            } else { 
                "flex items-center px-4 py-3 rounded-lg hover:bg-slate-800" 
            },
            
            span { class: "mr-3", "{icon}" }
            span { "{label}" }
        }
    }
}
}

8. 路由设计

#![allow(unused)]
fn main() {
// src/router.rs
use dioxus::prelude::*;
use crate::pages::*;
use crate::components::layout::Layout;

#[derive(Routable, Clone, PartialEq)]
pub enum Route {
    #[layout(Layout)]
        #[route("/")]
        Home {},
        
        #[route("/assets")]
        Assets {},
        
        #[route("/history")]
        History {},
        
        #[route("/plans")]
        Plans {},
        
        #[route("/analysis")]
        Analysis {},
    #[end_layout]
    
    #[route("/:..route")]
    NotFound { route: Vec<String> },
}
}

9. 错误处理

#![allow(unused)]
fn main() {
// src/utils/error.rs
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("数据库错误: {0}")]
    Database(#[from] rusqlite::Error),
    
    #[error("验证失败: {0}")]
    Validation(String),
    
    #[error("操作限制: {0}")]
    LimitExceeded(String),
    
    #[error("未找到: {0}")]
    NotFound(String),
}

pub type Result<T> = std::result::Result<T, AppError>;
}

10. 配置文件

Cargo.toml

[package]
name = "asset-light"
version = "0.1.0"
edition = "2021"

[dependencies]
dioxus = { version = "0.5", features = ["desktop", "router"] }
rusqlite = { version = "0.31", features = ["bundled"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1.0", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
rust_decimal = { version = "1.0", features = ["serde"] }
dirs = "5.0"
thiserror = "1.0"
tokio = { version = "1.0", features = ["full"] }

[profile.release]
opt-level = "z"
lto = true

Dioxus.toml

[application]
name = "asset-light"
default_platform = "desktop"

[desktop]
title = "asset-light"
min_width = 1280
min_height = 720

[desktop.window]
title = "asset-light - 个人资产管理"
resizable = true
decorations = true

11. 性能考虑

方面策略
启动速度延迟加载非首页数据
列表渲染虚拟滚动(大数据量时)
状态更新细粒度 Signal,避免全量刷新
数据库索引优化,批量操作
内存及时释放大对象引用

12. 安全考虑

方面措施
数据存储本地 SQLite,无网络传输
输入验证服务层统一验证
SQL 注入使用参数化查询
文件访问仅访问应用专属目录

13. 开发路线图

阶段 1:基础设施 (Week 1)

  • 项目初始化
  • 数据库层实现
  • 基础组件库
  • 路由和布局

阶段 2:核心功能 (Week 2-3)

  • 资产 CRUD
  • 盘点功能
  • 快照存储

阶段 3:配置和分析 (Week 4)

  • 配置方案管理
  • 偏离度计算
  • 收益分析

阶段 4:完善优化 (Week 5)

  • UI 打磨
  • 性能优化
  • 错误处理
  • 测试

修订历史

版本日期作者变更说明
1.0.02025-12-20Architect初始版本