版本: 1.0.0
日期: 2025-12-20
架构师: Architect Agent
| 层面 | 技术选型 | 版本 | 说明 |
| UI 框架 | Dioxus | 0.5.x | Rust 生态的声明式 UI 框架 |
| 语言 | Rust | 1.75+ | 系统级编程语言 |
| 平台 | Desktop (macOS) | - | 基于 WebView 渲染 |
| 数据库 | SQLite | 3.x | 本地嵌入式数据库 |
| ORM | rusqlite | 0.31.x | SQLite Rust 绑定 |
| 序列化 | serde + serde_json | 1.x | JSON 序列化 |
| 日期时间 | chrono | 0.4.x | 日期时间处理 |
| UUID | uuid | 1.x | 唯一标识生成 |
| 精确数值 | rust_decimal | 1.x | 金融级精度计算 |
采用分层架构 + 组件化 UI模式:
┌─────────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ (Dioxus Components) │
├─────────────────────────────────────────────────────────────┤
│ Application Layer │
│ (State Management + Hooks) │
├─────────────────────────────────────────────────────────────┤
│ Domain Layer │
│ (Models + Business Logic) │
├─────────────────────────────────────────────────────────────┤
│ Infrastructure Layer │
│ (Database + File System) │
└─────────────────────────────────────────────────────────────┘
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/ # 项目文档
#![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,
}
}
}
}
#![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()
}
}
}
#![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(),
}
}
}
}
#![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,
}
}
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);
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);
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);
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
);
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);
#![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(())
}
}
}
#![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()))
}
}
#![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)
}
}
}
#![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)
}
}
}
#![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)
}
}
}
#![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,
}
}
}
}
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
│ └── [各类模态框]
#![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}" }
}
}
}
}
#![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> },
}
}
#![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>;
}
[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
[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
| 方面 | 策略 |
| 启动速度 | 延迟加载非首页数据 |
| 列表渲染 | 虚拟滚动(大数据量时) |
| 状态更新 | 细粒度 Signal,避免全量刷新 |
| 数据库 | 索引优化,批量操作 |
| 内存 | 及时释放大对象引用 |
| 方面 | 措施 |
| 数据存储 | 本地 SQLite,无网络传输 |
| 输入验证 | 服务层统一验证 |
| SQL 注入 | 使用参数化查询 |
| 文件访问 | 仅访问应用专属目录 |
| 版本 | 日期 | 作者 | 变更说明 |
| 1.0.0 | 2025-12-20 | Architect | 初始版本 |